diff --git a/.claude/skills/game-control/SKILL.md b/.claude/skills/game-control/SKILL.md new file mode 100644 index 00000000..bdf2daa7 --- /dev/null +++ b/.claude/skills/game-control/SKILL.md @@ -0,0 +1,109 @@ +--- +name: game-control +description: Control a running Tiny game engine via HTTP. Send key presses, releases, and taps to interact with the game. Use when the user asks to play, control, test, or interact with a running game. +allowed-tools: Bash(curl:*), Bash(sleep:*), Read, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_navigate, mcp__playwright__browser_snapshot +argument-hint: [action] [key or instructions] +--- + +# Game Control Skill + +You can control a running Tiny game engine by sending HTTP requests to the debug server. + +## Server + +The game debug server runs on `http://localhost:8081` by default (started with `tiny-cli run`). + +## Available Endpoints + +### List available keys +```bash +curl -s http://localhost:8081/control/keys +``` + +### Press a key (stays pressed until released) +```bash +curl -s -X POST "http://localhost:8081/control/press?key=KEY_NAME" +``` + +### Release a key +```bash +curl -s -X POST "http://localhost:8081/control/release?key=KEY_NAME" +``` + +### Tap a key (press + auto-release after ~50ms) +```bash +curl -s -X POST "http://localhost:8081/control/tap?key=KEY_NAME" +``` + +## Available Keys + +Direction: `ARROW_LEFT`, `ARROW_RIGHT`, `ARROW_UP`, `ARROW_DOWN` +Action: `SPACE`, `ENTER`, `ESCAPE`, `TAB`, `BACKSPACE`, `DELETE` +Letters: `A` through `Z` +Numbers: `NUM0` through `NUM9` +Modifiers: `SHIFT`, `CTRL`, `ALT` +Function: `F1` through `F12` + +## Usage Patterns + +### Tap a key once (one-shot action like jump or shoot) +```bash +curl -s -X POST "http://localhost:8081/control/tap?key=SPACE" +``` + +### Hold a key for sustained movement +```bash +# Hold right for 500ms then release +curl -s -X POST "http://localhost:8081/control/press?key=ARROW_RIGHT" +sleep 0.5 +curl -s -X POST "http://localhost:8081/control/release?key=ARROW_RIGHT" +``` + +### Multiple rapid taps +```bash +for i in $(seq 1 5); do + curl -s -X POST "http://localhost:8081/control/tap?key=SPACE" + sleep 0.1 +done +``` + +### Simultaneous keys (e.g., diagonal movement) +```bash +curl -s -X POST "http://localhost:8081/control/press?key=ARROW_RIGHT" +curl -s -X POST "http://localhost:8081/control/press?key=ARROW_UP" +sleep 0.5 +curl -s -X POST "http://localhost:8081/control/release?key=ARROW_RIGHT" +curl -s -X POST "http://localhost:8081/control/release?key=ARROW_UP" +``` + +## How to Use + +When the user provides `$ARGUMENTS`: + +1. **If arguments describe an action** (e.g., "move right", "jump", "press space 3 times"): + - Translate the instruction into the appropriate curl commands + - Execute them + +2. **If arguments are a key name** (e.g., "SPACE", "ARROW_LEFT"): + - Tap that key once + +3. **If no arguments or "help"**: + - List the available keys by calling `GET /control/keys` + - Show usage examples + +4. **If "play" or complex instructions** (e.g., "play the game", "explore the level"): + - Take a screenshot first to see the game state (if possible via the serve endpoint or playwright) + - Send appropriate inputs based on what you observe + - Iterate: act, observe, act again + +## Error Handling + +- If curl fails with connection refused, the game is not running. Tell the user to start it with `tiny-cli run`. +- If the response contains `"error":"Engine not ready"`, the game is still loading. Wait a moment and retry. +- If the response contains `"error":"Unknown key"`, check the key name against the available keys list. + +## Response Format + +All endpoints return JSON: +- Success: `{"ok":true,"action":"press","key":"ARROW_LEFT"}` +- Error: `{"error":"Missing 'key' query parameter"}` diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 710eabff..455fa542 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,12 +32,20 @@ jobs: files: | tiny-cli/build/distributions/tiny-cli-${{github.ref_name}}.zip tiny-cli/build/distributions/tiny-cli-${{github.ref_name}}.tar + - name: Install butler + if: startsWith(github.ref, 'refs/tags/') + uses: jdno/setup-butler@v1 + - name: Publish to itch.io + if: startsWith(github.ref, 'refs/tags/') + env: + BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }} + run: butler push tiny-cli/build/distributions/tiny-cli-${{github.ref_name}}.zip dwursteisen/tiny:tiny-cli --userversion ${{github.ref_name}} - name: Update documentation sample run: | unzip tiny-cli/build/distributions/tiny-cli-${{github.ref_name}}.zip tiny-cli-${{github.ref_name}}/bin/tiny-cli docs --output tiny-doc/src/docs/asciidoc/dependencies/tiny-cli-commands.adoc - tiny-cli-${{github.ref_name}}/bin/tiny-cli export tiny-sample - unzip -o -d tiny-doc/src/docs/asciidoc/sample/game-example tiny-sample/tiny-export.zip + tiny-cli-${{github.ref_name}}/bin/tiny-cli export tiny-samples/breakout + unzip -o -d tiny-doc/src/docs/asciidoc/sample/game-example tiny-samples/breakout/tiny-export.zip tiny-cli-${{github.ref_name}}/bin/tiny-cli export tiny-cli/src/main/resources/sfx unzip -o -d tiny-doc/src/docs/asciidoc/sample/sfx-editor tiny-cli/src/main/resources/sfx/tiny-export.zip - name: Build examples and generate Asciidoctor HTML pages @@ -48,6 +56,76 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./tiny-doc/build/docs/asciidoc + - name: Upload CLI distribution + uses: actions/upload-artifact@v4 + with: + name: tiny-cli-dist + path: tiny-cli/build/distributions/tiny-cli-${{github.ref_name}}.zip + retention-days: 1 + + test-export: + needs: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'zulu' + - name: Download CLI distribution + uses: actions/download-artifact@v4 + with: + name: tiny-cli-dist + - name: Extract CLI distribution (Unix) + if: runner.os != 'Windows' + run: unzip tiny-cli-${{github.ref_name}}.zip + - name: Extract CLI distribution (Windows) + if: runner.os == 'Windows' + run: Expand-Archive -Path tiny-cli-${{github.ref_name}}.zip -DestinationPath . + - name: Test web export (Unix) + if: runner.os != 'Windows' + run: | + tiny-cli-${{github.ref_name}}/bin/tiny-cli export tiny-samples/breakout + test -f tiny-samples/breakout/tiny-export.zip + - name: Test web export (Windows) + if: runner.os == 'Windows' + run: | + tiny-cli-${{github.ref_name}}\bin\tiny-cli.bat export tiny-samples\breakout + if (-Not (Test-Path tiny-samples\breakout\tiny-export.zip)) { exit 1 } + - name: Test desktop export (Unix) + if: runner.os != 'Windows' + run: | + tiny-cli-${{github.ref_name}}/bin/tiny-cli export tiny-samples/breakout -p desktop --exclude-jdk + test -d exported-game + ls exported-game/*.jar + - name: Test desktop export (Windows) + if: runner.os == 'Windows' + run: | + tiny-cli-${{github.ref_name}}\bin\tiny-cli.bat export tiny-samples\breakout -p desktop --exclude-jdk + if (-Not (Test-Path exported-game)) { exit 1 } + if (-Not (Get-ChildItem exported-game\*.jar)) { exit 1 } + - name: Test desktop export with JDK (Unix) + if: runner.os != 'Windows' + run: | + tiny-cli-${{github.ref_name}}/bin/tiny-cli export tiny-samples/breakout -p desktop --include-jdk -o exported-game-jdk + test -d exported-game-jdk + if [ "$(uname -s)" = "Linux" ]; then + ls exported-game-jdk/*.deb + elif [ "$(uname -s)" = "Darwin" ]; then + ls exported-game-jdk/*.dmg + fi + - name: Test desktop export with JDK (Windows) + if: runner.os == 'Windows' + run: | + tiny-cli-${{github.ref_name}}\bin\tiny-cli.bat export tiny-samples\breakout -p desktop --include-jdk -o exported-game-jdk + if (-Not (Test-Path exported-game-jdk)) { exit 1 } + if (-Not (Get-ChildItem exported-game-jdk\*.exe)) { exit 1 } env: GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=true -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" diff --git a/.gitignore b/.gitignore index ee365118..7b65dd81 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,7 @@ build/ .test/ ### IntelliJ IDEA ### -.idea/** -.idea/ +.idea *.iws *.iml *.ipr @@ -44,5 +43,9 @@ bin/ /tiny-doc/src/docs/asciidoc/dependencies/ /tiny-doc/src/docs/asciidoc/sample/game-example /tiny-doc/src/docs/asciidoc/sample/sfx-editor +/tiny-doc/src/docs/asciidoc/sample/home + +**/tiny-export.zip + .kotlin \ No newline at end of file diff --git a/.idea/artifacts/tiny_cli_js_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_cli_js_DEV_SNAPSHOT.xml deleted file mode 100644 index 383cb117..00000000 --- a/.idea/artifacts/tiny_cli_js_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-cli/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_cli_jvm_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_cli_jvm_DEV_SNAPSHOT.xml deleted file mode 100644 index 9335c2e9..00000000 --- a/.idea/artifacts/tiny_cli_jvm_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-cli/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_doc_annotations_js_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_doc_annotations_js_DEV_SNAPSHOT.xml deleted file mode 100644 index 7e7d67e4..00000000 --- a/.idea/artifacts/tiny_doc_annotations_js_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-doc-annotations/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_doc_annotations_jvm_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_doc_annotations_jvm_DEV_SNAPSHOT.xml deleted file mode 100644 index 6ef44cdc..00000000 --- a/.idea/artifacts/tiny_doc_annotations_jvm_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-doc-annotations/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_doc_generator_js_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_doc_generator_js_DEV_SNAPSHOT.xml deleted file mode 100644 index 027ed3d6..00000000 --- a/.idea/artifacts/tiny_doc_generator_js_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-doc-generator/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_doc_generator_jvm_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_doc_generator_jvm_DEV_SNAPSHOT.xml deleted file mode 100644 index 56ddddff..00000000 --- a/.idea/artifacts/tiny_doc_generator_jvm_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-doc-generator/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_engine_js_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_engine_js_DEV_SNAPSHOT.xml deleted file mode 100644 index ae061d86..00000000 --- a/.idea/artifacts/tiny_engine_js_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-engine/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_engine_jvm_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_engine_jvm_DEV_SNAPSHOT.xml deleted file mode 100644 index 81bd4b18..00000000 --- a/.idea/artifacts/tiny_engine_jvm_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-engine/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_web_editor_js_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_web_editor_js_DEV_SNAPSHOT.xml deleted file mode 100644 index ab61da67..00000000 --- a/.idea/artifacts/tiny_web_editor_js_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-web-editor/build/libs - - - - - \ No newline at end of file diff --git a/.idea/artifacts/tiny_web_editor_jvm_DEV_SNAPSHOT.xml b/.idea/artifacts/tiny_web_editor_jvm_DEV_SNAPSHOT.xml deleted file mode 100644 index f40eb118..00000000 --- a/.idea/artifacts/tiny_web_editor_jvm_DEV_SNAPSHOT.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - $PROJECT_DIR$/tiny-web-editor/build/libs - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 6238726f..00000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0476299f..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index aea9e961..1e4d1d6b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ Tiny is a Kotlin Multiplatform game engine with Lua scripting support that compi - **tiny-doc-annotations**: Annotations for documentation generation - **tiny-doc-generator**: KSP-based documentation processor - **tiny-web-editor**: Web-based editor interface -- **tiny-sample**: Sample games and examples +- **tiny-samples**: Sample games and examples ## Key Technologies diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e82825b2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,82 @@ +# Contributing to Tiny + +Thanks for your interest in contributing to Tiny! This guide will help you get started. + +## Prerequisites + +- **JDK 17** or later +- **Gradle** (wrapper included in the project) +- **Make** (optional, for convenience commands) + +## Setup + +```bash +git clone https://github.com/minigdx/tiny.git +cd tiny +./gradlew build +``` + +## Development Workflow + +### Building + +```bash +./gradlew build # Build all modules +./gradlew publishToMavenLocal # Deploy to local Maven repository +``` + +### Testing + +```bash +./gradlew test # Run all tests +./gradlew :tiny-engine:test # Run tests for a specific module +./gradlew :tiny-engine:jvmTest # Run JVM-specific tests +./gradlew :tiny-engine:jsTest # Run JS-specific tests +``` + +### Linting + +```bash +make lint # or ./gradlew ktlintCheck +make lintfix # or ./gradlew ktlintFormat +``` + +### Installing the CLI locally + +```bash +make install # Builds and installs tiny-cli to ~/.bin/tiny-cli +``` + +## Project Structure + +| Module | Description | +|-------------------------|------------------------------------------| +| `tiny-engine` | Core multiplatform game engine | +| `tiny-cli` | CLI tool for development workflows | +| `tiny-doc` | Documentation (Asciidoctor) | +| `tiny-doc-annotations` | Annotations for doc generation | +| `tiny-doc-generator` | KSP-based documentation processor | +| `tiny-web-editor` | Web-based editor interface | +| `tiny-samples` | Sample games and examples | + +## Making Changes + +1. Fork the repository +2. Create a feature branch from `main` +3. Make your changes +4. Run tests: `./gradlew test` +5. Run the linter: `make lint` +6. Open a Pull Request against `main` + +## Code Style + +This project uses [ktlint](https://pinterest.github.io/ktlint/) for Kotlin code formatting. Run `make lintfix` to auto-fix formatting issues before submitting. + +## Reporting Issues + +Please use [GitHub Issues](https://github.com/minigdx/tiny/issues) to report bugs or request features. Include: + +- Steps to reproduce the issue +- Expected vs actual behavior +- Platform (desktop/web) and OS +- Tiny version diff --git a/Makefile b/Makefile index fb013225..c53d9c23 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,65 @@ install: docs: install ./gradlew tiny-web-editor:tinyWebEditor - tiny-cli docs --output tiny-doc/src/docs/asciidoc/dependencies/tiny-cli-commands.adoc - tiny-cli export tiny-sample - unzip -o -d tiny-doc/src/docs/asciidoc/sample/game-example tiny-sample/tiny-export.zip + tiny-cli docs --output tiny-doc/src/docs/asciidoc/tiny-cli-commands.json + tiny-cli export tiny-samples/breakout + unzip -o -d tiny-doc/src/docs/asciidoc/sample/game-example tiny-samples/breakout/tiny-export.zip + tiny-cli export tiny-samples/home + unzip -o -d tiny-doc/src/docs/asciidoc/sample/home tiny-samples/home/tiny-export.zip tiny-cli export tiny-cli/src/main/resources/sfx unzip -o -d tiny-doc/src/docs/asciidoc/sample/sfx-editor tiny-cli/src/main/resources/sfx/tiny-export.zip ./gradlew asciidoctor -Pversion=$(uuidgen) + +# Add a game to the showcase +# Usage: make add game= [url=] [duration=] +duration ?= 5 +add: install + @if [ -z "$(game)" ]; then echo "Error: 'game' is required. Usage: make add game= [url=] [duration=]"; exit 1; fi + @command -v ffmpeg >/dev/null 2>&1 || { echo "Error: ffmpeg is required but not installed."; exit 1; } + @set -e; \ + NAME=$$(basename "$(game)"); \ + GIF_NAME=$$(echo "$$NAME" | tr '[:upper:]' '[:lower:]'); \ + DISPLAY_NAME=$$(echo "$$NAME" | sed 's/[-_]/ /g' | awk '{for(i=1;i<=NF;i++) $$i=toupper(substr($$i,1,1)) tolower(substr($$i,2))}1'); \ + GIF_PATH="tiny-doc/src/docs/asciidoc/sample/$$GIF_NAME.gif"; \ + if grep -q "$$GIF_NAME.gif" README.md; then \ + echo "Warning: $$GIF_NAME.gif already referenced in README.md. Skipping."; \ + exit 0; \ + fi; \ + echo "Recording $$NAME..."; \ + tiny-cli record "$(game)" --headless -d $(duration) -o "/tmp/tiny-raw-$$GIF_NAME.gif"; \ + echo "Scaling to 256x256..."; \ + ffmpeg -i "/tmp/tiny-raw-$$GIF_NAME.gif" -vf "scale=256:256:force_original_aspect_ratio=decrease:flags=neighbor,pad=256:256:(ow-iw)/2:(oh-ih)/2:color=black" -loop 0 -y "$$GIF_PATH"; \ + rm -f "/tmp/tiny-raw-$$GIF_NAME.gif"; \ + echo "Updating README.md..."; \ + awk -v name="$$DISPLAY_NAME" -v gif="./$$GIF_PATH" -v url="$(url)" 'BEGIN{g=0} /Games Made With Tiny/{g=1} {if(g==1 && $$0=="---"){if(url!=""){printf "[![%s](%s)](%s)\n",name,gif,url}else{printf "![%s](%s)\n",name,gif} g=0} print}' README.md > README.md.tmp && mv README.md.tmp README.md; \ + echo "Updating tiny-showcase.adoc..."; \ + if [ -n "$(url)" ]; then \ + ADOC_LINE="image:sample/$$GIF_NAME.gif[$$DISPLAY_NAME - a game made with Tiny game engine,link=$(url)]"; \ + else \ + ADOC_LINE="image:sample/$$GIF_NAME.gif[$$DISPLAY_NAME - a game made with Tiny game engine]"; \ + fi; \ + awk -v line="$$ADOC_LINE" '/^image:/{w=1; print; next} w==1{print line; w=0} {print}' tiny-doc/src/docs/asciidoc/tiny-showcase.adoc > tiny-showcase.adoc.tmp && mv tiny-showcase.adoc.tmp tiny-doc/src/docs/asciidoc/tiny-showcase.adoc; \ + echo "Added $$DISPLAY_NAME to the showcase!" + +sfx: + ./gradlew :tiny-cli:run --args="run ." -Ptiny.workDir=tiny-cli/src/main/resources/sfx + +sample: + ./gradlew :tiny-cli:run --args="run ." -Ptiny.workDir=tiny-samples/breakout + +home: + ./gradlew :tiny-cli:run --args="run ." -Ptiny.workDir=tiny-samples/home + +test-linux-export: + ./gradlew assembleDist -Pversion=DEV-SNAPSHOT + docker run --rm \ + -v "$(PWD)/tiny-cli/build/distributions:/dist" \ + -v "$(PWD)/tiny-samples/breakout:/tiny-sample" \ + eclipse-temurin:17-jdk-jammy \ + bash -c '\ + apt-get update && apt-get install -y --no-install-recommends fakeroot binutils unzip && \ + cd /tmp && \ + unzip /dist/tiny-cli-DEV-SNAPSHOT.zip && \ + tiny-cli-DEV-SNAPSHOT/bin/tiny-cli export /tiny-sample -p desktop --include-jdk -o /tmp/exported-game-jdk && \ + ls /tmp/exported-game-jdk/*.deb \ + ' diff --git a/README.md b/README.md index e2f61577..dc393333 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ Want to create games like these? Dive into the docs and start building: [![Gravity Balls](./tiny-doc/src/docs/asciidoc/sample/gravity-balls.gif)](https://dwursteisen.itch.io/gravity-balls) [![Reflections](./tiny-doc/src/docs/asciidoc/sample/reflections.gif)](https://dwursteisen.itch.io/macro-jams-06-reflections) +[![2026 1bit Jam 2](./tiny-doc/src/docs/asciidoc/sample/2026-1bit-jam-2.gif)](https://dwursteisen.itch.io/pair-of-pipes) --- ## 🤝 Contributing diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4870bf77..689289cd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ luak = "1.2.0" lwjgl = "3.3.6" minigdx-developer = "1.5.0" mokkery = "2.10.1" +pebble = "3.2.3" rsyntax = "3.6.0" slf4j = "2.0.7" @@ -28,6 +29,8 @@ kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializat luak = { module = "com.github.minigdx:luak", version.ref = "luak" } +pebble = { module = "io.pebbletemplates:pebble", version.ref = "pebble" } + lwjgl-core = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" } lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 72613ee1..0a0d701c 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -12,135 +12,135 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz#f13c7c205915eb91ae54c557f5e92bddd8be0e83" integrity sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ== -"@esbuild/aix-ppc64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz#ee6b7163a13528e099ecf562b972f2bcebe0aa97" - integrity sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw== - -"@esbuild/android-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz#115fc76631e82dd06811bfaf2db0d4979c16e2cb" - integrity sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg== - -"@esbuild/android-arm@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.10.tgz#8d5811912da77f615398611e5bbc1333fe321aa9" - integrity sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w== - -"@esbuild/android-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.10.tgz#e3e96516b2d50d74105bb92594c473e30ddc16b1" - integrity sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg== - -"@esbuild/darwin-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz#6af6bb1d05887dac515de1b162b59dc71212ed76" - integrity sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA== - -"@esbuild/darwin-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz#99ae82347fbd336fc2d28ffd4f05694e6e5b723d" - integrity sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg== - -"@esbuild/freebsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz#0c6d5558a6322b0bdb17f7025c19bd7d2359437d" - integrity sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg== - -"@esbuild/freebsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz#8c35873fab8c0857a75300a3dcce4324ca0b9844" - integrity sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA== - -"@esbuild/linux-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz#3edc2f87b889a15b4cedaf65f498c2bed7b16b90" - integrity sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ== - -"@esbuild/linux-arm@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz#86501cfdfb3d110176d80c41b27ed4611471cde7" - integrity sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg== - -"@esbuild/linux-ia32@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz#e6589877876142537c6864680cd5d26a622b9d97" - integrity sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ== - -"@esbuild/linux-loong64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz#11119e18781f136d8083ea10eb6be73db7532de8" - integrity sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg== - -"@esbuild/linux-mips64el@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz#3052f5436b0c0c67a25658d5fc87f045e7def9e6" - integrity sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA== - -"@esbuild/linux-ppc64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz#2f098920ee5be2ce799f35e367b28709925a8744" - integrity sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA== - -"@esbuild/linux-riscv64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz#fa51d7fd0a22a62b51b4b94b405a3198cf7405dd" - integrity sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA== - -"@esbuild/linux-s390x@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz#a27642e36fc282748fdb38954bd3ef4f85791e8a" - integrity sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew== - -"@esbuild/linux-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz#9d9b09c0033d17529570ced6d813f98315dfe4e9" - integrity sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA== - -"@esbuild/netbsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz#25c09a659c97e8af19e3f2afd1c9190435802151" - integrity sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A== - -"@esbuild/netbsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz#7fa5f6ffc19be3a0f6f5fd32c90df3dc2506937a" - integrity sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig== - -"@esbuild/openbsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz#8faa6aa1afca0c6d024398321d6cb1c18e72a1c3" - integrity sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw== - -"@esbuild/openbsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz#a42979b016f29559a8453d32440d3c8cd420af5e" - integrity sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw== - -"@esbuild/openharmony-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz#fd87bfeadd7eeb3aa384bbba907459ffa3197cb1" - integrity sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag== - -"@esbuild/sunos-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz#3a18f590e36cb78ae7397976b760b2b8c74407f4" - integrity sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ== - -"@esbuild/win32-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz#e71741a251e3fd971408827a529d2325551f530c" - integrity sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw== - -"@esbuild/win32-ia32@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz#c6f010b5d3b943d8901a0c87ea55f93b8b54bf94" - integrity sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw== - -"@esbuild/win32-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz#e4b3e255a1b4aea84f6e1d2ae0b73f826c3785bd" - integrity sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw== +"@esbuild/aix-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" + integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== + +"@esbuild/android-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" + integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== + +"@esbuild/android-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" + integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== + +"@esbuild/android-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" + integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== + +"@esbuild/darwin-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" + integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== + +"@esbuild/darwin-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" + integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== + +"@esbuild/freebsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" + integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== + +"@esbuild/freebsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" + integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== + +"@esbuild/linux-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" + integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== + +"@esbuild/linux-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" + integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== + +"@esbuild/linux-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" + integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== + +"@esbuild/linux-loong64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" + integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== + +"@esbuild/linux-mips64el@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" + integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== + +"@esbuild/linux-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" + integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== + +"@esbuild/linux-riscv64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" + integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== + +"@esbuild/linux-s390x@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" + integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== + +"@esbuild/linux-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" + integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== + +"@esbuild/netbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" + integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== + +"@esbuild/netbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" + integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== + +"@esbuild/openbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" + integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== + +"@esbuild/openbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" + integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== + +"@esbuild/openharmony-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" + integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== + +"@esbuild/sunos-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" + integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== + +"@esbuild/win32-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" + integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== + +"@esbuild/win32-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" + integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== + +"@esbuild/win32-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" + integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -155,12 +155,11 @@ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" "@jridgewell/gen-mapping@^0.3.5": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": @@ -168,56 +167,132 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - "@jridgewell/source-map@^0.3.3": - version "0.3.6" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" - integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + version "0.3.11" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== dependencies: "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsonjoy.com/base64@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-17.67.0.tgz#7eeda3cb41138d77a90408fd2e42b2aba10576d7" + integrity sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw== + "@jsonjoy.com/base64@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== +"@jsonjoy.com/buffers@17.67.0", "@jsonjoy.com/buffers@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz#5c58dbcdeea8824ce296bd1cfce006c2eb167b3d" + integrity sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw== + "@jsonjoy.com/buffers@^1.0.0", "@jsonjoy.com/buffers@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.2.0.tgz#57b9bbc509055de80f22cf6b696ac7efd7554046" - integrity sha512-6RX+W5a+ZUY/c/7J5s5jK9UinLfJo5oWKh84fb4X0yK2q4WXEWUWZWuEMjvCb1YNUQhEAhUfr5scEGOH7jC4YQ== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz#8d99c7f67eaf724d3428dfd9826c6455266a5c83" + integrity sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA== + +"@jsonjoy.com/codegen@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz#3635fd8769d77e19b75dc5574bc9756019b2e591" + integrity sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q== "@jsonjoy.com/codegen@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz#5c23f796c47675f166d23b948cdb889184b93207" integrity sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g== +"@jsonjoy.com/fs-core@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz#320728b4b7bef63abb60e7630351623899237411" + integrity sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz#02bac88c4968ddf2effbd7452861aaed60ba3557" + integrity sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + thingies "^2.5.0" + +"@jsonjoy.com/fs-node-builtins@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz#a32a5bcb093f8b34a99aa8957e993a52ec316662" + integrity sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw== + +"@jsonjoy.com/fs-node-to-fsa@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz#33fc503e50d283ac5fc510e3accced7fccecf2f4" + integrity sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw== + dependencies: + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + +"@jsonjoy.com/fs-node-utils@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz#788e95052aa99744f6e8e55b5098afc203df2b9e" + integrity sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg== + dependencies: + "@jsonjoy.com/fs-node-builtins" "4.56.10" + +"@jsonjoy.com/fs-node@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz#70b18bfaf14544a9820d2016e913dde12c6de991" + integrity sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" + glob-to-regex.js "^1.0.0" + thingies "^2.5.0" + +"@jsonjoy.com/fs-print@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz#7c181b9aefcc1b268be0e6233bff26310c355335" + integrity sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw== + dependencies: + "@jsonjoy.com/fs-node-utils" "4.56.10" + tree-dump "^1.1.0" + +"@jsonjoy.com/fs-snapshot@4.56.10": + version "4.56.10" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz#05aadd2c0eaa855b13d6cb17d29b7c8cee239c8c" + integrity sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g== + dependencies: + "@jsonjoy.com/buffers" "^17.65.0" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/json-pack" "^17.65.0" + "@jsonjoy.com/util" "^17.65.0" + "@jsonjoy.com/json-pack@^1.11.0": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.18.0.tgz#488554da33092839580e9c30e2a4a35496db6112" - integrity sha512-Wql75nw7QEjejpPuOu/LtgYgG7VC3uGho4rICqIeoXdpoqjoQ/hrc195Dms183p4a7cNtjfutcHGdr/2TUVChA== + version "1.21.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz#93f8dd57fe3a3a92132b33d1eb182dcd9e7629fa" + integrity sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg== dependencies: "@jsonjoy.com/base64" "^1.1.2" "@jsonjoy.com/buffers" "^1.2.0" @@ -226,6 +301,28 @@ "@jsonjoy.com/util" "^1.9.0" hyperdyperid "^1.2.0" thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pack@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz#8dd8ff65dd999c5d4d26df46c63915c7bdec093a" + integrity sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w== + dependencies: + "@jsonjoy.com/base64" "17.67.0" + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + "@jsonjoy.com/json-pointer" "17.67.0" + "@jsonjoy.com/util" "17.67.0" + hyperdyperid "^1.2.0" + thingies "^2.5.0" + tree-dump "^1.1.0" + +"@jsonjoy.com/json-pointer@17.67.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz#74439573dc046e0c9a3a552fb94b391bc75313b8" + integrity sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA== + dependencies: + "@jsonjoy.com/util" "17.67.0" "@jsonjoy.com/json-pointer@^1.0.2": version "1.0.2" @@ -235,6 +332,14 @@ "@jsonjoy.com/codegen" "^1.0.0" "@jsonjoy.com/util" "^1.9.0" +"@jsonjoy.com/util@17.67.0", "@jsonjoy.com/util@^17.65.0": + version "17.67.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-17.67.0.tgz#7c4288fc3808233e55c7610101e7bb4590cddd3f" + integrity sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew== + dependencies: + "@jsonjoy.com/buffers" "17.67.0" + "@jsonjoy.com/codegen" "17.67.0" + "@jsonjoy.com/util@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.9.0.tgz#7ee95586aed0a766b746cd8d8363e336c3c47c46" @@ -244,9 +349,9 @@ "@jsonjoy.com/codegen" "^1.0.0" "@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -262,125 +367,140 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@rollup/rollup-android-arm-eabi@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz#59e7478d310f7e6a7c72453978f562483828112f" - integrity sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA== - -"@rollup/rollup-android-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz#a825192a0b1b2f27a5c950c439e7e37a33c5d056" - integrity sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w== - -"@rollup/rollup-darwin-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz#4ee37078bccd725ae3c5f30ef92efc8e1bf886f3" - integrity sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg== - -"@rollup/rollup-darwin-x64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz#43cc08bd05bf9f388f125e7210a544e62d368d90" - integrity sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw== - -"@rollup/rollup-freebsd-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz#bc8e640e28abe52450baf3fc80d9b26d9bb6587d" - integrity sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ== - -"@rollup/rollup-freebsd-x64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz#e981a22e057cc8c65bb523019d344d3a66b15bbc" - integrity sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw== - -"@rollup/rollup-linux-arm-gnueabihf@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz#4036b68904f392a20f3499d63b33e055b67eb274" - integrity sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ== - -"@rollup/rollup-linux-arm-musleabihf@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz#d3b1b9589606e0ff916801c855b1ace9e733427a" - integrity sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q== - -"@rollup/rollup-linux-arm64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz#cbf0943c477e3b96340136dd3448eaf144378cf2" - integrity sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg== - -"@rollup/rollup-linux-arm64-musl@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz#837f5a428020d5dce1c3b4cc049876075402cf78" - integrity sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g== - -"@rollup/rollup-linux-loong64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz#532c214ababb32ab4bc21b4054278b9a8979e516" - integrity sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ== - -"@rollup/rollup-linux-ppc64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz#93900163b61b49cee666d10ee38257a8b1dd161a" - integrity sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g== - -"@rollup/rollup-linux-riscv64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz#f0ffdcc7066ca04bc972370c74289f35c7a7dc42" - integrity sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg== - -"@rollup/rollup-linux-riscv64-musl@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz#361695c39dbe96773509745d77a870a32a9f8e48" - integrity sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA== - -"@rollup/rollup-linux-s390x-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz#09fc6cc2e266a2324e366486ae5d1bca48c43a6a" - integrity sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA== - -"@rollup/rollup-linux-x64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz#aa9d5b307c08f05d3454225bb0a2b4cc87eeb2e1" - integrity sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg== - -"@rollup/rollup-linux-x64-musl@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz#26949e5b4645502a61daba2f7a8416bd17cb5382" - integrity sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw== - -"@rollup/rollup-openharmony-arm64@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz#ef493c072f9dac7e0edb6c72d63366846b6ffcd9" - integrity sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA== - -"@rollup/rollup-win32-arm64-msvc@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz#56e1aaa6a630d2202ee7ec0adddd05cf384ffd44" - integrity sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ== - -"@rollup/rollup-win32-ia32-msvc@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz#0a44bbf933a9651c7da2b8569fa448dec0de7480" - integrity sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw== - -"@rollup/rollup-win32-x64-gnu@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz#730e12f0b60b234a7c02d5d3179ca3ec7972033d" - integrity sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ== - -"@rollup/rollup-win32-x64-msvc@4.52.4": - version "4.52.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz#5b2dd648a960b8fa00d76f2cc4eea2f03daa80f4" - integrity sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w== +"@rollup/rollup-android-arm-eabi@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz#add5e608d4e7be55bc3ca3d962490b8b1890e088" + integrity sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg== + +"@rollup/rollup-android-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz#10bd0382b73592beee6e9800a69401a29da625c4" + integrity sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w== + +"@rollup/rollup-darwin-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz#1e99ab04c0b8c619dd7bbde725ba2b87b55bfd81" + integrity sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg== + +"@rollup/rollup-darwin-x64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz#69e741aeb2839d2e8f0da2ce7a33d8bd23632423" + integrity sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w== + +"@rollup/rollup-freebsd-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz#3736c232a999c7bef7131355d83ebdf9651a0839" + integrity sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug== + +"@rollup/rollup-freebsd-x64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz#227dcb8f466684070169942bd3998901c9bfc065" + integrity sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q== + +"@rollup/rollup-linux-arm-gnueabihf@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz#ba004b30df31b724f99ce66e7128248bea17cb0c" + integrity sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw== + +"@rollup/rollup-linux-arm-musleabihf@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz#6929f3e07be6b6da5991f63c6b68b3e473d0a65a" + integrity sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw== + +"@rollup/rollup-linux-arm64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz#06e89fd4a25d21fe5575d60b6f913c0e65297bfa" + integrity sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g== + +"@rollup/rollup-linux-arm64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz#fddabf395b90990d5194038e6cd8c00156ed8ac0" + integrity sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q== + +"@rollup/rollup-linux-loong64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz#04c10bb764bbf09a3c1bd90432e92f58d6603c36" + integrity sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA== + +"@rollup/rollup-linux-loong64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz#f2450361790de80581d8687ea19142d8a4de5c0f" + integrity sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw== + +"@rollup/rollup-linux-ppc64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz#0474f4667259e407eee1a6d38e29041b708f6a30" + integrity sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w== + +"@rollup/rollup-linux-ppc64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz#9f32074819eeb1ddbe51f50ea9dcd61a6745ec33" + integrity sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw== + +"@rollup/rollup-linux-riscv64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz#3fdb9d4b1e29fb6b6a6da9f15654d42eb77b99b2" + integrity sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A== + +"@rollup/rollup-linux-riscv64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz#1de780d64e6be0e3e8762035c22e0d8ea68df8ed" + integrity sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw== + +"@rollup/rollup-linux-s390x-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz#1da022ffd2d9e9f0fd8344ea49e113001fbcac64" + integrity sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg== + +"@rollup/rollup-linux-x64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz#78c16eef9520bd10e1ea7a112593bb58e2842622" + integrity sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg== + +"@rollup/rollup-linux-x64-musl@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz#a7598591b4d9af96cb3167b50a5bf1e02dfea06c" + integrity sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw== + +"@rollup/rollup-openbsd-x64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz#c51d48c07cd6c466560e5bed934aec688ce02614" + integrity sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw== + +"@rollup/rollup-openharmony-arm64@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz#f09921d0b2a0b60afbf3586d2a7a7f208ba6df17" + integrity sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ== + +"@rollup/rollup-win32-arm64-msvc@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz#08d491717135376e4a99529821c94ecd433d5b36" + integrity sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ== + +"@rollup/rollup-win32-ia32-msvc@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz#b0c12aac1104a8b8f26a5e0098e5facbb3e3964a" + integrity sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew== + +"@rollup/rollup-win32-x64-gnu@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz#b9cccef26f5e6fdc013bf3c0911a3c77428509d0" + integrity sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ== + +"@rollup/rollup-win32-x64-msvc@4.57.1": + version "4.57.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz#a03348e7b559c792b6277cc58874b89ef46e1e72" + integrity sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA== "@socket.io/component-emitter@~3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" - integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== dependencies: "@types/connect" "*" "@types/node" "*" @@ -401,16 +521,16 @@ "@types/node" "*" "@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== dependencies: "@types/node" "*" "@types/cors@^2.8.12": - version "2.8.13" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.13.tgz#b8ade22ba455a1b8cb3b5d3f35910fd204f84f94" - integrity sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA== + version "2.8.19" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" + integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== dependencies: "@types/node" "*" @@ -440,19 +560,20 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.33" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" - integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" + "@types/send" "*" -"@types/express-serve-static-core@^4.17.21": - version "4.19.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" - integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== +"@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.33": + version "4.19.8" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz#99b960322a4d576b239a640ab52ef191989b036f" + integrity sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -460,24 +581,23 @@ "@types/send" "*" "@types/express@*": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" "@types/express@^4.17.21": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== + version "4.17.25" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" + integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" - "@types/serve-static" "*" + "@types/serve-static" "^1" "@types/http-errors@*": version "2.0.5" @@ -485,53 +605,45 @@ integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== "@types/http-proxy@^1.17.8": - version "1.17.10" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.10.tgz#e576c8e4a0cc5c6a138819025a88e167ebb38d6c" - integrity sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g== + version "1.17.17" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.17.tgz#d9e2c4571fe3507343cb210cd41790375e59a533" + integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== dependencies: "@types/node" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.15": +"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/node-forge@^1.3.0": - version "1.3.11" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" - integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + version "1.3.14" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.14.tgz#006c2616ccd65550560c2757d8472eb6d3ecea0b" + integrity sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw== dependencies: "@types/node" "*" "@types/node@*", "@types/node@>=10.0.0": - version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.2.3.tgz#9c18245be768bdb4ce631566c7da303a5c99a7f8" + integrity sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ== + dependencies: + undici-types "~7.16.0" "@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== "@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/retry@0.12.2": version "0.12.2" @@ -539,16 +651,16 @@ integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== "@types/send@*": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.0.tgz#ae9dfa0e3ab0306d3c566182324a54c4be2fb45a" - integrity sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== dependencies: "@types/node" "*" "@types/send@<1": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + version "0.17.6" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" + integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== dependencies: "@types/mime" "^1" "@types/node" "*" @@ -560,22 +672,22 @@ dependencies: "@types/express" "*" -"@types/serve-static@*": - version "1.15.1" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" - integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== +"@types/serve-static@^1", "@types/serve-static@^1.15.5": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" + integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== dependencies: - "@types/mime" "*" + "@types/http-errors" "*" "@types/node" "*" + "@types/send" "<1" -"@types/serve-static@^1.15.5": - version "1.15.9" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.9.tgz#f9b08ab7dd8bbb076f06f5f983b683654fe0a025" - integrity sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA== +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== dependencies: "@types/http-errors" "*" "@types/node" "*" - "@types/send" "<1" "@types/sockjs@^0.3.36": version "0.3.36" @@ -737,7 +849,7 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -755,11 +867,6 @@ acorn@^8.15.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -acorn@^8.8.2: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== - ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -767,27 +874,17 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" -ajv-keywords@^5.0.0, ajv-keywords@^5.1.0: +ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: fast-deep-equal "^3.1.3" -ajv@^8.0.0, ajv@^8.8.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== +ajv@^8.0.0, ajv@^8.9.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" @@ -854,10 +951,10 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== -baseline-browser-mapping@^2.8.9: - version "2.8.14" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.14.tgz#b73b0ae23efcb967e30b381c09a1a001777ec927" - integrity sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ== +baseline-browser-mapping@^2.9.0: + version "2.9.19" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" + integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== batch@0.6.1: version "0.6.1" @@ -865,45 +962,27 @@ batch@0.6.1: integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -body-parser@^1.19.0: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@^1.19.0, body-parser@~1.20.3: + version "1.20.4" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" + integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== dependencies: - bytes "3.1.2" + bytes "~3.1.2" content-type "~1.0.5" debug "2.6.9" depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.2" + destroy "~1.2.0" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + on-finished "~2.4.1" + qs "~6.14.0" + raw-body "~2.5.3" type-is "~1.6.18" - unpipe "1.0.0" + unpipe "~1.0.0" bonjour-service@^1.2.1: version "1.3.0" @@ -914,26 +993,26 @@ bonjour-service@^1.2.1: multicast-dns "^7.2.5" brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.2, braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-stdout@^1.3.1: version "1.3.1" @@ -941,15 +1020,15 @@ browser-stdout@^1.3.1: integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== browserslist@^4.24.0: - version "4.26.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56" - integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w== + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - baseline-browser-mapping "^2.8.9" - caniuse-lite "^1.0.30001746" - electron-to-chromium "^1.5.227" - node-releases "^2.0.21" - update-browserslist-db "^1.1.3" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" buffer-from@^1.0.0: version "1.1.2" @@ -963,12 +1042,7 @@ bundle-name@^4.1.0: dependencies: run-applescript "^7.0.0" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: +bytes@3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -981,15 +1055,7 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -call-bound@^1.0.2: +call-bound@^1.0.2, call-bound@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -1002,10 +1068,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001746: - version "1.0.30001749" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz#21a43b923577932097fe32bcaabb6da7f4677632" - integrity sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q== +caniuse-lite@^1.0.30001759: + version "1.0.30001770" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz#4dc47d3b263a50fbb243448034921e0a88591a84" + integrity sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw== chalk@^4.1.0: version "4.1.2" @@ -1015,22 +1081,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@^3.5.1: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^3.6.0: +chokidar@^3.5.1, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -1053,9 +1104,9 @@ chokidar@^4.0.1: readdirp "^4.0.1" chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== cliui@^7.0.2: version "7.0.4" @@ -1097,9 +1148,9 @@ color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colorette@^2.0.10, colorette@^2.0.14: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== commander@^12.1.0: version "12.1.0" @@ -1111,7 +1162,7 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -compressible@~2.0.16: +compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== @@ -1119,16 +1170,16 @@ compressible@~2.0.16: mime-db ">= 1.43.0 < 2" compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + version "1.8.1" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" + integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" + negotiator "~0.6.4" + on-headers "~1.1.0" + safe-buffer "5.2.1" vary "~1.1.2" concat-map@0.0.1: @@ -1151,7 +1202,7 @@ connect@^3.7.0: parseurl "~1.3.3" utils-merge "1.0.1" -content-disposition@0.5.4: +content-disposition@~0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== @@ -1163,17 +1214,12 @@ content-type@~1.0.4, content-type@~1.0.5: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== +cookie-signature@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== -cookie@~0.7.2: +cookie@~0.7.1, cookie@~0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== @@ -1184,23 +1230,14 @@ core-util-is@~1.0.0: integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cors@~2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + version "2.8.6" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" + integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== dependencies: object-assign "^4" vary "^1" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.6: +cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1226,17 +1263,10 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^4.3.5: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== +debug@^4.1.0, debug@^4.3.4, debug@^4.3.5, debug@~4.4.1: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" @@ -1251,14 +1281,14 @@ decode-uri-component@^0.2.0: integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.1.tgz#f7a7ccb8f5104bf8e0f71ba3b1ccfa5eafdb21e8" + integrity sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + version "5.5.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.5.0.tgz#2792e886f2422894545947cc80e1a444496c5976" + integrity sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== dependencies: bundle-name "^4.1.0" default-browser-id "^5.0.0" @@ -1268,7 +1298,7 @@ define-lazy-prop@^3.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -depd@2.0.0: +depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -1278,7 +1308,7 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -destroy@1.2.0: +destroy@1.2.0, destroy@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -1299,9 +1329,9 @@ diff@^7.0.0: integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== dns-packet@^5.2.2: - version "5.5.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.5.0.tgz#f59cbf3396c130957c56a6ad5fd3959ccdc30065" - integrity sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA== + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== dependencies: "@leichtgewicht/ip-codec" "^2.0.1" @@ -1334,10 +1364,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.5.227: - version "1.5.233" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz#05db98476cee317527d6c48934571e13ad6b6f58" - integrity sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg== +electron-to-chromium@^1.5.263: + version "1.5.286" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" + integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== emoji-regex@^8.0.0: version "8.0.0" @@ -1365,9 +1395,9 @@ engine.io-parser@~5.2.1: integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== engine.io@~6.6.0: - version "6.6.4" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.4.tgz#0a89a3e6b6c1d4b0c2a2a637495e7c149ec8d8ee" - integrity sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g== + version "6.6.5" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.5.tgz#a009522f5d5628109781b46722014349859269d2" + integrity sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A== dependencies: "@types/cors" "^2.8.12" "@types/node" ">=10.0.0" @@ -1375,27 +1405,32 @@ engine.io@~6.6.0: base64id "2.0.0" cookie "~0.7.2" cors "~2.8.5" - debug "~4.3.1" + debug "~4.4.1" engine.io-parser "~5.2.1" - ws "~8.17.1" + ws "~8.18.3" enhanced-resolve@^5.17.3: - version "5.18.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz#9b5f4c5c076b8787c78fe540392ce76a88855b44" - integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== + version "5.19.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz#6687446a15e969eaa63c2fa2694510e17ae6d97c" + integrity sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg== dependencies: graceful-fs "^4.2.4" - tapable "^2.2.0" + tapable "^2.3.0" ent@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== + version "2.2.2" + resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.2.tgz#22a5ed2fd7ce0cbcff1d1474cf4909a44bdb6e85" + integrity sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + punycode "^1.4.1" + safe-regex-test "^1.1.0" envinfo@^7.14.0: - version "7.17.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.17.0.tgz#4a9cb6fe0c91e27b5be5fcca61f681894f88db14" - integrity sha512-GpfViocsFM7viwClFgxK26OtjMlKN67GCR5v6ASFkotxtpBWd9d+vNy+AH7F2E1TUkMDZ8P/dDPZX71/NG8xnQ== + version "7.21.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.21.0.tgz#04a251be79f92548541f37d13c8b6f22940c3bae" + integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== es-define-property@^1.0.1: version "1.0.1" @@ -1408,9 +1443,9 @@ es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-module-lexer@^1.2.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" - integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" @@ -1419,44 +1454,39 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" -esbuild@^0.25.0: - version "0.25.10" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.10.tgz#37f5aa5cd14500f141be121c01b096ca83ac34a9" - integrity sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ== +esbuild@^0.27.0: + version "0.27.3" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.3.tgz#5859ca8e70a3af956b26895ce4954d7e73bd27a8" + integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== optionalDependencies: - "@esbuild/aix-ppc64" "0.25.10" - "@esbuild/android-arm" "0.25.10" - "@esbuild/android-arm64" "0.25.10" - "@esbuild/android-x64" "0.25.10" - "@esbuild/darwin-arm64" "0.25.10" - "@esbuild/darwin-x64" "0.25.10" - "@esbuild/freebsd-arm64" "0.25.10" - "@esbuild/freebsd-x64" "0.25.10" - "@esbuild/linux-arm" "0.25.10" - "@esbuild/linux-arm64" "0.25.10" - "@esbuild/linux-ia32" "0.25.10" - "@esbuild/linux-loong64" "0.25.10" - "@esbuild/linux-mips64el" "0.25.10" - "@esbuild/linux-ppc64" "0.25.10" - "@esbuild/linux-riscv64" "0.25.10" - "@esbuild/linux-s390x" "0.25.10" - "@esbuild/linux-x64" "0.25.10" - "@esbuild/netbsd-arm64" "0.25.10" - "@esbuild/netbsd-x64" "0.25.10" - "@esbuild/openbsd-arm64" "0.25.10" - "@esbuild/openbsd-x64" "0.25.10" - "@esbuild/openharmony-arm64" "0.25.10" - "@esbuild/sunos-x64" "0.25.10" - "@esbuild/win32-arm64" "0.25.10" - "@esbuild/win32-ia32" "0.25.10" - "@esbuild/win32-x64" "0.25.10" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escalade@^3.2.0: + "@esbuild/aix-ppc64" "0.27.3" + "@esbuild/android-arm" "0.27.3" + "@esbuild/android-arm64" "0.27.3" + "@esbuild/android-x64" "0.27.3" + "@esbuild/darwin-arm64" "0.27.3" + "@esbuild/darwin-x64" "0.27.3" + "@esbuild/freebsd-arm64" "0.27.3" + "@esbuild/freebsd-x64" "0.27.3" + "@esbuild/linux-arm" "0.27.3" + "@esbuild/linux-arm64" "0.27.3" + "@esbuild/linux-ia32" "0.27.3" + "@esbuild/linux-loong64" "0.27.3" + "@esbuild/linux-mips64el" "0.27.3" + "@esbuild/linux-ppc64" "0.27.3" + "@esbuild/linux-riscv64" "0.27.3" + "@esbuild/linux-s390x" "0.27.3" + "@esbuild/linux-x64" "0.27.3" + "@esbuild/netbsd-arm64" "0.27.3" + "@esbuild/netbsd-x64" "0.27.3" + "@esbuild/openbsd-arm64" "0.27.3" + "@esbuild/openbsd-x64" "0.27.3" + "@esbuild/openharmony-arm64" "0.27.3" + "@esbuild/sunos-x64" "0.27.3" + "@esbuild/win32-arm64" "0.27.3" + "@esbuild/win32-ia32" "0.27.3" + "@esbuild/win32-x64" "0.27.3" + +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -1517,38 +1547,38 @@ events@^3.2.0: integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== express@^4.21.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + version "4.22.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" + integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" + body-parser "~1.20.3" + content-disposition "~0.5.4" content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" + cookie "~0.7.1" + cookie-signature "~1.0.6" debug "2.6.9" depd "2.0.0" encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" + finalhandler "~1.3.1" + fresh "~0.5.2" + http-errors "~2.0.0" merge-descriptors "1.0.3" methods "~1.1.2" - on-finished "2.4.1" + on-finished "~2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.12" + path-to-regexp "~0.1.12" proxy-addr "~2.0.7" - qs "6.13.0" + qs "~6.14.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" + send "~0.19.0" + serve-static "~1.16.2" setprototypeof "1.2.0" - statuses "2.0.1" + statuses "~2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -1558,15 +1588,15 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-uri@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" - integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== fastest-levenshtein@^1.0.12: version "1.0.16" @@ -1585,10 +1615,10 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -1605,17 +1635,17 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== +finalhandler@~1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" + integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== dependencies: debug "2.6.9" encodeurl "~2.0.0" escape-html "~1.0.3" - on-finished "2.4.1" + on-finished "~2.4.1" parseurl "~1.3.3" - statuses "2.0.1" + statuses "~2.0.2" unpipe "~1.0.0" find-up@^4.0.0: @@ -1640,14 +1670,14 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== foreground-child@^3.1.0: version "3.3.1" @@ -1667,7 +1697,7 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2: +fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== @@ -1686,21 +1716,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -fsevents@~2.3.3: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -1711,15 +1731,6 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" @@ -1751,7 +1762,7 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-to-regex.js@^1.0.1: +glob-to-regex.js@^1.0.0, glob-to-regex.js@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz#2b323728271d133830850e32311f40766c5f6413" integrity sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ== @@ -1762,9 +1773,9 @@ glob-to-regexp@^0.4.1: integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob@^10.4.5: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -1805,22 +1816,17 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-symbols@^1.1.0: +has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - function-bind "^1.1.1" + has-symbols "^1.0.3" hasown@^2.0.2: version "2.0.2" @@ -1849,31 +1855,32 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== +http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== dependencies: - depd "2.0.0" + depd "~1.1.2" inherits "2.0.4" setprototypeof "1.2.0" - statuses "2.0.1" + statuses ">= 1.5.0 < 2" toidentifier "1.0.1" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== +http-errors@~2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + version "0.5.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== http-proxy-middleware@^2.0.9: version "2.0.9" @@ -1900,13 +1907,6 @@ hyperdyperid@^1.2.0: resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -1914,10 +1914,17 @@ iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@~0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -1930,16 +1937,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - interpret@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" @@ -1951,9 +1953,9 @@ ipaddr.js@1.9.1: integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" + integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== is-binary-path@~2.1.0: version "2.1.0" @@ -1962,7 +1964,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -2025,15 +2027,25 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + version "3.1.1" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.1.tgz#327897b26832a3eb117da6c27492d04ca132594f" + integrity sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw== dependencies: is-inside-container "^1.0.0" @@ -2076,9 +2088,9 @@ jest-worker@^27.4.5: supports-color "^8.0.0" js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" @@ -2172,17 +2184,17 @@ kotlin-web-helpers@2.1.0: format-util "^1.0.5" launch-editor@^2.6.1: - version "2.11.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.11.1.tgz#61a0b7314a42fd84a6cbb564573d9e9ffcf3d72b" - integrity sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg== + version "2.12.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.12.0.tgz#cc740f4e0263a6b62ead2485f9896e545321f817" + integrity sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg== dependencies: picocolors "^1.1.1" shell-quote "^1.8.3" loader-runner@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" - integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + version "4.3.1" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" + integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== locate-path@^5.0.0: version "5.0.0" @@ -2199,9 +2211,9 @@ locate-path@^6.0.0: p-locate "^5.0.0" lodash@^4.17.15, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== log-symbols@^4.1.0: version "4.1.0" @@ -2238,10 +2250,18 @@ media-typer@0.3.0: integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^4.43.1: - version "4.49.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.49.0.tgz#bc35069570d41a31c62e31f1a6ec6057a8ea82f0" - integrity sha512-L9uC9vGuc4xFybbdOpRLoOAOq1YEBBsocCs5NVW32DfU+CZWWIn3OVF+lB8Gp4ttBVSMazwrTrjv8ussX/e3VQ== - dependencies: + version "4.56.10" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.56.10.tgz#eaf2f6556db10f91f1e9ad9f1274fd988c646202" + integrity sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w== + dependencies: + "@jsonjoy.com/fs-core" "4.56.10" + "@jsonjoy.com/fs-fsa" "4.56.10" + "@jsonjoy.com/fs-node" "4.56.10" + "@jsonjoy.com/fs-node-builtins" "4.56.10" + "@jsonjoy.com/fs-node-to-fsa" "4.56.10" + "@jsonjoy.com/fs-node-utils" "4.56.10" + "@jsonjoy.com/fs-print" "4.56.10" + "@jsonjoy.com/fs-snapshot" "4.56.10" "@jsonjoy.com/json-pack" "^1.11.0" "@jsonjoy.com/util" "^1.9.0" glob-to-regex.js "^1.0.1" @@ -2265,24 +2285,24 @@ methods@~1.1.2: integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== micromatch@^4.0.2: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-db@^1.54.0: +"mime-db@>= 1.43.0 < 2", mime-db@^1.54.0: version "1.54.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2290,9 +2310,9 @@ mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: mime-db "1.52.0" mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== dependencies: mime-db "^1.54.0" @@ -2373,11 +2393,6 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -2401,20 +2416,25 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751" + integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg== -node-releases@^2.0.21: - version "2.0.23" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.23.tgz#2ecf3d7ba571ece05c67c77e5b7b1b6fb9e18cea" - integrity sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -2431,17 +2451,12 @@ object-inspect@^1.13.3: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@^2.4.1: +on-finished@^2.4.1, on-finished@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -2455,10 +2470,10 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== once@^1.3.0: version "1.4.0" @@ -2524,7 +2539,7 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -2557,7 +2572,7 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@0.1.12: +path-to-regexp@~0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== @@ -2606,29 +2621,22 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== qjobs@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== +qs@~6.14.0: + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== dependencies: - side-channel "^1.0.4" - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" + side-channel "^1.1.0" randombytes@^2.1.0: version "2.1.0" @@ -2642,15 +2650,15 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== +raw-body@~2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" + integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + unpipe "~1.0.0" readable-stream@^2.0.1: version "2.3.8" @@ -2721,11 +2729,11 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.20.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -2735,11 +2743,11 @@ retry@^0.13.1: integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -2755,34 +2763,37 @@ rollup-plugin-sourcemaps@^0.6.3: source-map-resolve "^0.6.0" rollup@^4.43.0: - version "4.52.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.4.tgz#71e64cce96a865fcbaa6bb62c6e82807f4e378a1" - integrity sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ== + version "4.57.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.57.1.tgz#947f70baca32db2b9c594267fe9150aa316e5a88" + integrity sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A== dependencies: "@types/estree" "1.0.8" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.52.4" - "@rollup/rollup-android-arm64" "4.52.4" - "@rollup/rollup-darwin-arm64" "4.52.4" - "@rollup/rollup-darwin-x64" "4.52.4" - "@rollup/rollup-freebsd-arm64" "4.52.4" - "@rollup/rollup-freebsd-x64" "4.52.4" - "@rollup/rollup-linux-arm-gnueabihf" "4.52.4" - "@rollup/rollup-linux-arm-musleabihf" "4.52.4" - "@rollup/rollup-linux-arm64-gnu" "4.52.4" - "@rollup/rollup-linux-arm64-musl" "4.52.4" - "@rollup/rollup-linux-loong64-gnu" "4.52.4" - "@rollup/rollup-linux-ppc64-gnu" "4.52.4" - "@rollup/rollup-linux-riscv64-gnu" "4.52.4" - "@rollup/rollup-linux-riscv64-musl" "4.52.4" - "@rollup/rollup-linux-s390x-gnu" "4.52.4" - "@rollup/rollup-linux-x64-gnu" "4.52.4" - "@rollup/rollup-linux-x64-musl" "4.52.4" - "@rollup/rollup-openharmony-arm64" "4.52.4" - "@rollup/rollup-win32-arm64-msvc" "4.52.4" - "@rollup/rollup-win32-ia32-msvc" "4.52.4" - "@rollup/rollup-win32-x64-gnu" "4.52.4" - "@rollup/rollup-win32-x64-msvc" "4.52.4" + "@rollup/rollup-android-arm-eabi" "4.57.1" + "@rollup/rollup-android-arm64" "4.57.1" + "@rollup/rollup-darwin-arm64" "4.57.1" + "@rollup/rollup-darwin-x64" "4.57.1" + "@rollup/rollup-freebsd-arm64" "4.57.1" + "@rollup/rollup-freebsd-x64" "4.57.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.57.1" + "@rollup/rollup-linux-arm-musleabihf" "4.57.1" + "@rollup/rollup-linux-arm64-gnu" "4.57.1" + "@rollup/rollup-linux-arm64-musl" "4.57.1" + "@rollup/rollup-linux-loong64-gnu" "4.57.1" + "@rollup/rollup-linux-loong64-musl" "4.57.1" + "@rollup/rollup-linux-ppc64-gnu" "4.57.1" + "@rollup/rollup-linux-ppc64-musl" "4.57.1" + "@rollup/rollup-linux-riscv64-gnu" "4.57.1" + "@rollup/rollup-linux-riscv64-musl" "4.57.1" + "@rollup/rollup-linux-s390x-gnu" "4.57.1" + "@rollup/rollup-linux-x64-gnu" "4.57.1" + "@rollup/rollup-linux-x64-musl" "4.57.1" + "@rollup/rollup-openbsd-x64" "4.57.1" + "@rollup/rollup-openharmony-arm64" "4.57.1" + "@rollup/rollup-win32-arm64-msvc" "4.57.1" + "@rollup/rollup-win32-ia32-msvc" "4.57.1" + "@rollup/rollup-win32-x64-gnu" "4.57.1" + "@rollup/rollup-win32-x64-msvc" "4.57.1" fsevents "~2.3.2" run-applescript@^7.0.0: @@ -2790,32 +2801,31 @@ run-applescript@^7.0.0: resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911" integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.8.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" - -schema-utils@^4.2.0, schema-utils@^4.3.2: +schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0, schema-utils@^4.3.2: version "4.3.3" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== @@ -2825,16 +2835,6 @@ schema-utils@^4.2.0, schema-utils@^4.3.2: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -schema-utils@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0" - integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -2848,24 +2848,24 @@ selfsigned@^2.4.1: "@types/node-forge" "^1.3.0" node-forge "^1" -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== +send@~0.19.0, send@~0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== dependencies: debug "2.6.9" depd "2.0.0" destroy "1.2.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" + fresh "~0.5.2" + http-errors "~2.0.1" mime "1.6.0" ms "2.1.3" - on-finished "2.4.1" + on-finished "~2.4.1" range-parser "~1.2.1" - statuses "2.0.1" + statuses "~2.0.2" serialize-javascript@^6.0.2: version "6.0.2" @@ -2875,34 +2875,29 @@ serialize-javascript@^6.0.2: randombytes "^2.1.0" serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + version "1.9.2" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.2.tgz#2988e3612106d78a5e4849ddff552ce7bd3d9bcb" + integrity sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ== dependencies: - accepts "~1.3.4" + accepts "~1.3.8" batch "0.6.1" debug "2.6.9" escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" + http-errors "~1.8.0" + mime-types "~2.1.35" + parseurl "~1.3.3" -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== +serve-static@~1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== dependencies: encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.19.0" + send "~0.19.1" -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: +setprototypeof@1.2.0, setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -2960,16 +2955,7 @@ side-channel-weakmap@^1.0.2: object-inspect "^1.13.3" side-channel-map "^1.0.1" -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -side-channel@^1.0.6: +side-channel@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== @@ -2986,29 +2972,30 @@ signal-exit@^4.0.1: integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== socket.io-adapter@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" - integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + version "2.5.6" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz#c697f609d36a676a46749782274607d8df52c1d8" + integrity sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ== dependencies: - ws "~8.11.0" + debug "~4.4.1" + ws "~8.18.3" socket.io-parser@~4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" - integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + version "4.2.5" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.5.tgz#3f41b8d369129a93268f2abecba94b5292850099" + integrity sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ== dependencies: "@socket.io/component-emitter" "~3.1.0" - debug "~4.3.1" + debug "~4.4.1" socket.io@^4.7.2: - version "4.8.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.1.tgz#fa0eaff965cc97fdf4245e8d4794618459f7558a" - integrity sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg== + version "4.8.3" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.3.tgz#ca6ba1431c69532e1e0a6f496deebeb601dbc4df" + integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A== dependencies: accepts "~1.3.4" base64id "~2.0.0" cors "~2.8.5" - debug "~4.3.2" + debug "~4.4.1" engine.io "~6.6.0" socket.io-adapter "~2.5.2" socket.io-parser "~4.2.4" @@ -3022,12 +3009,7 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-js@^1.2.1: +source-map-js@^1.0.2, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -3084,16 +3066,16 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2", statuses@~1.5.0: +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +statuses@~2.0.1, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + streamroller@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" @@ -3173,15 +3155,15 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tapable@^2.1.1, tapable@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== terser-webpack-plugin@^5.3.11: - version "5.3.14" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" - integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + version "5.3.16" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" + integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q== dependencies: "@jridgewell/trace-mapping" "^0.3.25" jest-worker "^27.4.5" @@ -3190,12 +3172,12 @@ terser-webpack-plugin@^5.3.11: terser "^5.31.1" terser@^5.31.1: - version "5.39.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a" - integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw== + version "5.46.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.0.tgz#1b81e560d584bbdd74a8ede87b4d9477b0ff9695" + integrity sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg== dependencies: "@jridgewell/source-map" "^0.3.3" - acorn "^8.8.2" + acorn "^8.15.0" commander "^2.20.0" source-map-support "~0.5.20" @@ -3218,11 +3200,9 @@ tinyglobby@^0.2.15: picomatch "^4.0.3" tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== to-regex-range@^5.0.1: version "5.0.1" @@ -3231,12 +3211,12 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: +toidentifier@1.0.1, toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tree-dump@^1.0.3: +tree-dump@^1.0.3, tree-dump@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4" integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== @@ -3255,35 +3235,33 @@ type-is@~1.6.18: mime-types "~2.1.24" ua-parser-js@^0.7.30: - version "0.7.35" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.35.tgz#8bda4827be4f0b1dda91699a29499575a1f1d307" - integrity sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g== + version "0.7.41" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.41.tgz#9f6dee58c389e8afababa62a4a2dc22edb69a452" + integrity sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: escalade "^3.2.0" picocolors "^1.1.1" -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -3305,11 +3283,11 @@ vary@^1, vary@~1.1.2: integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== vite@^7.1.7: - version "7.1.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.9.tgz#ba844410e5d0c0f2a4eaf17a52af60ebea322cbf" - integrity sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg== + version "7.3.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.3.1.tgz#7f6cfe8fb9074138605e822a75d9d30b814d6507" + integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA== dependencies: - esbuild "^0.25.0" + esbuild "^0.27.0" fdir "^6.5.0" picomatch "^4.0.3" postcss "^8.5.6" @@ -3324,9 +3302,9 @@ void-elements@^2.0.0: integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== watchpack@^2.4.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" - integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + version "2.5.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.1.tgz#dd38b601f669e0cbf567cb802e75cead82cde102" + integrity sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -3517,20 +3495,15 @@ wrappy@1: integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@^8.18.0: + version "8.19.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== + +ws@~8.18.3: version "8.18.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== -ws@~8.11.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== - -ws@~8.17.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== - wsl-utils@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/wsl-utils/-/wsl-utils-0.1.0.tgz#8783d4df671d4d50365be2ee4c71917a0557baab" diff --git a/settings.gradle.kts b/settings.gradle.kts index bca41059..115b9210 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,13 +34,15 @@ plugins { include("tiny-cli") include("tiny-doc") include("tiny-doc-annotations") -include("tiny-annotation-processors:tiny-api-to-asciidoc-generator") include("tiny-annotation-processors:tiny-asciidoctor-dsl") include("tiny-annotation-processors:tiny-lua-dsl") +include("tiny-annotation-processors:tiny-api-to-json-generator") include("tiny-annotation-processors:tiny-lua-stub-generator") include("tiny-engine") -include("tiny-sample") +include("tiny-samples:breakout") +include("tiny-samples:home") include("tiny-web-editor") +include("tiny-debugger") develocity { buildScan { diff --git a/tiny-annotation-processors/tiny-api-to-asciidoc-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToAsciidocKspProcessor.kt b/tiny-annotation-processors/tiny-api-to-asciidoc-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToAsciidocKspProcessor.kt index ec17c41b..4502ec17 100644 --- a/tiny-annotation-processors/tiny-api-to-asciidoc-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToAsciidocKspProcessor.kt +++ b/tiny-annotation-processors/tiny-api-to-asciidoc-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToAsciidocKspProcessor.kt @@ -46,7 +46,7 @@ class TinyToAsciidocKspProcessor( title = "Tiny API" libs.sortedBy { it.name } .forEach { lib -> - section(lib.name.ifBlank { "std" }, lib.description) { + section(lib.name.ifBlank { "std" }, lib.description, lib.icon.ifBlank { null }) { lib.variables .sortedBy { it.name } .filterNot { it.hidden } diff --git a/tiny-annotation-processors/tiny-api-to-json-generator/build.gradle.kts b/tiny-annotation-processors/tiny-api-to-json-generator/build.gradle.kts new file mode 100644 index 00000000..45d68ade --- /dev/null +++ b/tiny-annotation-processors/tiny-api-to-json-generator/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + alias(libs.plugins.minigdx.mpp) +} + +dependencies { + jvmMainImplementation(libs.ksp.symbol.processing.api) + jvmMainImplementation(project(":tiny-doc-annotations")) + jvmMainImplementation(project(":tiny-annotation-processors:tiny-asciidoctor-dsl")) +} diff --git a/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/JsonDsl.kt b/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/JsonDsl.kt new file mode 100644 index 00000000..cb152339 --- /dev/null +++ b/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/JsonDsl.kt @@ -0,0 +1,107 @@ +package com.github.minigdx.tiny.doc + +@DslMarker +annotation class JsonDslMarker + +private sealed interface JsonEntry { + fun render(indent: Int): String +} + +private class JsonValueEntry(val name: String, val value: String?) : JsonEntry { + override fun render(indent: Int): String { + val pad = " ".repeat(indent) + val v = if (value != null) "\"${escapeJson(value)}\"" else "null" + return "$pad\"$name\": $v" + } +} + +private class JsonObjectEntry(val name: String, val obj: JsonObject) : JsonEntry { + override fun render(indent: Int): String { + val pad = " ".repeat(indent) + return "$pad\"$name\": ${obj.generate(indent)}" + } +} + +private class JsonArrayEntry(val name: String, val arr: JsonArray) : JsonEntry { + override fun render(indent: Int): String { + val pad = " ".repeat(indent) + return "$pad\"$name\": ${arr.generate(indent)}" + } +} + +private fun escapeJson(value: String): String { + return value + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") +} + +@JsonDslMarker +class JsonObject { + private val entries = mutableListOf() + + fun value( + name: String, + value: String?, + ) { + entries.add(JsonValueEntry(name, value)) + } + + fun obj( + name: String, + block: JsonObject.() -> Unit, + ) { + val child = JsonObject() + child.block() + entries.add(JsonObjectEntry(name, child)) + } + + fun array( + name: String, + block: JsonArray.() -> Unit, + ) { + val child = JsonArray() + child.block() + entries.add(JsonArrayEntry(name, child)) + } + + fun generate(indent: Int = 0): String { + val pad = " ".repeat(indent) + val innerPad = " ".repeat(indent + 2) + if (entries.isEmpty()) { + return "{\n$pad}" + } + val content = entries.joinToString(",\n") { it.render(indent + 2) } + return "{\n$content\n$pad}" + } +} + +@JsonDslMarker +class JsonArray { + private val elements = mutableListOf() + + fun obj(block: JsonObject.() -> Unit) { + val child = JsonObject() + child.block() + elements.add(child) + } + + fun generate(indent: Int = 0): String { + val pad = " ".repeat(indent) + val innerIndent = indent + 2 + if (elements.isEmpty()) { + return "[\n$pad]" + } + val innerPad = " ".repeat(innerIndent) + val content = elements.joinToString(",\n") { "$innerPad${it.generate(innerIndent)}" } + return "[\n$content\n$pad]" + } +} + +fun json(block: JsonObject.() -> Unit): JsonObject { + val root = JsonObject() + root.block() + return root +} diff --git a/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToJsonKspProcessor.kt b/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToJsonKspProcessor.kt new file mode 100644 index 00000000..72edd843 --- /dev/null +++ b/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToJsonKspProcessor.kt @@ -0,0 +1,98 @@ +package com.github.minigdx.tiny.doc + +import com.github.mingdx.tiny.doc.TinyLib +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration + +class TinyToJsonKspProcessor( + val env: SymbolProcessorEnvironment, +) : SymbolProcessor { + override fun process(resolver: Resolver): List { + // Skip last KSP round. Everything should be done in one round + resolver.getNewFiles().firstOrNull() ?: return emptyList() + + val symbolsWithAnnotation = resolver.getSymbolsWithAnnotation(TinyLib::class.qualifiedName!!) + + val sourceFiles = symbolsWithAnnotation + .filterIsInstance() + .mapNotNull { it.containingFile } + .toList() + .toTypedArray() + + val file = env.codeGenerator.createNewFile( + Dependencies(true, *sourceFiles), + "/", + "tiny-api", + "json", + ) + + val libs = symbolsWithAnnotation.map { s -> + s.accept(TinyLibVisitor(), TinyLibDescriptor()) + } + + val json = generateJson(libs) + file.write(json.toByteArray(charset = Charsets.UTF_8)) + + return emptyList() + } + + private fun generateJson(libs: Sequence): String { + val sortedLibs = libs.sortedBy { it.name }.toList() + return json { + array("libraries") { + sortedLibs.forEach { lib -> + obj { + value("name", lib.name.ifBlank { "std" }) + value("description", lib.description) + value("icon", lib.icon) + array("variables") { + lib.variables.filterNot { it.hidden }.sortedBy { it.name }.forEach { variable -> + obj { + value("name", variable.name) + value("description", variable.description) + } + } + } + array("functions") { + lib.functions.sortedBy { it.name }.forEach { func -> + obj { + value("name", func.name) + value("description", func.description) + value("example", func.example) + array("calls") { + func.calls.forEach { call -> + obj { + value("description", call.description) + value("returnType", call.returnType) + array("args") { + call.args.forEach { arg -> + obj { + value("name", arg.name) + value("type", arg.type) + value("description", arg.description) + } + } + } + } + } + } + } + } + } + } + } + } + }.generate() + "\n" + } +} + +class TinyToJsonKspProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return TinyToJsonKspProcessor(environment) + } +} diff --git a/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000..5c42eba2 --- /dev/null +++ b/tiny-annotation-processors/tiny-api-to-json-generator/src/jvmMain/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.github.minigdx.tiny.doc.TinyToJsonKspProcessorProvider diff --git a/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/AsciidocDsl.kt b/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/AsciidocDsl.kt index e6e8ac13..8d0da009 100644 --- a/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/AsciidocDsl.kt +++ b/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/AsciidocDsl.kt @@ -15,9 +15,10 @@ class AsciidocDocument { fun section( title: String? = null, description: String? = null, + icon: String? = null, block: AsciidocSection.() -> Unit, ) { - val section = AsciidocSection(title, description) + val section = AsciidocSection(title, description, icon) section.block() sections.add(section) } @@ -25,7 +26,7 @@ class AsciidocDocument { fun generate(): String { return buildString { if (title != null) { - appendLine("== $title") + appendLine("= $title") appendLine() } if (author != null) { @@ -40,7 +41,7 @@ class AsciidocDocument { } @AsciidocDslMarker -class AsciidocSection(val title: String?, val description: String?) { +class AsciidocSection(val title: String?, val description: String?, val icon: String? = null) { val childs = mutableListOf() fun lib( @@ -54,8 +55,15 @@ class AsciidocSection(val title: String?, val description: String?) { fun generate(): String { return buildString { + if (!icon.isNullOrBlank()) { + appendLine("++++") + appendLine("""
""") + appendLine("++++") + appendLine() + } + if (title != null) { - appendLine("=== $title") + appendLine("== $title") appendLine() } @@ -132,7 +140,7 @@ class AsciidocLibSection(val title: String?) { fun generate(): String { return buildString { if (title != null) { - appendLine("==== $title") + appendLine("=== $title") appendLine() } paragraphs.forEach { diff --git a/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyDescriptor.kt b/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyDescriptor.kt index cbc11a58..665c1a83 100644 --- a/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyDescriptor.kt +++ b/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyDescriptor.kt @@ -24,6 +24,7 @@ data class TinyFunctionDescriptor( class TinyLibDescriptor( var name: String = "", var description: String = "", + var icon: String = "", var functions: List = emptyList(), var variables: List = emptyList(), ) diff --git a/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyVisitor.kt b/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyVisitor.kt index c4382232..69244c4e 100644 --- a/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyVisitor.kt +++ b/tiny-annotation-processors/tiny-asciidoctor-dsl/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyVisitor.kt @@ -107,12 +107,17 @@ class TinyFunctionVisitor : documentation } - call.args += multiArg.names.map { n -> TinyArgDescriptor(n) } - .zip(docs) { ano, doc -> - ano.apply { - ano.description = doc - } + val types = multiArg.types + call.args += multiArg.names.mapIndexed { i, n -> + TinyArgDescriptor( + n, + type = types.getOrNull(i)?.type ?: "any", + ) + }.zip(docs) { ano, doc -> + ano.apply { + ano.description = doc } + } } else { val args = p.getAnnotationsByType(TinyArg::class).firstOrNull() val arg = args?.name ?: p.name?.asString() ?: "" @@ -159,6 +164,7 @@ class TinyLibVisitor : KSDefaultVisitor() data.name = lib.name data.description = lib.description + data.icon = lib.icon return super.visitAnnotated(annotated, data) } diff --git a/tiny-annotation-processors/tiny-lua-stub-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToLuaStubKspProcessor.kt b/tiny-annotation-processors/tiny-lua-stub-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToLuaStubKspProcessor.kt index be09c86e..fee4e2a9 100644 --- a/tiny-annotation-processors/tiny-lua-stub-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToLuaStubKspProcessor.kt +++ b/tiny-annotation-processors/tiny-lua-stub-generator/src/jvmMain/kotlin/com/github/minigdx/tiny/doc/TinyToLuaStubKspProcessor.kt @@ -42,8 +42,30 @@ class TinyToLuaStubKspProcessor( -- DO NOT EDIT // DO NOT EDIT // DO NOT EDIT // DO NOT EDIT // DO NOT EDIT -- Tiny stub lua file generated automatically -- The file is used only to help Lua editors with autocomplete - -- + -- -- An error, an issue? Please consult https://github.com/minigdx/tiny + -- + -- COLOR SYSTEM: + -- Color 0 = TRANSPARENT. Never use 0 if you want something visible. + -- Colors 1..N are defined in the _tiny.json configuration file ("colors" array). + -- The first hex color in the array is index 1, the second is index 2, etc. + -- Typical palette example (PICO-8 style, 16 colors): + -- 1 = black, 2 = dark blue, 3 = dark purple, 4 = dark green, + -- 5 = brown, 6 = dark grey, 7 = light grey, 8 = white, + -- 9 = red, 10 = orange, 11 = yellow, 12 = green, + -- 13 = blue, 14 = lavender, 15 = pink, 16 = peach + -- Check _tiny.json "colors" array for the actual palette of the current game. + -- + -- DEFAULT COLORS: + -- gfx.cls() without arguments clears to the closest color to black (#000000). + -- print() without a color argument uses the closest color to white (#FFFFFF). + -- To ensure visibility: use a color index DIFFERENT from the cls() color. + -- Common safe pattern: gfx.cls(1) then draw with colors >= 2. + -- + -- COMMON MISTAKES: + -- WRONG: shape.circlef(10, 10, 10, 0) -- color 0 is transparent, nothing visible! + -- WRONG: gfx.cls(1) then shape.circlef(10, 10, 10, 1) -- same color as background! + -- RIGHT: gfx.cls(1) then shape.circlef(10, 10, 10, 8) -- visible: white circle on black """.trimIndent(), ) { libs.forEach { diff --git a/tiny-cli/build.gradle.kts b/tiny-cli/build.gradle.kts index ed516420..9e415f19 100644 --- a/tiny-cli/build.gradle.kts +++ b/tiny-cli/build.gradle.kts @@ -19,19 +19,15 @@ dependencies { implementation(libs.kotlin.serialization.json) implementation(libs.clikt) - // Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/jna/Platform - // https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform - implementation(libs.jna) - implementation(libs.rsyntax) - implementation(project(":tiny-doc-annotations")) implementation(project(":tiny-engine", "jvmRuntimeElements"))!! .because("Depends on the JVM Jar containing commons resources in the JAR.") + implementation(project(":tiny-debugger", "jvmRuntimeElements"))!! + .because("Depends on the debugger protocol classes and web UI.") implementation(libs.kgl.lwjgl) implementation(libs.bundles.jvm.ktor.server) - implementation(libs.bundles.jvm.ktor.client) add( externalDependencies.name, @@ -45,6 +41,19 @@ dependencies { "Embed the JS engine in the CLI " + "so it can be included when the game is exported.", ) + + add( + externalDependencies.name, + project( + mapOf( + "path" to ":tiny-debugger", + "configuration" to "tinyDebugger", + ), + ), + )?.because( + "Embed the web debugger in the CLI " + + "so it can be served by the run command debug server.", + ) } application { @@ -73,4 +82,11 @@ project.tasks.withType(JavaExec::class.java).configureEach { val runtimeClasspath by configurations.existing classpath(jar, runtimeClasspath, externalDependencies) + + if (project.hasProperty("tiny.workDir")) { + workingDir = rootProject.projectDir.resolve(project.property("tiny.workDir") as String) + } + if (System.getProperty("os.name").contains("Mac", ignoreCase = true)) { + jvmArgs("-XstartOnFirstThread") + } } diff --git a/tiny-cli/mockup-sfx2.aseprite b/tiny-cli/mockup-sfx2.aseprite index 913749a3..1ee09d43 100644 Binary files a/tiny-cli/mockup-sfx2.aseprite and b/tiny-cli/mockup-sfx2.aseprite differ diff --git a/tiny-cli/sfx-editor.aseprite b/tiny-cli/sfx-editor.aseprite new file mode 100644 index 00000000..a7e565f7 Binary files /dev/null and b/tiny-cli/sfx-editor.aseprite differ diff --git a/tiny-cli/sfx-spritesheet.aseprite b/tiny-cli/sfx-spritesheet.aseprite index 79b8e667..5155efe0 100644 Binary files a/tiny-cli/sfx-spritesheet.aseprite and b/tiny-cli/sfx-spritesheet.aseprite differ diff --git a/tiny-cli/sprite-sheet.aseprite b/tiny-cli/sprite-sheet.aseprite new file mode 100644 index 00000000..0a3cdcfc Binary files /dev/null and b/tiny-cli/sprite-sheet.aseprite differ diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/AddCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/AddCommand.kt index db4248c4..bc764495 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/AddCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/AddCommand.kt @@ -1,12 +1,17 @@ package com.github.minigdx.tiny.cli.command +import com.github.ajalt.clikt.core.Abort import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.optionalValue import com.github.ajalt.clikt.parameters.types.file +import com.github.minigdx.tiny.cli.command.utils.FontAnalyzer +import com.github.minigdx.tiny.cli.command.utils.TtfConverter import com.github.minigdx.tiny.cli.config.GameParameters import com.github.minigdx.tiny.cli.exception.MissingTinyConfigurationException import java.io.File @@ -16,6 +21,18 @@ class AddCommand : CliktCommand(name = "add") { .file(mustExist = true, canBeDir = true, canBeFile = false) .default(File(".")) + val fontName by option("--font", help = "Mark .png resources as fonts instead of spritesheets. Optionally specify the font name.") + .optionalValue("") + + val size by option("--size", help = "Character size in pixels (e.g., '8x12' or '8' for square). Auto-detected if omitted.") + + val offset by option("--offset", help = "Pixel offset where the character grid begins (e.g., '8,12'). Auto-detected if omitted.") + + val boot by option("--boot", help = "Set a .lua script as the boot script instead of adding it to the scripts list.") + .flag(default = false) + + val chars by option("--chars", help = "Characters in the font, in reading order (left-to-right, top-to-bottom). Requires --font.") + val resources by argument(help = "The resource to add to the game. The kind of resource will be deducted from the file extension.") .multiple(required = true) @@ -26,16 +43,50 @@ class AddCommand : CliktCommand(name = "add") { if (!tiny.exists()) { throw MissingTinyConfigurationException(tiny) } + + val hasTtf = resources.any { it.endsWith("ttf") || it.endsWith("otf") } + if (fontName != null && chars == null && !hasTtf) { + echo("❌ --font requires --chars option (or use a .ttf file for auto-detection).") + throw Abort() + } + + if (fontName == null && (size != null || chars != null || offset != null)) { + echo("⚠️ --size, --chars, and --offset are only used with --font. They will be ignored.") + } + + if (boot && resources.any { !it.endsWith("lua") }) { + echo("❌ --boot can only be used with .lua scripts.") + throw Abort() + } + + if (boot && resources.size > 1) { + echo("❌ --boot can only be used with a single script.") + throw Abort() + } + // Open the _tiny.json var gameParameters = GameParameters.read(tiny) // regarding the input, add it into the right resource resources.forEach { r -> val type = - if (r.endsWith("png")) { + if ((r.endsWith("ttf") || r.endsWith("otf")) && fontName != null) { + addTtfFont(r, gameParameters).let { gameParameters = it } + "font (from TTF)" + } else if ((r.endsWith("ttf") || r.endsWith("otf")) && fontName == null) { + echo("⚠️ TTF files require --font option. Use: tiny-cli add --font $r") + null + } else if (r.endsWith("png") && fontName != null) { + addFont(r, gameParameters).let { gameParameters = it } + "font" + } else if (r.endsWith("png")) { // Add spritesheet gameParameters = gameParameters.addSpritesheet(r) "spritesheet" + } else if (r.endsWith("lua") && boot) { + // Set as boot script + gameParameters = gameParameters.setBootScript(r) + "boot script" } else if (r.endsWith("lua")) { // Add script gameParameters = gameParameters.addScript(r) @@ -64,4 +115,95 @@ class AddCommand : CliktCommand(name = "add") { // Save the updated _tiny.json gameParameters.write(tiny) } + + private fun echoFontWarnings() { + echo(" ⚠️ Tiny renders fonts as monospace: every character occupies the same cell width.") + echo(" Proportional fonts will have uneven spacing. Prefer pixel/monospace fonts.") + echo(" ⚠️ Semi-transparent pixels (anti-aliasing) are not supported.") + echo(" They will be rendered as fully opaque, causing visual artifacts on glyph edges.") + } + + private fun addTtfFont( + resource: String, + params: GameParameters, + ): GameParameters { + val ttfFile = gameDirectory.resolve(resource) + val fontName = this.fontName!!.ifEmpty { FontAnalyzer.deriveFontName(resource) } + val pngName = "$fontName.png" + val pngFile = gameDirectory.resolve(pngName) + + val effectiveChars = chars ?: TtfConverter.DEFAULT_CHARS + val targetHeight = size?.let { FontAnalyzer.parseSize(it).second } + + echo(" 🔤 Converting TTF to PNG spritesheet...") + + val result = TtfConverter.convert(ttfFile, pngFile, effectiveChars, targetHeight) + + echo(" 📐 Character size: ${result.cellWidth}x${result.cellHeight}") + echo(" 📏 Grid: ${result.cols} chars/row, ${result.rows} rows, ${effectiveChars.length} total chars") + echoFontWarnings() + + val rows = FontAnalyzer.splitCharsIntoRows( + result.cols * result.cellWidth, + result.cellWidth, + effectiveChars, + ) + + val font = FontAnalyzer.buildFontConfig( + fontName = fontName, + spritesheet = pngName, + charWidth = result.cellWidth, + charHeight = result.cellHeight, + characters = rows, + spaceWidth = result.spaceWidth, + ) + + return params.addFont(font) + } + + private fun addFont( + resource: String, + params: GameParameters, + ): GameParameters { + val image = FontAnalyzer.readImage(gameDirectory, resource) + val parsedSize = size?.let { FontAnalyzer.parseSize(it) } + val parsedOffset = offset?.let { FontAnalyzer.parseOffset(it) } + + val result = FontAnalyzer.autoDetect(image, parsedSize, parsedOffset, chars!!.length) + if (result == null) { + echo("❌ Could not auto-detect character size. Please provide --size explicitly.") + throw Abort() + } + + if (result.sizeDetected) { + echo(" 🔍 Auto-detected character size: ${result.cellWidth}x${result.cellHeight}") + } + if (result.offsetDetected) { + echo(" 🔍 Auto-detected grid offset: ${result.offsetX},${result.offsetY}") + } + + val fontName = this.fontName!!.ifEmpty { FontAnalyzer.deriveFontName(resource) } + val effectiveWidth = image.width - result.offsetX + val rows = FontAnalyzer.splitCharsIntoRows(effectiveWidth, result.cellWidth, chars!!) + val charsPerRow = effectiveWidth / result.cellWidth + + val font = FontAnalyzer.buildFontConfig( + fontName = fontName, + spritesheet = resource, + charWidth = result.cellWidth, + charHeight = result.cellHeight, + characters = rows, + offsetX = result.offsetX, + offsetY = result.offsetY, + ) + + echo(" 📐 Character size: ${result.cellWidth}x${result.cellHeight}") + echo(" 📏 Grid: $charsPerRow chars/row, ${rows.size} rows, ${chars!!.length} total chars") + if (result.offsetX != 0 || result.offsetY != 0) { + echo(" 📍 Offset: ${result.offsetX},${result.offsetY}") + } + echoFontWarnings() + + return params.addFont(font) + } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/CreateCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/CreateCommand.kt index 9ddb7b41..84534963 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/CreateCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/CreateCommand.kt @@ -13,6 +13,7 @@ import com.github.ajalt.mordant.rendering.TextStyles import com.github.minigdx.tiny.cli.GamePalette import com.github.minigdx.tiny.cli.command.utils.ColorUtils import com.github.minigdx.tiny.cli.command.utils.ColorUtils.brightness +import com.github.minigdx.tiny.cli.command.utils.IconImageGenerator import com.github.minigdx.tiny.cli.command.utils.PaletteImageGenerator import com.github.minigdx.tiny.cli.config.GameParameters import com.github.minigdx.tiny.cli.config.GameParametersV1 @@ -40,6 +41,38 @@ function _draw() end """ +@Language("Lua") +private const val DEFAULT_BOOT_SCRIPT = """ +local ready = false + +function _init(screen_width, screen_height) + -- prepare your boot script +end + +function _update() + -- clear the screen and exist the boot script when all resources are loaded + if (ready) then + gfx.cls("#000000") + tiny.exit(0) -- start the first script in the game script stack + end +end + +function _draw() + gfx.cls("#000000") + -- draw your boot animation +end + +--[[ +_resources is a magic method called when all the resources are loaded +Before this call, only primitives can be used. Sprites are not loaded yet, as sound, levels, ... +After, all resources are available +]]-- +function _resources() + -- all game resources are loaded + ready = true +end +""" + class CreateCommand : CliktCommand(name = "create") { val gameDirectory by argument(help = "The directory containing all game information") .file(mustExist = false, canBeDir = true, canBeFile = false) @@ -90,6 +123,12 @@ ${ .prompt("\uD83D\uDDB1\uFE0F Hide system cursor mouse? (yes or no)", default = "No") .validate { it.lowercase() == "yes" || it.lowercase() == "no" } + private val bootScript by option(help = "🚀 Custom boot script to use instead of the default boot.lua") + .prompt("\uD83D\uDE80 Custom boot script (leave empty for default boot.lua)", default = "") + .validate { + require(it.isEmpty() || it.endsWith(".lua")) { "Invalid boot script extension: $it. Must be a .lua file." } + } + override fun help(context: Context) = "Create a new game with the help of a wizard 🧙." override fun run() { @@ -98,6 +137,16 @@ ${ echo("➡\uFE0F Game Resolution: $spriteSize") echo("➡\uFE0F Sprite Sheet Filenames: ${spritesheets.ifBlank { "No spritesheet added!" }}") echo("➡\uFE0F Color palette: ${GamePalette.ALL[palette - 1].name}") + if (bootScript.isNotBlank()) { + echo("➡\uFE0F Boot script: $bootScript") + } + + val sortedColors = GamePalette.ALL[palette - 1].colors.sortedBy { brightness(it) } + + if (!gameDirectory.exists()) gameDirectory.mkdirs() + + // Generate game icon + val iconFile = IconImageGenerator.generateIcon(gameDirectory, sortedColors, gameName) val configuration = GameParametersV1( name = gameName, @@ -105,14 +154,14 @@ ${ resolution = gameResolution.toSize(), sprites = spriteSize.toSize(), zoom = zoom, - colors = GamePalette.ALL[palette - 1].colors.sortedBy { brightness(it) }, + colors = sortedColors, scripts = listOf(gameScript), sound = "default-sound.sfx", hideMouseCursor = hideMouseCursor == "yes".lowercase(), + bootScript = bootScript.ifBlank { null }, + icon = iconFile.name, ) as GameParameters - if (!gameDirectory.exists()) gameDirectory.mkdirs() - val configurationFile = gameDirectory.resolve("_tiny.json") configuration.write(configurationFile) @@ -121,6 +170,10 @@ ${ gameDirectory.resolve(gameScript).writeText(DEFAULT_GAME_SCRIPT) + if (bootScript.isNotBlank()) { + gameDirectory.resolve(bootScript).writeText(DEFAULT_BOOT_SCRIPT) + } + CreateCommand::class.java.getResourceAsStream("/_tiny.stub.lua")?.let { content -> gameDirectory.resolve("_tiny.stub.lua").writeBytes(content.readAllBytes()) } @@ -130,6 +183,7 @@ ${ echo("\uD83C\uDFD7\uFE0F Game created into: ${gameDirectory.absolutePath}") echo("\uD83C\uDFA8 Palette image created: ${paletteFile.name}") + echo("\uD83C\uDFAE Game icon created: ${iconFile.name}") echo("\uD83C\uDFC3\u200D♂\uFE0F To run the game: tiny-cli run ${computePath(gameDirectory)}") } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DebugCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DebugCommand.kt deleted file mode 100644 index fc4b0bb0..00000000 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DebugCommand.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.github.minigdx.tiny.cli.command - -import com.github.ajalt.clikt.core.Abort -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.core.Context -import com.github.ajalt.clikt.parameters.options.default -import com.github.ajalt.clikt.parameters.options.option -import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.clikt.parameters.types.int -import com.github.minigdx.tiny.cli.config.GameParameters -import com.github.minigdx.tiny.cli.debug.DebugRemoteCommand -import com.github.minigdx.tiny.cli.debug.Disconnect -import com.github.minigdx.tiny.cli.debug.EngineRemoteCommand -import com.github.minigdx.tiny.cli.ui.TinyDebuggerUI -import io.ktor.client.HttpClient -import io.ktor.client.plugins.websocket.WebSockets -import io.ktor.client.plugins.websocket.webSocketSession -import io.ktor.websocket.Frame -import io.ktor.websocket.close -import io.ktor.websocket.readText -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import java.io.File -import javax.swing.SwingUtilities - -class DebugCommand : CliktCommand(name = "debug") { - val gameDirectory by option("-d", "--directory", help = "The directory containing all game information.") - .file(mustExist = true, canBeDir = true, canBeFile = false) - .default(File(".")) - - val debug by option(help = "Debug port used by the game.") - .int() - .default(8081) - - override fun help(context: Context) = "Debug the current game" - - override fun run() { - val configFile = gameDirectory.resolve("_tiny.json") - if (!configFile.exists()) { - echo("\uD83D\uDE2D No _tiny.json found! Can't run the game without.") - throw Abort() - } - val gameParameters = GameParameters.read(configFile) - - val debugCommandSender = Channel() - val engineCommandReceiver = Channel() - - SwingUtilities.invokeLater { - TinyDebuggerUI(debugCommandSender, engineCommandReceiver, gameParameters).apply { isVisible = true } - } - - runBlocking { - val client = - HttpClient { - install(WebSockets) - } - - var connected = false - while (!connected) { - try { - connectToGame(client, debugCommandSender, engineCommandReceiver) - connected = true - } catch (ex: Exception) { - delay(500) - connected = false - } - } - } - } - - private suspend fun connectToGame( - client: HttpClient, - channel: ReceiveChannel, - received: SendChannel, - ) { - val session = client.webSocketSession("ws://localhost:$debug/debug") - - coroutineScope { - launch { - for (message in channel) { - session.outgoing.send(Frame.Text(Json.encodeToString(message))) - if (message is Disconnect) { - session.close() - } - } - } - - launch { - for (message in session.incoming) { - if (message is Frame.Text) { - received.send(Json.decodeFromString(message.readText())) - } - } - } - } - } -} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DocsCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DocsCommand.kt index aa70194e..ea2118c5 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DocsCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/DocsCommand.kt @@ -9,76 +9,82 @@ import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.file import com.github.mingdx.tiny.doc.CliAnnotation -import com.github.minigdx.tiny.cli.command.utils.AsciidocHelpFormatter +import com.github.minigdx.tiny.cli.command.utils.JsonHelpFormatter +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject import java.io.File /** - * Command to generate AsciiDoc documentation for all CLI commands. + * Command to generate JSON documentation for all CLI commands. * * This command iterates through all registered Tiny CLI commands and generates - * comprehensive AsciiDoc documentation including arguments, options, defaults, - * and usage examples. + * a JSON file containing structured documentation including arguments, options, + * defaults, and usage examples. */ @CliAnnotation(hidden = true) class DocsCommand : CliktCommand(name = "docs") { private val outputFile by option( "--output", "-o", - help = "Output file path for the generated AsciiDoc documentation", + help = "Output file path for the generated JSON documentation", ) .file(mustExist = false, canBeDir = false, canBeFile = true) - .default(File("tiny-cli-commands.adoc")) + .default(File("tiny-cli-commands.json")) - override fun help(context: Context) = "Generate AsciiDoc documentation for all CLI commands" + override fun help(context: Context) = "Generate JSON documentation for all CLI commands" override fun run() { - echo("📚 Generating CLI documentation...") + echo("Generating CLI documentation...") - val documentation = buildString { - // Document header (level 2 since this will be included in main docs) - appendLine("== Tiny CLI Commands Reference") - appendLine() - appendLine("=== Commands") - appendLine() + val prettyJson = Json { prettyPrint = true } + val commandJsons = mutableListOf() - // Get all command classes to document - val commands = listOf( - CreateCommand(), - RunCommand(), - DebugCommand(), - AddCommand(), - ExportCommand(), - ServeCommand(), - PaletteCommand(), - SfxCommand(), - UpdateCommand(), - ResourcesCommand(), - ) + val commands = listOf( + CreateCommand(), + RunCommand(), + AddCommand(), + ExportCommand(), + ServeCommand(), + PaletteCommand(), + SfxCommand(), + UpdateCommand(), + ResourcesCommand(), + RecordCommand(), + ) - // Generate documentation for each command - commands.forEach { command -> - try { - command.context { helpFormatter = { AsciidocHelpFormatter } } - command.parse(arrayOf("-h")) - } catch (e: CliktError) { - val asciidocHelp = command.getFormattedHelp(e) - appendLine(asciidocHelp) - // Use AsciidocHelpFormatter to convert help to AsciiDoc - appendLine() - } catch (e: Exception) { - echo("⚠️ Warning: Could not generate docs for ${command.commandName}: ${e.message}", err = true) - e.printStackTrace() + commands.forEach { command -> + try { + command.context { helpFormatter = { JsonHelpFormatter } } + command.parse(arrayOf("-h")) + } catch (e: CliktError) { + val helpJson = command.getFormattedHelp(e) + if (helpJson != null) { + commandJsons.add(Json.parseToJsonElement(helpJson).jsonObject) } + } catch (e: Exception) { + echo("Warning: Could not generate docs for ${command.commandName}: ${e.message}", err = true) } } - // Write to output file + val result = buildJsonObject { + put( + "commands", + buildJsonArray { + commandJsons.forEach { add(it) } + }, + ) + } + try { outputFile.parentFile?.mkdirs() - outputFile.writeText(documentation) - echo("✅ Documentation generated successfully: ${outputFile.absolutePath}") + outputFile.writeText(prettyJson.encodeToString(JsonElement.serializer(), result)) + echo("Documentation generated successfully: ${outputFile.absolutePath}") } catch (e: Exception) { - echo("❌ Error writing documentation file: ${e.message}", err = true) + echo("Error writing documentation file: ${e.message}", err = true) throw e } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ExportCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ExportCommand.kt index ea714614..84bb0341 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ExportCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ExportCommand.kt @@ -1,5 +1,6 @@ package com.github.minigdx.tiny.cli.command +import com.github.ajalt.clikt.core.Abort import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.parameters.arguments.argument @@ -9,6 +10,7 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.choice import com.github.ajalt.clikt.parameters.types.file +import com.github.minigdx.tiny.cli.command.utils.IconConverter import com.github.minigdx.tiny.cli.config.GameParameters import com.github.minigdx.tiny.cli.config.GameParameters.Companion.JSON import com.github.minigdx.tiny.cli.config.GameParametersV1 @@ -95,7 +97,7 @@ class ExportCommand : CliktCommand(name = "export") { if (includeJdk && !isJpackageAvailable()) { echo("\uD83D\uDE31 jpackage is not available. Please use Java 14 or later with jpackage support.") echo("\uD83D\uDCA1 Alternatively, use --exclude-jdk to create a portable JAR launcher.") - return + throw Abort() } val targetPlatform = when (desktopPlatform) { @@ -113,6 +115,7 @@ class ExportCommand : CliktCommand(name = "export") { appVersion = appVersion, platform = targetPlatform, debug = debug, + gameParameters = gameParameters, ) } else { createPortableJarLauncher( @@ -143,6 +146,52 @@ class ExportCommand : CliktCommand(name = "export") { } } + /** + * Resolve the icon file for the target platform, converting to the platform-specific + * format if needed (ICO for Windows, ICNS for macOS, PNG for Linux). + */ + private fun resolveIconForPlatform( + gameDir: File, + gameParameters: GameParameters, + platform: String, + tempDir: File, + ): File? { + val iconName = when (gameParameters) { + is GameParametersV1 -> gameParameters.icon + } + + // Find the icon file: use configured icon, or fall back to icon.png + val iconFile = if (iconName != null) { + gameDir.resolve(iconName) + } else { + gameDir.resolve("icon.png") + } + + if (!iconFile.exists()) { + return null + } + + return when (platform) { + "windows" -> { + val icoFile = File(tempDir, "icon.ico") + val result = IconConverter.convertToIco(iconFile, icoFile) + if (result == null) { + echo("\u26A0\uFE0F Failed to convert icon to ICO format.") + } + result + } + "mac" -> { + val icnsFile = File(tempDir, "icon.icns") + val result = IconConverter.convertToIcns(iconFile, icnsFile) + if (result == null) { + echo("\u26A0\uFE0F Failed to convert icon to ICNS format (sips not available?).") + } + result + } + else -> iconFile // Linux accepts PNG directly + } + } + private fun createStandaloneAppWithJdk( gameDir: File, outputDir: File, @@ -150,6 +199,7 @@ class ExportCommand : CliktCommand(name = "export") { appVersion: String, platform: String, debug: Boolean = false, + gameParameters: GameParameters, ) { echo("\uD83D\uDCE6 Creating standalone application with bundled JDK...") @@ -169,6 +219,12 @@ class ExportCommand : CliktCommand(name = "export") { "--main-class", "com.github.minigdx.tiny.cli.MainKt", ) + // Add application icon + val iconFile = resolveIconForPlatform(gameDir, gameParameters, platform, tempDir) + if (iconFile != null) { + jpackageCommand.addAll(listOf("--icon", iconFile.absolutePath)) + } + // Add platform-specific arguments for game location when (platform) { "mac" -> { @@ -180,6 +236,8 @@ class ExportCommand : CliktCommand(name = "export") { "run", "--arguments", "--mac-from-jpackage", + "--arguments", + "--no-debug", ), ) @@ -198,6 +256,8 @@ class ExportCommand : CliktCommand(name = "export") { "run", "--arguments", "game", + "--arguments", + "--no-debug", ), ) @@ -213,6 +273,8 @@ class ExportCommand : CliktCommand(name = "export") { "run", "--arguments", "game", + "--arguments", + "--no-debug", ), ) @@ -228,6 +290,8 @@ class ExportCommand : CliktCommand(name = "export") { "run", "--arguments", "game", + "--arguments", + "--no-debug", ), ) } @@ -242,6 +306,7 @@ class ExportCommand : CliktCommand(name = "export") { if (exitCode != 0) { echo("\uD83D\uDE31 jpackage failed with exit code $exitCode") + throw Abort() } if (!debug) { @@ -279,7 +344,7 @@ class ExportCommand : CliktCommand(name = "export") { echo("\uD83D\uDE31 Could not find tiny-cli JAR.") echo("\uD83D\uDCA1 The CLI must be installed before using export-desktop.") echo("\uD83D\uDCA1 Run 'make install' to install the CLI.") - return + throw Abort() } Files.copy(cliJar.toPath(), outputJar.toPath(), StandardCopyOption.REPLACE_EXISTING) @@ -359,7 +424,7 @@ class ExportCommand : CliktCommand(name = "export") { private fun getDependencies(): List { val classPath = System.getProperty("java.class.path") - return classPath.split(":") + return classPath.split(File.pathSeparator) .filter { it.endsWith(".jar") } .filter { !it.contains("tiny-cli-") } // Exclude the CLI jar as it's already copied .distinct() @@ -436,7 +501,7 @@ class ExportCommand : CliktCommand(name = "export") { return when (platform) { "windows" -> "exe" "mac" -> "dmg" - "linux" -> "pkg" + "linux" -> "deb" else -> "app-image" } } @@ -444,6 +509,22 @@ class ExportCommand : CliktCommand(name = "export") { @OptIn(ExperimentalSerializationApi::class) class GameExporter { + /** + * Resolve a file name relative to the game directory, ensuring the resolved path + * stays within the game directory to prevent path traversal attacks. + */ + private fun safeResolve( + gameDirectory: File, + name: String, + ): File { + val resolved = gameDirectory.resolve(name).canonicalFile + val gameRoot = gameDirectory.canonicalFile + require(resolved.path.startsWith(gameRoot.path + File.separator) || resolved.path == gameRoot.path) { + "Path traversal detected: '$name' resolves outside the game directory" + } + return gameDirectory.resolve(name) + } + fun export( gameDirectory: File, archive: String, @@ -497,11 +578,22 @@ class GameExporter { when (gameParameters) { is GameParametersV1 -> { + // Bundle the custom boot script if configured + listOfNotNull(gameParameters.bootScript) + .filterNot { exportedFile.contains(it) } + .forEach { name -> + exportedGame.putNextEntry(ZipEntry(name)) + exportedGame.write(safeResolve(gameDirectory, name).readBytes()) + exportedGame.closeEntry() + + exportedFile += name + } + (gameParameters.scripts) .filterNot { exportedFile.contains(it) } .forEach { name -> exportedGame.putNextEntry(ZipEntry(name)) - exportedGame.write(gameDirectory.resolve(name).readBytes()) + exportedGame.write(safeResolve(gameDirectory, name).readBytes()) exportedGame.closeEntry() exportedFile += name @@ -510,7 +602,17 @@ class GameExporter { .filterNot { exportedFile.contains(it) } .forEach { name -> exportedGame.putNextEntry(ZipEntry(name)) - exportedGame.write(gameDirectory.resolve(name).readBytes()) + exportedGame.write(safeResolve(gameDirectory, name).readBytes()) + exportedGame.closeEntry() + + exportedFile += name + } + gameParameters.fonts + .map { it.spritesheet } + .filterNot { exportedFile.contains(it) } + .forEach { name -> + exportedGame.putNextEntry(ZipEntry(name)) + exportedGame.write(safeResolve(gameDirectory, name).readBytes()) exportedGame.closeEntry() exportedFile += name @@ -519,7 +621,7 @@ class GameExporter { .filterNot { exportedFile.contains(it) } .forEach { name -> exportedGame.putNextEntry(ZipEntry(name)) - exportedGame.write(gameDirectory.resolve(name).readBytes()) + exportedGame.write(safeResolve(gameDirectory, name).readBytes()) exportedGame.closeEntry() exportedFile += name @@ -528,15 +630,15 @@ class GameExporter { .filterNot { exportedFile.contains(it) } .forEach { name -> exportedGame.putNextEntry(ZipEntry(name)) - exportedGame.write(gameDirectory.resolve(name).readBytes()) + exportedGame.write(safeResolve(gameDirectory, name).readBytes()) exportedGame.closeEntry() exportedFile += name - val ldtk = Ldtk.read(gameDirectory.resolve(name).readText()) + val ldtk = Ldtk.read(safeResolve(gameDirectory, name).readText()) ldtk.levels.flatMap { level -> level.layerInstances } .mapNotNull { it.__tilesetRelPath } - .map { gameDirectory.resolve(it) } + .map { safeResolve(gameDirectory, it) } .filterNot { file -> exportedFile.contains(file.relativeTo(gameDirectory).name) } .toSet() .forEach { file -> @@ -548,34 +650,33 @@ class GameExporter { } } - // Add index.html - - var template = indexContent - template = template.replace("{GAME_ID}", gameParameters.id) - template = template.replace("{GAME_NAME}", gameParameters.name) - template = template.replace("{GAME_WIDTH}", gameParameters.resolution.width.toString()) - template = template.replace("{GAME_HEIGHT}", gameParameters.resolution.height.toString()) - template = template.replace("{GAME_ZOOM}", gameParameters.zoom.toString()) - template = template.replace("{GAME_SPRW}", gameParameters.sprites.width.toString()) - template = template.replace("{GAME_SPRH}", gameParameters.sprites.height.toString()) - template = template.replace("{GAME_HIDE_MOUSE}", gameParameters.hideMouseCursor.toString()) - - template = replaceList( - template, - gameParameters.scripts, - "{GAME_SCRIPT}", - "GAME_SCRIPT", - ) - template = replaceList( - template, - gameParameters.spritesheets, - "{GAME_SPRITESHEET}", - "GAME_SPRITESHEET", - ) - template = replaceList(template, gameParameters.levels, "{GAME_LEVEL}", "GAME_LEVEL") - template = replaceList(template, listOfNotNull(gameParameters.sound), "{GAME_SOUND}", "GAME_SOUND") + // Add game icon to the export + val iconFileName = gameParameters.icon + val iconList = if (iconFileName != null) { + val iconFile = safeResolve(gameDirectory, iconFileName) + if (iconFile.exists() && !exportedFile.contains(iconFileName)) { + exportedGame.putNextEntry(ZipEntry(iconFileName)) + exportedGame.write(iconFile.readBytes()) + exportedGame.closeEntry() + exportedFile += iconFileName + } + listOf(iconFileName) + } else { + // Fallback: check if icon.png exists in game directory + val defaultIcon = gameDirectory.resolve("icon.png") + if (defaultIcon.exists() && !exportedFile.contains("icon.png")) { + exportedGame.putNextEntry(ZipEntry("icon.png")) + exportedGame.write(defaultIcon.readBytes()) + exportedGame.closeEntry() + exportedFile += "icon.png" + listOf("icon.png") + } else { + emptyList() + } + } - template = template.replace("{GAME_COLORS}", gameParameters.colors.joinToString(",")) + // Add index.html with only the game name replaced + val template = indexContent.replace("{GAME_NAME}", gameParameters.name) exportedGame.putNextEntry(ZipEntry("index.html")) exportedGame.write(template.toByteArray()) @@ -586,22 +687,6 @@ class GameExporter { exportedGame.close() } - private fun replaceList( - template: String, - values: List, - tag: String, - delimiter: String, - ): String { - val pattern = ("(.*?)").toRegex(RegexOption.DOT_MATCHES_ALL) - val delimiterTag = pattern.find(template)!!.groupValues[1] - - var result = "" - values.forEach { script -> - result += delimiterTag.replace(tag, script) - } - return template.replace(delimiterTag, result) - } - companion object { val ENGINE_FILES = setOf( diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/MainCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/MainCommand.kt index edbdb29e..f5bd91ba 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/MainCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/MainCommand.kt @@ -10,13 +10,13 @@ class MainCommand : CliktCommand() { subcommands( CreateCommand(), RunCommand(), - DebugCommand(), AddCommand(), ExportCommand(), ServeCommand(), PaletteCommand(), SfxCommand(), UpdateCommand(), + RecordCommand(), ResourcesCommand(), DocsCommand(), ) diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RecordCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RecordCommand.kt new file mode 100644 index 00000000..76688ff8 --- /dev/null +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RecordCommand.kt @@ -0,0 +1,124 @@ +package com.github.minigdx.tiny.cli.command + +import com.github.ajalt.clikt.core.Abort +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.default +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.int +import com.github.ajalt.clikt.parameters.types.long +import com.github.minigdx.tiny.cli.config.GameParameters +import com.github.minigdx.tiny.engine.GameEngine +import com.github.minigdx.tiny.engine.GameEngineListener +import com.github.minigdx.tiny.file.CommonVirtualFileSystem +import com.github.minigdx.tiny.log.LogLevel +import com.github.minigdx.tiny.log.StdOutLogger +import com.github.minigdx.tiny.platform.glfw.GlfwPlatform +import com.github.minigdx.tiny.resources.GameScript +import java.io.File + +class RecordCommand : CliktCommand(name = "record") { + val gameDirectory by argument(help = "The directory containing your game to record.") + .file(mustExist = true, canBeDir = true, canBeFile = false) + .default(File(".")) + + val duration by option("--duration", "-d", help = "Duration in seconds (default: 5)") + .int() + .default(5) + + val frames by option("--frames", "-f", help = "Number of frames to capture (overrides --duration)") + .long() + + val output by option("--output", "-o", help = "Output file path (extension determines format: .png for screenshot, .gif for animation)") + + val headless by option("--headless", help = "Run without displaying the game window") + .flag() + + val includeBoot by option("--include-boot", help = "Include the boot animation in the recording") + .flag() + + override fun help(context: Context) = "Record your game as a GIF or PNG screenshot." + + override fun run() { + val configFile = gameDirectory.resolve("_tiny.json") + if (!configFile.exists()) { + echo("No _tiny.json found in ${gameDirectory.absolutePath}! Can't record the game without it.") + throw Abort() + } + + val gameParameters = GameParameters.read(configFile) + val maxFrames = frames ?: (duration * 60L) + val outputFile = File(output ?: gameDirectory.resolve("recording.gif").path) + val isScreenshot = outputFile.extension.lowercase() == "png" + + val logger = StdOutLogger("tiny-cli", level = LogLevel.INFO) + val homeDirectory = findHomeDirectory(gameParameters) + val vfs = CommonVirtualFileSystem() + + val baseOptions = gameParameters.toGameOptions() + val recordSeconds = (maxFrames / 60f) + 1f + // When excluding boot, use unlimited frames initially (0) so the engine + // doesn't stop during boot. The real limit is set after boot completes. + val engineMaxFrames = if (includeBoot) maxFrames else 0L + val gameOption = baseOptions.copy( + headless = headless, + maxFrames = engineMaxFrames, + record = recordSeconds, + ) + + val platform = GlfwPlatform( + gameOption, + logger, + vfs, + gameDirectory, + homeDirectory, + ) + + var engine: GameEngine? = null + var bootEnded = false + + val gameEngine = GameEngine( + gameOptions = gameOption, + platform = platform, + vfs = vfs, + logger = logger, + listener = object : GameEngineListener { + override fun switchScript( + before: GameScript?, + after: GameScript?, + ) { + if (!bootEnded) { + bootEnded = true + if (!includeBoot) { + platform.clearRecordingCache() + engine?.resetFrameCounter(maxFrames) + } + } + } + + override fun reload(gameScript: GameScript?) = Unit + }, + ) + engine = gameEngine + + echo("Recording ${if (isScreenshot) "screenshot" else "GIF"} ($maxFrames frames)...") + + gameEngine.main() + + if (isScreenshot) { + platform.screenshotSync(outputFile) + } else { + platform.recordSync(outputFile) + } + + echo("Saved to ${outputFile.absolutePath}") + + // Force exit as the sound manager may keep background threads alive. + @Suppress("ExitProcess") + kotlin.system.exitProcess(0) + } +} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ResourcesCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ResourcesCommand.kt index 2afa59fe..eba44362 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ResourcesCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/ResourcesCommand.kt @@ -1,15 +1,12 @@ package com.github.minigdx.tiny.cli.command +import com.github.ajalt.clikt.core.Abort import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context -import com.github.ajalt.clikt.core.terminal import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.multiple import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.mordant.input.InputReceiver -import com.github.ajalt.mordant.input.receiveKeyEvents -import com.github.ajalt.mordant.rendering.TextStyles -import com.github.ajalt.mordant.table.table import com.github.minigdx.tiny.cli.config.GameParameters import com.github.minigdx.tiny.cli.config.GameParametersV1 import com.github.minigdx.tiny.cli.exception.MissingTinyConfigurationException @@ -20,166 +17,107 @@ class ResourcesCommand : CliktCommand(name = "resources") { .file(mustExist = true, canBeDir = true, canBeFile = false) .default(File(".")) - private val categoryFilter by option("--category", help = "Filter categories by name or pattern") + private val delete by option("--delete", help = "Remove a resource from the game by filename.") + .multiple() - private var selectedIndex = 0 - - override fun help(context: Context) = "Inspect and manage categorized resources in the game." + override fun help(context: Context) = "Inspect and manage game resources." override fun run() { - val tiny = gameDirectory.resolve("_tiny.json") - if (!tiny.exists()) { - throw MissingTinyConfigurationException(tiny) + val configFile = gameDirectory.resolve("_tiny.json") + if (!configFile.exists()) { + throw MissingTinyConfigurationException(configFile) } - try { - var gameParameters = GameParameters.read(tiny) as GameParametersV1 - - showResourceManager(gameParameters) - currentContext.terminal.receiveKeyEvents { event -> - val next = when (event.key) { - "ArrowUp" -> { - selectedIndex = (selectedIndex - 1).coerceAtLeast(0) - true - } - - "ArrowDown" -> { - val maximumValue = getResourceCategories(gameParameters as GameParametersV1).map { it.value.size }.sum() - 1 - selectedIndex = (selectedIndex + 1).coerceAtMost(maximumValue) - true - } - - "d" -> { - gameParameters = deleteSelected(selectedIndex, gameParameters as GameParametersV1) - selectedIndex = (selectedIndex - 1).coerceAtLeast(0) - true - } - - "q" -> { - save(gameParameters) - false - } - - else -> true - } - if (next) { - showResourceManager(gameParameters) - InputReceiver.Status.Continue - } else { - InputReceiver.Status.Finished - } - } + val gameParameters = try { + GameParameters.read(configFile) } catch (e: Exception) { echo("❌ Error reading _tiny.json: ${e.message}") + throw Abort() + } + + if (gameParameters !is GameParametersV1) { + echo("❌ Only V1 game configuration is supported.") + throw Abort() } - } - private fun save(configuration: GameParameters) { - val configurationFile = gameDirectory.resolve("_tiny.json") - configuration.write(configurationFile) + if (delete.isNotEmpty()) { + deleteResources(gameParameters, configFile) + } else { + displayResources(gameParameters) + } } - private fun deleteSelected( - selectedIndex: Int, - parameters: GameParametersV1, - ): GameParametersV1 { - val categories = getResourceCategories(parameters) - - // Flatten all resources while keeping track of their category and index within that category - val flattenedResources = mutableListOf>() // (categoryName, resourcePath, indexInCategory) - categories.forEach { (categoryName, resources) -> - resources.forEachIndexed { indexInCategory, resourcePath -> - flattenedResources.add(Triple(categoryName, resourcePath, indexInCategory)) + private fun deleteResources( + params: GameParametersV1, + configFile: File, + ) { + var updated = params + + for (resource in delete) { + val found = resource in updated.scripts || + resource in updated.spritesheets || + resource in updated.levels || + resource == updated.sound || + updated.fonts.any { it.spritesheet == resource || it.name == resource } + + if (!found) { + echo("❌ Resource not found: $resource") + continue } - } - // Validate selectedIndex - if (selectedIndex < 0 || selectedIndex >= flattenedResources.size) { - throw IndexOutOfBoundsException("Invalid selectedIndex: $selectedIndex. Valid range: 0-${flattenedResources.size - 1}") + updated = updated.copy( + scripts = updated.scripts.filter { it != resource }, + spritesheets = updated.spritesheets.filter { it != resource }, + levels = updated.levels.filter { it != resource }, + sound = if (updated.sound == resource) null else updated.sound, + fonts = updated.fonts.filter { it.spritesheet != resource && it.name != resource }, + ) + echo("✅ Removed: $resource") } - // Get the resource to delete - val (categoryName, resourceToDelete, indexInCategory) = flattenedResources[selectedIndex] - - // Create a copy of parameters with the resource removed from the appropriate category - return when (categoryName) { - "\uD83D\uDCDD scripts" -> { - val updatedScripts = parameters.scripts.toMutableList() - updatedScripts.removeAt(indexInCategory) - parameters.copy(scripts = updatedScripts) - } - "\uD83D\uDDBC\uFE0F spritesheets" -> { - val updatedSpritesheets = parameters.spritesheets.toMutableList() - updatedSpritesheets.removeAt(indexInCategory) - parameters.copy(spritesheets = updatedSpritesheets) - } - "\uD83D\uDDFA\uFE0F levels" -> { - val updatedLevels = parameters.levels.toMutableList() - updatedLevels.removeAt(indexInCategory) - parameters.copy(levels = updatedLevels) - } - "\uD83D\uDD08 sounds" -> { - val updatedSounds = listOfNotNull(parameters.sound).toMutableList() - updatedSounds.removeAt(indexInCategory) - parameters.copy(sound = updatedSounds.firstOrNull()) - } - else -> throw IllegalStateException("Unknown category: $categoryName") + try { + updated.write(configFile) + } catch (e: Exception) { + echo("❌ Error saving _tiny.json: ${e.message}") + throw Abort() } - } - private fun showResourceManager(gameParameters: GameParametersV1) { - val categories = getResourceCategories(gameParameters) + echo() + displayResources(updated) + } - if (categories.isEmpty()) { - echo("No categories found${if (categoryFilter != null) " matching filter '$categoryFilter'" else ""}") - return - } + private fun displayResources(params: GameParametersV1) { + echo("📦 Game resources") + echo() - val table = table { - header { - row("Category", "Resource") - } - body { - var index = 0 - categories.forEach { (categorie, resources) -> - resources.forEachIndexed { rindex, resource -> - row { - val isSelected = selectedIndex == index - cell(if (isSelected) TextStyles.bold(categorie) else categorie) - val lineContent = "$rindex $resource" - val content = if (isSelected) { - TextStyles.bold(lineContent) - } else { - lineContent - } - cell(content) { - columnSpan = 4 - } - - index++ - } - } - } - } - } + displayCategory("📝 Scripts", params.scripts) + displayCategory("🖼️ Spritesheets", params.spritesheets) + displayCategory("🗺️ Levels", params.levels) + displayCategory("🔊 Sounds", listOfNotNull(params.sound)) + displayFonts(params) + } - currentContext.terminal.cursor.move { - clearScreen() + private fun displayCategory( + label: String, + resources: List, + ) { + if (resources.isEmpty()) return + echo("$label:") + resources.forEachIndexed { index, resource -> + echo(" ${index + 1}. $resource") } - echo(table) - echo("\nCommands:") - echo("• Enter category number to manage resources") - echo("• 'q' to quit") - echo("• 'd' to delete from the game") + echo() } - private fun getResourceCategories(gameParameters: GameParametersV1): Map> { - return listOf( - "\uD83D\uDCDD scripts" to gameParameters.scripts, - "\uD83D\uDDBC\uFE0F spritesheets" to gameParameters.spritesheets, - "\uD83D\uDDFA\uFE0F levels" to gameParameters.levels, - "\uD83D\uDD08 sound" to listOfNotNull(gameParameters.sound), - ).filter { it.second.isNotEmpty() } - .toMap() + private fun displayFonts(params: GameParametersV1) { + if (params.fonts.isEmpty()) return + echo("🔤 Fonts:") + params.fonts.forEachIndexed { index, font -> + val banks = font.banks.joinToString(", ") { bank -> + "${bank.name}(${bank.width}x${bank.height})" + } + echo(" ${index + 1}. ${font.name} [${font.spritesheet}] — $banks") + } + echo() } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RunCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RunCommand.kt index 7c1440fc..2be61ffb 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RunCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/RunCommand.kt @@ -11,22 +11,37 @@ import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.file import com.github.ajalt.clikt.parameters.types.int import com.github.minigdx.tiny.cli.config.GameParameters +import com.github.minigdx.tiny.cli.debug.AllFiles import com.github.minigdx.tiny.cli.debug.DebugRemoteCommand import com.github.minigdx.tiny.cli.debug.DebuggerExecutionListener import com.github.minigdx.tiny.cli.debug.EngineRemoteCommand +import com.github.minigdx.tiny.cli.debug.FileChanged +import com.github.minigdx.tiny.cli.debug.FileInfo +import com.github.minigdx.tiny.cli.debug.GameMetadata import com.github.minigdx.tiny.cli.debug.Reload import com.github.minigdx.tiny.engine.GameEngine import com.github.minigdx.tiny.engine.GameEngineListener import com.github.minigdx.tiny.engine.TinyException import com.github.minigdx.tiny.file.CommonVirtualFileSystem +import com.github.minigdx.tiny.input.Key import com.github.minigdx.tiny.log.LogLevel import com.github.minigdx.tiny.log.StdOutLogger import com.github.minigdx.tiny.lua.errorLine import com.github.minigdx.tiny.platform.glfw.GlfwPlatform +import com.github.minigdx.tiny.platform.glfw.LwjglInput +import com.github.minigdx.tiny.platform.glfw.keyCode import com.github.minigdx.tiny.resources.GameScript +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode import io.ktor.server.application.install import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty +import io.ktor.server.response.respond +import io.ktor.server.response.respondBytes +import io.ktor.server.response.respondText +import io.ktor.server.routing.RoutingContext +import io.ktor.server.routing.get +import io.ktor.server.routing.post import io.ktor.server.routing.routing import io.ktor.server.websocket.WebSockets import io.ktor.server.websocket.webSocket @@ -35,10 +50,16 @@ import io.ktor.websocket.readText import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.luaj.vm2.LuaError import java.io.File +import java.util.concurrent.atomic.AtomicReference +import java.util.jar.JarInputStream +import kotlin.system.exitProcess import kotlin.time.ExperimentalTime class RunCommand : CliktCommand(name = "run") { @@ -50,6 +71,9 @@ class RunCommand : CliktCommand(name = "run") { .int() .default(8081) + val noDebug by option("--no-debug", help = "Disable the debug server") + .flag() + val macFromJpackage by option( help = "Find the game directory inside the Game MacOS App Bundle instead", hidden = true, @@ -58,16 +82,6 @@ class RunCommand : CliktCommand(name = "run") { override fun help(context: Context) = "Run your game." - private fun isOracleOrOpenJDK(): Boolean { - val vendor = System.getProperty("java.vendor")?.lowercase() - return vendor?.contains("oracle") == true || vendor?.contains("eclipse") == true || vendor?.contains("openjdk") == true - } - - private fun isMacOS(): Boolean { - val os = System.getProperty("os.name").lowercase() - return os.contains("mac") || os.contains("darwin") - } - private fun getClassLocationDirectory(): File { val classLocation = RunCommand::class.java.protectionDomain.codeSource.location.toURI().path val classLocationFile = File(classLocation) @@ -81,11 +95,6 @@ class RunCommand : CliktCommand(name = "run") { @OptIn(ExperimentalTime::class) override fun run() { - if (isMacOS() && isOracleOrOpenJDK()) { - echo("\uD83D\uDEA7 === The Tiny CLI on Mac with require a special option.") - echo("\uD83D\uDEA7 === If the application crash ➡ use the command 'tiny-cli-mac' instead.") - } - val effectiveGameDirectory = if (macFromJpackage) { val classLocationDir = getClassLocationDirectory() echo("\uD83D\uDC1F === Using class location directory instead of game directory due to mac-from-jpackage flag ===") @@ -95,37 +104,15 @@ class RunCommand : CliktCommand(name = "run") { gameDirectory } - echo("\uD83D\uDC1B === Running the game using debugger on the port '$debug' ===") - echo("\uD83D\uDC1B === Use the command 'tiny-cli debug' to connect the debugger to your game ===") - val debugCommandReceiver = Channel() val engineCommandSender = Channel() + val remoteInput = AtomicReference(null) - embeddedServer( - factory = Netty, - port = debug, - ) { - install(WebSockets) - - routing { - webSocket("/debug") { - launch { - for (command in engineCommandSender) { - outgoing.send(Frame.Text(Json.encodeToString(command))) - } - } - - for (frame in incoming) { - if (frame is Frame.Text) { - val command = Json.decodeFromString(frame.readText()) - debugCommandReceiver.send(command) - } else { - TODO("$frame content not expected") - } - } - } - } - }.start() + val server = if (!noDebug) { + startDebugServer(effectiveGameDirectory, debugCommandReceiver, engineCommandSender, remoteInput) + } else { + null + } try { val configFile = effectiveGameDirectory.resolve("_tiny.json") @@ -179,6 +166,9 @@ class RunCommand : CliktCommand(name = "run") { } }, ) + // Set remote input reference for the debug server control endpoints + remoteInput.set((gameEngine.platform as GlfwPlatform).remoteInput()) + Runtime.getRuntime().addShutdownHook( Thread { gameEngine.end() @@ -187,6 +177,14 @@ class RunCommand : CliktCommand(name = "run") { ) gameEngine.main() + + // Clean shutdown: stop the debug server and exit + if (server != null) { + echo("\uD83D\uDEE1\uFE0F Shutting down debug server...") + server.stop(1000, 2000) + echo("\u2705 Debug server stopped. Exiting...") + } + exitProcess(0) } catch (ex: Exception) { echo( "\uD83E\uDDE8 An unexpected exception occurred. " + @@ -205,7 +203,274 @@ class RunCommand : CliktCommand(name = "run") { } echo() ex.printStackTrace() + + // Clean shutdown even on exception + if (server != null) { + echo("\uD83D\uDEE1\uFE0F Shutting down debug server...") + server.stop(1000, 2000) + } + exitProcess(1) + } + } + + private fun startDebugServer( + effectiveGameDirectory: File, + debugCommandReceiver: Channel, + engineCommandSender: Channel, + remoteInput: AtomicReference, + ): io.ktor.server.engine.EmbeddedServer<*, *> { + val configFile = effectiveGameDirectory.resolve("_tiny.json") + val staticResources = loadDebuggerResources() + + val scriptFiles = if (configFile.exists()) { + val params = GameParameters.read(configFile) + params.getAllScripts() + } else { + emptyList() + } + + val lastModified = mutableMapOf() + if (configFile.exists()) { + lastModified["_tiny.json"] = configFile.lastModified() + } + scriptFiles.forEach { script -> + val file = effectiveGameDirectory.resolve(script) + if (file.exists()) { + lastModified[script] = file.lastModified() + } + } + + val server = embeddedServer( + factory = Netty, + port = debug, + ) { + install(WebSockets) + + routing { + // Unified WebSocket: engine debug commands + file watching + metadata + webSocket("/debug") { + // Send game metadata on connect + if (configFile.exists()) { + val params = GameParameters.read(configFile) + val metadata = GameMetadata(gameId = params.id, gameName = params.name) + outgoing.send(Frame.Text(Json.encodeToString(metadata))) + } + + // Send initial file list + val allFiles = buildFilesList(effectiveGameDirectory) + outgoing.send(Frame.Text(Json.encodeToString(AllFiles(allFiles)))) + + // Forward engine commands to the client + launch { + for (command in engineCommandSender) { + outgoing.send(Frame.Text(Json.encodeToString(command))) + } + } + + // File-watching coroutine + launch { + while (isActive) { + delay(500) + + val currentScripts = try { + val params = GameParameters.read(configFile) + params.getAllScripts() + } catch (_: Exception) { + scriptFiles + } + + val configModified = configFile.lastModified() + if (configModified != lastModified["_tiny.json"]) { + lastModified["_tiny.json"] = configModified + val content = configFile.readText() + val msg = FileChanged(file = FileInfo("_tiny.json", content)) + outgoing.send(Frame.Text(Json.encodeToString(msg))) + } + + currentScripts.forEach { script -> + val file = effectiveGameDirectory.resolve(script) + if (file.exists()) { + val modified = file.lastModified() + if (modified != lastModified[script]) { + lastModified[script] = modified + val content = file.readText() + val msg = FileChanged(file = FileInfo(script, content)) + outgoing.send(Frame.Text(Json.encodeToString(msg))) + } + } + } + } + } + + // Process incoming debug commands from the client + for (frame in incoming) { + if (frame is Frame.Text) { + val command = Json.decodeFromString(frame.readText()) + debugCommandReceiver.send(command) + } else { + TODO("$frame content not expected") + } + } + } + + // Serve debugger webapp static files + get("/") { + val value = staticResources["index.html"] + if (value != null) { + call.respondBytes(value, ContentType.Text.Html) + } else { + call.respond(HttpStatusCode.NotFound) + } + } + + get("/{...}") { + val key = call.request.local.uri.let { k -> + if (k.startsWith("/")) k.drop(1) else k + } + val value = staticResources[key] + if (value != null) { + val contentType = when { + key.endsWith(".js") -> ContentType.Application.JavaScript + key.endsWith(".css") -> ContentType.Text.CSS + key.endsWith(".html") -> ContentType.Text.Html + key.endsWith(".png") -> ContentType.Image.PNG + key.endsWith(".svg") -> ContentType.Image.SVG + key.endsWith(".json") -> ContentType.Application.Json + key.endsWith(".mjs") -> ContentType.Application.JavaScript + else -> ContentType.Application.OctetStream + } + call.respondBytes(value, contentType) + } else { + call.respond(HttpStatusCode.NotFound) + } + } + + // --- Remote control endpoints --- + + get("/control/keys") { + val keyNames = Key.entries + .filter { it != Key.ANY_KEY } + .joinToString(",", "[", "]") { "\"${it.name}\"" } + call.respondText(keyNames, ContentType.Application.Json) + } + + post("/control/press") { + withKeyInput(remoteInput) { input, key -> + input.injectKeyPress(key.keyCode) + call.respondText( + """{"ok":true,"action":"press","key":"${key.name}"}""", + ContentType.Application.Json, + ) + } + } + + post("/control/release") { + withKeyInput(remoteInput) { input, key -> + input.injectKeyRelease(key.keyCode) + call.respondText( + """{"ok":true,"action":"release","key":"${key.name}"}""", + ContentType.Application.Json, + ) + } + } + + post("/control/tap") { + withKeyInput(remoteInput) { input, key -> + val kc = key.keyCode + input.injectKeyPress(kc) + launch { + delay(50) + input.injectKeyRelease(kc) + } + call.respondText( + """{"ok":true,"action":"tap","key":"${key.name}"}""", + ContentType.Application.Json, + ) + } + } + } + }.start() + + val debuggerAddress = "http://localhost:$debug" + echo("\uD83D\uDC1B === Debug server started on port '$debug' ===") + echo("\uD83D\uDC1B === Debugger webapp: $debuggerAddress ===") + echo("\uD83C\uDFAE === Remote control: $debuggerAddress/control/keys ===") + + return server + } + + private suspend fun RoutingContext.withKeyInput( + remoteInput: AtomicReference, + action: suspend RoutingContext.(LwjglInput, Key) -> Unit, + ) { + val input = remoteInput.get() + if (input == null) { + call.respondText( + """{"error":"Engine not ready"}""", + ContentType.Application.Json, + HttpStatusCode.ServiceUnavailable, + ) + return + } + val keyName = call.request.queryParameters["key"] + if (keyName == null) { + call.respondText( + """{"error":"Missing 'key' query parameter"}""", + ContentType.Application.Json, + HttpStatusCode.BadRequest, + ) + return + } + val key = try { + Key.valueOf(keyName) + } catch (_: IllegalArgumentException) { + null + } + if (key == null || key == Key.ANY_KEY) { + call.respondText( + """{"error":"Unknown key: $keyName"}""", + ContentType.Application.Json, + HttpStatusCode.BadRequest, + ) + return + } + action(input, key) + } + + private fun loadDebuggerResources(): Map { + val resources = mutableMapOf() + + val debuggerZip = RunCommand::class.java + .getResourceAsStream("/tiny-debugger.zip") + + if (debuggerZip != null) { + JarInputStream(debuggerZip).use { jarInput -> + var entry = jarInput.nextJarEntry + while (entry != null) { + if (!entry.isDirectory && !entry.name.startsWith("META-INF/")) { + resources[entry.name] = jarInput.readAllBytes() + } + jarInput.closeEntry() + entry = jarInput.nextJarEntry + } + } + } + + return resources + } + + private fun buildFilesList(gameDir: File): List { + val configFile = gameDir.resolve("_tiny.json") + if (!configFile.exists()) return emptyList() + val gameParameters = GameParameters.read(configFile) + val files = mutableListOf() + gameParameters.getAllScripts().forEach { script -> + val file = gameDir.resolve(script) + if (file.exists()) { + files.add(FileInfo(script, file.readText())) + } } + return files } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/SfxCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/SfxCommand.kt index 639300ec..7c2192be 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/SfxCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/SfxCommand.kt @@ -24,24 +24,9 @@ class SfxCommand : CliktCommand(name = "sfx") { canBeFile = true, ) - fun isOracleOrOpenJDK(): Boolean { - val vendor = System.getProperty("java.vendor")?.lowercase() - return vendor?.contains("oracle") == true || vendor?.contains("eclipse") == true || vendor?.contains("openjdk") == true - } - - fun isMacOS(): Boolean { - val os = System.getProperty("os.name").lowercase() - return os.contains("mac") || os.contains("darwin") - } - override fun help(context: Context) = "Start the SFX Editor" override fun run() { - if (isMacOS() && isOracleOrOpenJDK()) { - echo("\uD83D\uDEA7 === The Tiny CLI on Mac with require a special option.") - echo("\uD83D\uDEA7 === If the application crash ➡ use the command 'tiny-cli-mac' instead.") - } - try { val configFile = SfxCommand::class.java.getResourceAsStream("/sfx/_tiny.json") if (configFile == null) { @@ -81,7 +66,7 @@ class SfxCommand : CliktCommand(name = "sfx") { if (!sfxFileName.exists()) { val json = Json.encodeToString(Music()) - platform.saveIntoHome(sfxFileName.name, json) + platform.saveIntoGameDirectory(sfxFileName.name, json) } val gameEngine = GameEngine( diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/UpdateCommand.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/UpdateCommand.kt index b6e63314..233102ed 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/UpdateCommand.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/UpdateCommand.kt @@ -3,184 +3,129 @@ package com.github.minigdx.tiny.cli.command import com.github.ajalt.clikt.core.Abort import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.Context -import com.github.ajalt.clikt.core.terminal import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.mordant.input.InputReceiver -import com.github.ajalt.mordant.input.receiveKeyEvents -import com.github.ajalt.mordant.rendering.TextStyles -import com.github.ajalt.mordant.table.table +import com.github.ajalt.clikt.parameters.types.int import com.github.minigdx.tiny.cli.command.utils.ColorUtils import com.github.minigdx.tiny.cli.config.GameParameters import com.github.minigdx.tiny.cli.config.GameParametersV1 import java.io.File class UpdateCommand : CliktCommand(name = "update") { - val gameDirectory by option("-d", "--directory", help = "The directory containing your game to be updated.") + private val gameDirectory by option("-d", "--directory", help = "The directory containing your game to be updated.") .file(mustExist = true, canBeDir = true, canBeFile = false) .default(File(".")) - override fun help(context: Context) = "Interactively view and update game parameters." + private val newZoom by option("--zoom", help = "Set the game zoom level (1-8).") + .int() - private var currentParameters: GameParametersV1? = null - private var selectedIndex = 0 - private val editableParameters = mutableListOf() + private val hideMouseCursor by option("--hide-cursor", help = "Hide the system mouse cursor.") + .flag() + + private val showMouseCursor by option("--show-cursor", help = "Show the system mouse cursor.") + .flag() + + private val entryPoint by option("--entry-point", help = "Set the entry point script (moved to first in scripts list).") + + override fun help(context: Context) = "View and update game parameters." override fun run() { val configFile = gameDirectory.resolve("_tiny.json") if (!configFile.exists()) { - echo("❌ No _tiny.json found in ${gameDirectory.absolutePath}! Can't update parameters without it.") + echo("❌ No _tiny.json found in ${gameDirectory.absolutePath}") throw Abort() } - try { - val gameParameters = GameParameters.read(configFile) - if (gameParameters !is GameParametersV1) { - echo("❌ Only GameParametersV1 is supported for updates.") - throw Abort() - } - - currentParameters = gameParameters - setupEditableParameters() - runInteractiveLoop(configFile) + val gameParameters = try { + GameParameters.read(configFile) } catch (e: Exception) { echo("❌ Error reading _tiny.json: ${e.message}") throw Abort() } - } - - private fun setupEditableParameters() { - val params = currentParameters ?: return - editableParameters.clear() - - // Add basic parameters - editableParameters.add(EditableParameter("name", params.name, false)) - editableParameters.add( - EditableParameter( - "resolution", - "${params.resolution.width}x${params.resolution.height}", - false, - ), - ) - editableParameters.add(EditableParameter("sprites", "${params.sprites.width}x${params.sprites.height}", false)) - editableParameters.add(EditableParameter("zoom", params.zoom.toString(), true)) - editableParameters.add(EditableParameter("palette", ColorUtils.formatCurrentPaletteDisplay(params.colors, maxColors = 16), false)) - editableParameters.add(EditableParameter("scripts", params.scripts.joinToString(", "), false)) - editableParameters.add(EditableParameter("spritesheets", params.spritesheets.joinToString(", "), false)) - editableParameters.add(EditableParameter("levels", params.levels.joinToString(", "), false)) - editableParameters.add(EditableParameter("sounds", listOfNotNull(params.sound).joinToString(", "), false)) - editableParameters.add(EditableParameter("hideMouseCursor", if (params.hideMouseCursor) "Yes" else "No", true)) - } - - private fun runInteractiveLoop(configFile: File) { - echo("🎮 Interactive Game Parameter Editor") - echo("Use ↑/↓ arrow keys to navigate, Enter to toggle values, 'q' to quit and save") - echo() - displayParameters() - currentContext.terminal.receiveKeyEvents { event -> - val next = when (event.key) { - "ArrowUp" -> { - selectedIndex = (selectedIndex - 1).coerceAtLeast(0) - true - } - - "ArrowDown" -> { - selectedIndex = (selectedIndex + 1).coerceAtMost(editableParameters.lastIndex) - true - } - - "Enter" -> { - toggleParameter() - true - } - - "q" -> { - saveAndExit(configFile) - false - } - else -> true - } - if (next) { - displayParameters() - InputReceiver.Status.Continue - } else { - InputReceiver.Status.Finished - } + if (gameParameters !is GameParametersV1) { + echo("❌ Only V1 game configuration is supported.") + throw Abort() } - } - private fun displayParameters() { - currentContext.terminal.cursor.move { - clearScreen() + val hasUpdates = newZoom != null || hideMouseCursor || showMouseCursor || entryPoint != null + if (hasUpdates) { + applyUpdates(gameParameters, configFile) + } else { + displayParameters(gameParameters) } + } - echo("🎮 Game Parameters for: ${currentParameters?.name}") - echo() + private fun applyUpdates( + params: GameParametersV1, + configFile: File, + ) { + var updated = params - val table = table { - header { - row("Parameter", "Value", "Editable") - } - body { - editableParameters.forEachIndexed { index, param -> - val isSelected = index == selectedIndex - val paramName = if (isSelected) TextStyles.bold(param.name) else param.name - val paramValue = if (isSelected) TextStyles.bold(param.value) else param.value - val editable = if (param.isEditable) "✓" else "✗" - - row(paramName, paramValue, editable) - } + newZoom?.let { zoom -> + if (zoom !in 1..8) { + echo("❌ Zoom must be between 1 and 8.") + throw Abort() } + updated = updated.copy(zoom = zoom) + echo("✅ Zoom updated to $zoom") } - echo(table) - echo() - echo("Selected: ${editableParameters[selectedIndex].name}") - if (editableParameters[selectedIndex].isEditable) { - echo("Press Enter to toggle this value") + if (hideMouseCursor) { + updated = updated.copy(hideMouseCursor = true) + echo("✅ Mouse cursor hidden") + } else if (showMouseCursor) { + updated = updated.copy(hideMouseCursor = false) + echo("✅ Mouse cursor visible") } - echo("Press 'q' to quit and save changes") - } - - private fun toggleParameter() { - val param = editableParameters[selectedIndex] - if (!param.isEditable) { - echo("⚠️ This parameter is not editable") - return - } - - when (param.name) { - "hideMouseCursor" -> { - val currentParams = currentParameters ?: return - val newValue = !currentParams.hideMouseCursor - currentParameters = currentParams.copy(hideMouseCursor = newValue) - param.value = if (newValue) "Yes" else "No" - echo("✅ ${param.name} toggled to: ${param.value}") - } - "zoom" -> { - val currentParams = currentParameters ?: return - val newValue = ((currentParams.zoom + 1) % 9).coerceIn(1, 8) - param.value = newValue.toString() - currentParameters = currentParams.copy(zoom = newValue) + entryPoint?.let { script -> + if (script !in updated.scripts) { + echo("❌ Script '$script' not found. Available scripts: ${updated.scripts.joinToString(", ")}") + throw Abort() } + updated = updated.setEntryPoint(script) + echo("✅ Entry point set to $script") } - } - private fun saveAndExit(configFile: File) { try { - currentParameters?.write(configFile) - echo("✅ Parameters saved successfully!") + updated.write(configFile) } catch (e: Exception) { echo("❌ Error saving parameters: ${e.message}") + throw Abort() } + + echo() + displayParameters(updated) } - private data class EditableParameter( - val name: String, - var value: String, - val isEditable: Boolean, - ) + private fun displayParameters(params: GameParametersV1) { + echo("🎮 ${params.name}") + echo() + echo("🖥 Resolution: ${params.resolution.width}x${params.resolution.height}") + echo("📐 Sprites: ${params.sprites.width}x${params.sprites.height}") + echo("🔍 Zoom: ${params.zoom}") + echo("🎨 Palette: ${ColorUtils.formatCurrentPaletteDisplay(params.colors, maxColors = 16)}") + if (params.scripts.isNotEmpty()) { + echo("🚀 Entry point: ${params.scripts.first()}") + } + echo("📝 Scripts: ${params.scripts.joinToString(", ").ifEmpty { "none" }}") + echo("🖼️ Spritesheets: ${params.spritesheets.joinToString(", ").ifEmpty { "none" }}") + echo("🗺️ Levels: ${params.levels.joinToString(", ").ifEmpty { "none" }}") + echo("🔊 Sounds: ${listOfNotNull(params.sound).joinToString(", ").ifEmpty { "none" }}") + val fontsDisplay = if (params.fonts.isEmpty()) { + "none" + } else { + params.fonts.joinToString(", ") { font -> + val banks = font.banks.joinToString("+") { bank -> + "${bank.name}(${bank.width}x${bank.height})" + } + "${font.name} [$banks]" + } + } + echo("🔤 Fonts: $fontsDisplay") + echo("🖱️ Hide mouse cursor: ${if (params.hideMouseCursor) "yes" else "no"}") + } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/ColorUtils.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/ColorUtils.kt index 57864af7..f56e67f9 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/ColorUtils.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/ColorUtils.kt @@ -1,8 +1,29 @@ package com.github.minigdx.tiny.cli.command.utils import com.github.ajalt.mordant.rendering.TextColors +import java.awt.Color object ColorUtils { + fun parseColor(colorString: String): Color { + val hex = colorString.removePrefix("#") + return when (hex.length) { + 6 -> { + val r = hex.substring(0, 2).toInt(16) + val g = hex.substring(2, 4).toInt(16) + val b = hex.substring(4, 6).toInt(16) + Color(r, g, b) + } + 8 -> { + val r = hex.substring(0, 2).toInt(16) + val g = hex.substring(2, 4).toInt(16) + val b = hex.substring(4, 6).toInt(16) + val a = hex.substring(6, 8).toInt(16) + Color(r, g, b, a) + } + else -> Color.BLACK + } + } + fun brightness(hexColor: String): Float { // Remove the '#' prefix val colorWithoutHash = hexColor.removePrefix("#") diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/FontAnalyzer.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/FontAnalyzer.kt new file mode 100644 index 00000000..0a56c268 --- /dev/null +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/FontAnalyzer.kt @@ -0,0 +1,346 @@ +package com.github.minigdx.tiny.cli.command.utils + +import com.github.minigdx.tiny.engine.GameConfigFont +import com.github.minigdx.tiny.engine.GameConfigFontBank +import java.awt.image.BufferedImage +import java.io.File + +data class BoundingBox(val minX: Int, val minY: Int, val maxX: Int, val maxY: Int) { + val width get() = maxX - minX + 1 + val height get() = maxY - minY + 1 +} + +data class FontDetectionResult( + val offsetX: Int, + val offsetY: Int, + val cellWidth: Int, + val cellHeight: Int, + val offsetDetected: Boolean, + val sizeDetected: Boolean, +) + +object FontAnalyzer { + /** + * Parse a size string like "8x12" or "8" into a width/height pair. + */ + fun parseSize(size: String): Pair { + val parts = size.split("x", "X") + return if (parts.size == 2) { + parts[0].trim().toInt() to parts[1].trim().toInt() + } else { + val s = parts[0].trim().toInt() + s to s + } + } + + /** + * Parse an offset string like "8,12" or "8x12" into an x/y pair. + */ + fun parseOffset(offset: String): Pair { + val parts = offset.split(",", "x", "X") + return if (parts.size == 2) { + parts[0].trim().toInt() to parts[1].trim().toInt() + } else { + val s = parts[0].trim().toInt() + s to s + } + } + + /** + * Derive a font name from a file path. + * "fonts/big.png" → "big" + */ + fun deriveFontName(filePath: String): String { + return File(filePath).nameWithoutExtension + } + + /** + * Read a PNG image using ImageIO. + */ + fun readImage( + gameDirectory: File, + filePath: String, + ): BufferedImage { + val file = gameDirectory.resolve(filePath) + return javax.imageio.ImageIO.read(file) + ?: throw IllegalArgumentException("Cannot read image: $filePath") + } + + /** + * Read PNG image dimensions using ImageIO. + */ + fun readImageDimensions( + gameDirectory: File, + filePath: String, + ): Pair { + val image = readImage(gameDirectory, filePath) + return image.width to image.height + } + + /** + * Split a chars string into rows based on image width and character width. + * Given image width 64px, char width 8px → 8 chars/row. + * "abcdefghijklmnop" becomes ["abcdefgh", "ijklmnop"]. + */ + fun splitCharsIntoRows( + imageWidth: Int, + charWidth: Int, + chars: String, + ): List { + val charsPerRow = imageWidth / charWidth + return chars.chunked(charsPerRow) + } + + /** + * Detect the bounding box of non-transparent pixels in an image. + * Returns null if the image is fully transparent. + */ + fun detectBoundingBox(image: BufferedImage): BoundingBox? { + var minX = image.width + var minY = image.height + var maxX = -1 + var maxY = -1 + + for (y in 0 until image.height) { + for (x in 0 until image.width) { + val alpha = (image.getRGB(x, y) ushr 24) and 0xFF + if (alpha > 0) { + if (x < minX) minX = x + if (x > maxX) maxX = x + if (y < minY) minY = y + if (y > maxY) maxY = y + } + } + } + + return if (maxX < 0) null else BoundingBox(minX, minY, maxX, maxY) + } + + /** + * Detect cell size by analyzing gaps (fully transparent columns/rows) within the bounding box. + * Returns the cell width and height, or null if detection fails. + */ + fun detectCellSize( + image: BufferedImage, + box: BoundingBox, + totalChars: Int, + ): Pair? { + val cellWidth = detectCellDimension(image, box, isHorizontal = true, totalChars) + val cellHeight = detectCellDimension(image, box, isHorizontal = false, totalChars) + + if (cellWidth != null && cellHeight != null) { + return cellWidth to cellHeight + } + + // Fallback: try divisor-based detection + return detectCellSizeByDivisors(box, totalChars) + } + + private fun detectCellDimension( + image: BufferedImage, + box: BoundingBox, + isHorizontal: Boolean, + totalChars: Int, + ): Int? { + val size = if (isHorizontal) box.width else box.height + + // For each line (column if horizontal, row if vertical), check if it's fully transparent + val isTransparent = BooleanArray(size) { i -> + val pos = i + if (isHorizontal) box.minX else box.minY + isLineTransparent(image, box, pos, isHorizontal) + } + + // Find content bands (consecutive non-transparent lines) + val bandWidths = mutableListOf() + var i = 0 + while (i < size) { + if (!isTransparent[i]) { + val start = i + while (i < size && !isTransparent[i]) i++ + bandWidths.add(i - start) + } else { + i++ + } + } + + if (bandWidths.isEmpty()) return null + + // Check if the first band width repeats consistently (allowing the last band to be smaller) + val candidateWidth = bandWidths.first() + val consistent = bandWidths.dropLast(1).all { it == candidateWidth } && + bandWidths.last() <= candidateWidth + + if (!consistent || candidateWidth <= 0) return null + + // The cell size includes the gap: find the gap width after the first band + val firstBandEnd = run { + var pos = 0 + // skip to first content + while (pos < size && isTransparent[pos]) pos++ + // skip content + while (pos < size && !isTransparent[pos]) pos++ + pos + } + var gapWidth = 0 + var gapPos = firstBandEnd + while (gapPos < size && isTransparent[gapPos]) { + gapWidth++ + gapPos++ + } + + // If there's only one band, cell size = content width (no gap info) + return if (bandWidths.size == 1) { + // Can't determine cell size from a single band, try divisors instead + null + } else { + candidateWidth + gapWidth + } + } + + private fun isLineTransparent( + image: BufferedImage, + box: BoundingBox, + pos: Int, + isHorizontal: Boolean, + ): Boolean { + val start = if (isHorizontal) box.minY else box.minX + val end = if (isHorizontal) box.maxY else box.maxX + + for (i in start..end) { + val x = if (isHorizontal) pos else i + val y = if (isHorizontal) i else pos + val alpha = (image.getRGB(x, y) ushr 24) and 0xFF + if (alpha > 0) return false + } + return true + } + + private fun detectCellSizeByDivisors( + box: BoundingBox, + totalChars: Int, + ): Pair? { + val widthDivisors = (1..box.width).filter { box.width % it == 0 } + val heightDivisors = (1..box.height).filter { box.height % it == 0 } + + var bestCellWidth = 0 + var bestCellHeight = 0 + + for (cw in widthDivisors) { + for (ch in heightDivisors) { + val cols = box.width / cw + val rows = box.height / ch + if (cols * rows >= totalChars) { + val ratio = maxOf(cw, ch).toFloat() / maxOf(1, minOf(cw, ch)).toFloat() + if (ratio <= 3f && cw * ch > bestCellWidth * bestCellHeight) { + bestCellWidth = cw + bestCellHeight = ch + } + } + } + } + + return if (bestCellWidth > 0 && bestCellHeight > 0) { + bestCellWidth to bestCellHeight + } else { + null + } + } + + /** + * Auto-detect font grid parameters from the image. + * Uses explicit values when provided, auto-detects when not. + * Returns null if size detection fails entirely. + */ + fun autoDetect( + image: BufferedImage, + explicitSize: Pair?, + explicitOffset: Pair?, + totalChars: Int, + ): FontDetectionResult? { + if (explicitSize != null && explicitOffset != null) { + return FontDetectionResult( + offsetX = explicitOffset.first, + offsetY = explicitOffset.second, + cellWidth = explicitSize.first, + cellHeight = explicitSize.second, + offsetDetected = false, + sizeDetected = false, + ) + } + + val box = detectBoundingBox(image) + + val offsetX: Int + val offsetY: Int + val offsetDetected: Boolean + + if (explicitOffset != null) { + offsetX = explicitOffset.first + offsetY = explicitOffset.second + offsetDetected = false + } else if (box != null) { + offsetX = box.minX + offsetY = box.minY + offsetDetected = true + } else { + offsetX = 0 + offsetY = 0 + offsetDetected = true + } + + if (explicitSize != null) { + return FontDetectionResult( + offsetX = offsetX, + offsetY = offsetY, + cellWidth = explicitSize.first, + cellHeight = explicitSize.second, + offsetDetected = offsetDetected, + sizeDetected = false, + ) + } + + // Auto-detect size + val effectiveBox = box ?: return null + val cellSize = detectCellSize(image, effectiveBox, totalChars) ?: return null + + return FontDetectionResult( + offsetX = offsetX, + offsetY = offsetY, + cellWidth = cellSize.first, + cellHeight = cellSize.second, + offsetDetected = offsetDetected, + sizeDetected = true, + ) + } + + /** + * Build a GameConfigFont from the given parameters. + */ + fun buildFontConfig( + fontName: String, + spritesheet: String, + charWidth: Int, + charHeight: Int, + characters: List, + bankName: String = "default", + offsetX: Int = 0, + offsetY: Int = 0, + spaceWidth: Int? = null, + ): GameConfigFont { + return GameConfigFont( + name = fontName, + spritesheet = spritesheet, + spaceWidth = spaceWidth, + banks = listOf( + GameConfigFontBank( + name = bankName, + width = charWidth, + height = charHeight, + characters = characters, + x = offsetX, + y = offsetY, + ), + ), + ) + } +} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/IconConverter.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/IconConverter.kt new file mode 100644 index 00000000..9ceb77bf --- /dev/null +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/IconConverter.kt @@ -0,0 +1,91 @@ +package com.github.minigdx.tiny.cli.command.utils + +import java.io.ByteArrayOutputStream +import java.io.File + +object IconConverter { + /** + * Convert a PNG icon file to Windows ICO format. + * + * Uses the ICO format with an embedded PNG image, which is supported + * by Windows Vista and later (ICO with PNG payload). + */ + fun convertToIco( + pngFile: File, + outputFile: File, + ): File? { + return try { + val pngBytes = pngFile.readBytes() + val image = javax.imageio.ImageIO.read(pngFile) + val width = image.width + val height = image.height + + val ico = ByteArrayOutputStream() + + // ICONDIR header (6 bytes) + ico.write(shortToLE(0)) // Reserved + ico.write(shortToLE(1)) // Type: 1 = ICO + ico.write(shortToLE(1)) // Image count: 1 + + // ICONDIRENTRY (16 bytes) + ico.write(if (width >= 256) 0 else width) // Width (0 means 256) + ico.write(if (height >= 256) 0 else height) // Height (0 means 256) + ico.write(0) // Color palette count + ico.write(0) // Reserved + ico.write(shortToLE(1)) // Color planes + ico.write(shortToLE(32)) // Bits per pixel + ico.write(intToLE(pngBytes.size)) // Size of PNG data + ico.write(intToLE(22)) // Offset to PNG data (6 + 16) + + // PNG image data + ico.write(pngBytes) + + outputFile.writeBytes(ico.toByteArray()) + outputFile + } catch (e: Exception) { + null + } + } + + /** + * Convert a PNG icon file to macOS ICNS format using the `sips` tool. + * + * Only available on macOS where `sips` is a built-in system tool. + */ + fun convertToIcns( + pngFile: File, + outputFile: File, + ): File? { + return try { + val process = ProcessBuilder( + "sips", + "-s", + "format", + "icns", + pngFile.absolutePath, + "--out", + outputFile.absolutePath, + ).start() + val exitCode = process.waitFor() + if (exitCode == 0) outputFile else null + } catch (e: Exception) { + null + } + } + + private fun shortToLE(value: Int): ByteArray { + return byteArrayOf( + (value and 0xFF).toByte(), + ((value shr 8) and 0xFF).toByte(), + ) + } + + private fun intToLE(value: Int): ByteArray { + return byteArrayOf( + (value and 0xFF).toByte(), + ((value shr 8) and 0xFF).toByte(), + ((value shr 16) and 0xFF).toByte(), + ((value shr 24) and 0xFF).toByte(), + ) + } +} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/IconImageGenerator.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/IconImageGenerator.kt new file mode 100644 index 00000000..4aac425a --- /dev/null +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/IconImageGenerator.kt @@ -0,0 +1,85 @@ +package com.github.minigdx.tiny.cli.command.utils + +import java.awt.Font +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO +import kotlin.math.abs + +object IconImageGenerator { + private const val ICON_SIZE = 256 + private const val GRID_SIZE = 8 + private const val CELL_SIZE = ICON_SIZE / GRID_SIZE + + /** + * Generate a game icon as a 256x256 PNG using the game's color palette. + * + * The icon is a mosaic of palette colors arranged in a radial brightness pattern + * (darker colors on outer edges, lighter colors toward center) with the game name's + * first letter rendered in the center. + */ + fun generateIcon( + gameDirectory: File, + colors: List, + gameName: String, + ): File { + val image = BufferedImage(ICON_SIZE, ICON_SIZE, BufferedImage.TYPE_INT_ARGB) + val graphics = image.createGraphics() + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + + val sortedColors = colors.sortedBy { ColorUtils.brightness(it) } + + fillMosaic(graphics, sortedColors) + drawCenterLetter(graphics, gameName, sortedColors) + + graphics.dispose() + + val iconFile = gameDirectory.resolve("icon.png") + ImageIO.write(image, "PNG", iconFile) + return iconFile + } + + private fun fillMosaic( + graphics: java.awt.Graphics2D, + sortedColors: List, + ) { + if (sortedColors.isEmpty()) return + + val center = (GRID_SIZE - 1) / 2.0 + for (row in 0 until GRID_SIZE) { + for (col in 0 until GRID_SIZE) { + val distFromCenter = maxOf( + abs(row - center), + abs(col - center), + ) + val normalizedDist = distFromCenter / center + val colorIndex = ((1.0 - normalizedDist) * (sortedColors.size - 1)) + .toInt() + .coerceIn(0, sortedColors.lastIndex) + + graphics.color = ColorUtils.parseColor(sortedColors[colorIndex]) + graphics.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE) + } + } + } + + private fun drawCenterLetter( + graphics: java.awt.Graphics2D, + gameName: String, + sortedColors: List, + ) { + if (sortedColors.isEmpty()) return + + val letter = gameName.firstOrNull()?.uppercase() ?: "T" + + // Use the darkest color for the letter on the lightest center background + graphics.color = ColorUtils.parseColor(sortedColors.first()) + graphics.font = Font(Font.MONOSPACED, Font.BOLD, 96) + + val metrics = graphics.fontMetrics + val x = (ICON_SIZE - metrics.stringWidth(letter)) / 2 + val y = (ICON_SIZE - metrics.height) / 2 + metrics.ascent + graphics.drawString(letter, x, y) + } +} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/JsonHelpFormatter.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/JsonHelpFormatter.kt new file mode 100644 index 00000000..e0262e78 --- /dev/null +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/JsonHelpFormatter.kt @@ -0,0 +1,79 @@ +package com.github.minigdx.tiny.cli.command.utils + +import com.github.ajalt.clikt.core.UsageError +import com.github.ajalt.clikt.output.HelpFormatter +import com.github.ajalt.clikt.output.HelpFormatter.ParameterHelp +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +/** + * Formats CLI command help as a JSON object. + * + * Each call to [formatHelp] returns a JSON string representing a single command + * with its name, description, usage, options, and arguments. + */ +object JsonHelpFormatter : HelpFormatter { + override fun formatHelp( + error: UsageError?, + prolog: String, + epilog: String, + parameters: List, + programName: String, + ): String { + val options = parameters.filterIsInstance() + val arguments = parameters.filterIsInstance() + + val command = buildJsonObject { + put("name", programName) + put("description", prolog) + put("usage", buildUsage(programName, options, arguments)) + put( + "options", + buildJsonArray { + options.forEach { option -> + add( + buildJsonObject { + put( + "names", + buildJsonArray { + option.names.forEach { add(JsonPrimitive(it)) } + }, + ) + put("help", option.help) + }, + ) + } + }, + ) + put( + "arguments", + buildJsonArray { + arguments.forEach { arg -> + add( + buildJsonObject { + put("name", arg.name) + put("help", arg.help) + }, + ) + } + }, + ) + } + return Json.encodeToString(JsonElement.serializer(), command) + } + + private fun buildUsage( + programName: String, + options: List, + arguments: List, + ): String { + val parts = mutableListOf("tiny-cli", programName) + options.firstOrNull()?.names?.firstOrNull()?.let { parts.add("$it=") } + arguments.firstOrNull()?.name?.lowercase()?.let { parts.add("<$it>") } + return parts.joinToString(" ") + } +} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/PaletteImageGenerator.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/PaletteImageGenerator.kt index c8c7e4b3..8ddc7ed0 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/PaletteImageGenerator.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/PaletteImageGenerator.kt @@ -40,7 +40,7 @@ object PaletteImageGenerator { val color = if (index == 0) { Color(0, 0, 0, 0) // Transparent } else { - parseColor(colorString) + ColorUtils.parseColor(colorString) } // Fill cell background @@ -118,26 +118,6 @@ object PaletteImageGenerator { } } - private fun parseColor(colorString: String): Color { - val hex = colorString.removePrefix("#") - return when (hex.length) { - 6 -> { - val r = hex.substring(0, 2).toInt(16) - val g = hex.substring(2, 4).toInt(16) - val b = hex.substring(4, 6).toInt(16) - Color(r, g, b) - } - 8 -> { - val r = hex.substring(0, 2).toInt(16) - val g = hex.substring(2, 4).toInt(16) - val b = hex.substring(4, 6).toInt(16) - val a = hex.substring(6, 8).toInt(16) - Color(r, g, b, a) - } - else -> Color.BLACK - } - } - private fun findAvailableFilename( directory: File, baseName: String, diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/TtfConverter.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/TtfConverter.kt new file mode 100644 index 00000000..9ed5e9c1 --- /dev/null +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/command/utils/TtfConverter.kt @@ -0,0 +1,171 @@ +package com.github.minigdx.tiny.cli.command.utils + +import java.awt.Color +import java.awt.Font +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO +import kotlin.math.ceil +import kotlin.math.sqrt + +/** + * Converts a TTF font file into a PNG spritesheet suitable for the Tiny engine. + * Characters are rendered as white on transparent background in a grid layout. + */ +object TtfConverter { + /** Default printable ASCII characters (excludes space, handled by spaceWidth). */ + val DEFAULT_CHARS = ('!'..'~').joinToString("") + + /** + * Convert a TTF file to a PNG spritesheet. + * + * @param ttfFile The TTF font file + * @param outputFile The PNG output file + * @param chars Characters to include in the spritesheet + * @param targetHeight Target cell height in pixels (determines font size). Null for auto (16px). + * @return The cell width and height used + */ + fun convert( + ttfFile: File, + outputFile: File, + chars: String, + targetHeight: Int?, + ): ConversionResult { + val baseFont = Font.createFont(Font.TRUETYPE_FONT, ttfFile) + + // Use target height to derive font size, or default to 16px + val cellHeight = targetHeight ?: 16 + + // Find the font size that fits within the target cell height + val font = fitFontToHeight(baseFont, cellHeight) + + // Measure all characters to determine cell dimensions + val metrics = measureCharacters(font, chars) + val cellWidth = metrics.maxWidth + + // Calculate grid layout + val cols = ceil(sqrt(chars.length.toDouble())).toInt().coerceAtLeast(1) + val rows = ceil(chars.length.toDouble() / cols).toInt().coerceAtLeast(1) + + val imageWidth = cols * cellWidth + val imageHeight = rows * cellHeight + + // Render the spritesheet + val image = BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB) + val g2d = image.createGraphics() + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF) + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF) + g2d.font = font + g2d.color = Color.WHITE + + val fm = g2d.fontMetrics + val ascent = fm.ascent + + chars.forEachIndexed { index, char -> + val col = index % cols + val row = index / cols + val x = col * cellWidth + val y = row * cellHeight + ascent + g2d.drawString(char.toString(), x, y) + } + + g2d.dispose() + + // Post-process: threshold alpha to get crisp binary pixels. + // macOS may ignore anti-aliasing hints, so this ensures clean output. + thresholdAlpha(image) + + ImageIO.write(image, "PNG", outputFile) + + return ConversionResult( + cellWidth = cellWidth, + cellHeight = cellHeight, + cols = cols, + rows = rows, + spaceWidth = metrics.spaceWidth, + pngFile = outputFile, + ) + } + + private fun fitFontToHeight( + baseFont: Font, + targetHeight: Int, + ): Font { + // Start from target height as point size and adjust + var fontSize = targetHeight.toFloat() + var font = baseFont.deriveFont(fontSize) + + val testImage = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB) + val g2d = testImage.createGraphics() + + // Binary search for the right font size + var low = 1f + var high = targetHeight * 2f + var bestFont = font + + for (i in 0 until 20) { + fontSize = (low + high) / 2f + font = baseFont.deriveFont(fontSize) + val fm = g2d.getFontMetrics(font) + val h = fm.height + + if (h <= targetHeight) { + bestFont = font + low = fontSize + } else { + high = fontSize + } + } + + g2d.dispose() + return bestFont + } + + private fun measureCharacters( + font: Font, + chars: String, + ): CharMetrics { + val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB) + val g2d = image.createGraphics() + g2d.font = font + val fm = g2d.fontMetrics + + var maxWidth = 0 + chars.forEach { char -> + val w = fm.charWidth(char) + if (w > maxWidth) maxWidth = w + } + val spaceWidth = fm.charWidth(' ').coerceAtLeast(1) + + g2d.dispose() + return CharMetrics(maxWidth = maxWidth.coerceAtLeast(1), spaceWidth = spaceWidth) + } + + private fun thresholdAlpha(image: BufferedImage) { + for (y in 0 until image.height) { + for (x in 0 until image.width) { + val argb = image.getRGB(x, y) + val alpha = (argb ushr 24) and 0xFF + if (alpha > 127) { + // Fully opaque white + image.setRGB(x, y, 0xFFFFFFFF.toInt()) + } else { + // Fully transparent + image.setRGB(x, y, 0x00000000) + } + } + } + } + + data class CharMetrics(val maxWidth: Int, val spaceWidth: Int) + + data class ConversionResult( + val cellWidth: Int, + val cellHeight: Int, + val cols: Int, + val rows: Int, + val spaceWidth: Int, + val pngFile: File, + ) +} diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/config/GameParameters.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/config/GameParameters.kt index fb2eb936..66f2cafd 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/config/GameParameters.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/config/GameParameters.kt @@ -1,5 +1,7 @@ package com.github.minigdx.tiny.cli.config +import com.github.minigdx.tiny.engine.FontDescriptor +import com.github.minigdx.tiny.engine.GameConfigFont import com.github.minigdx.tiny.engine.GameOptions import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName @@ -37,8 +39,14 @@ sealed class GameParameters { abstract fun addSound(sound: String): GameParameters + abstract fun addFont(font: GameConfigFont): GameParameters + + abstract fun setBootScript(script: String): GameParameters + abstract fun setPalette(colors: List): GameParameters + abstract fun setIcon(icon: String): GameParameters + /** * Return the list of the user Lua script to load. */ @@ -101,6 +109,23 @@ data class GameParametersV1( * The game have to display it by itself if the mouse is required. */ val hideMouseCursor: Boolean = false, + /** + * Custom boot script to use instead of the default boot.lua. + * This script should exist in the game directory. + * When set, this script will be used as the first script to run. + */ + val bootScript: String? = null, + /** + * Path to the game icon image (PNG). + * Used as favicon for web export and application icon for desktop export. + * If null, the default generated icon.png is used if it exists. + */ + val icon: String? = null, + /** + * Custom fonts to be loaded. + * Each font has a name, a spritesheet image, and character bank definitions. + */ + val fonts: List = emptyList(), ) : GameParameters() { override fun toGameOptions(): GameOptions { return GameOptions( @@ -114,6 +139,11 @@ data class GameParametersV1( zoom = zoom, sound = sound, hideMouseCursor = hideMouseCursor, + bootScript = bootScript, + icon = icon, + fonts = fonts.map { font -> + FontDescriptor.fromConfig(font) + }, ) } @@ -149,4 +179,27 @@ data class GameParametersV1( override fun setPalette(colors: List): GameParameters { return copy(colors = colors) } + + override fun setIcon(icon: String): GameParameters { + return copy(icon = icon) + } + + override fun addFont(font: GameConfigFont): GameParameters { + val existing = fonts.find { it.name == font.name } + return if (existing != null) { + val merged = existing.copy(banks = existing.banks + font.banks) + copy(fonts = fonts.map { if (it.name == font.name) merged else it }) + } else { + copy(fonts = fonts + font) + } + } + + override fun setBootScript(script: String): GameParameters { + return copy(bootScript = script) + } + + fun setEntryPoint(scriptName: String): GameParametersV1 { + val reordered = listOf(scriptName) + scripts.filter { it != scriptName } + return copy(scripts = reordered) + } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/DebuggerExecutionListener.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/DebuggerExecutionListener.kt index 1d94eca0..8934612a 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/DebuggerExecutionListener.kt +++ b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/DebuggerExecutionListener.kt @@ -47,28 +47,41 @@ class DebuggerExecutionListener( private val blocker = CoroutineBlocker() - private var advanceByStep: Boolean = false + private var resumeMode: ResumeMode = ResumeMode.RESUME - // Current line when the execution resume. + // Current line when the execution resumed. // So when the step advance of one step, it's to another line. private var advanceByStepCurrentLine: Int = -1 + // Script name at the time the execution was resumed (used for STEP_OVER). + private var advanceByStepCurrentScript: String = "" + + // Call depth at the time the execution was resumed (used for STEP_OVER). + private var advanceByStepCallDepth: Int = 0 + + // Current call depth counter (incremented on call, decremented on return). + private var callDepth: Int = 0 + init { CoroutineScope(Dispatchers.IO).launch { for (debugRemoteCommand in debugCommandReceiver) { when (debugRemoteCommand) { is ToggleBreakpoint -> toggleBreakpoint(debugRemoteCommand) + is DeleteBreakpoint -> deleteBreakpoint(debugRemoteCommand) is ResumeExecution -> resumeExecution(debugRemoteCommand) Disconnect -> disconnect() RequestBreakpoints -> sendCurrentBreakpoints() + is EvaluateExpression -> evaluateExpression(debugRemoteCommand) } } } } private fun resumeExecution(debugRemoteCommand: ResumeExecution) { - advanceByStep = debugRemoteCommand.advanceByStep + resumeMode = debugRemoteCommand.mode advanceByStepCurrentLine = currentExecutionPoint.line + advanceByStepCurrentScript = currentExecutionPoint.script + advanceByStepCallDepth = callDepth blocker.unblock() } @@ -114,6 +127,11 @@ class DebuggerExecutionListener( ) } + private fun deleteBreakpoint(command: DeleteBreakpoint) { + val executionPoint = ExecutionPoint(command.script, command.line) + breakpoints = breakpoints - executionPoint + } + private fun toggleBreakpoint(debugRemoteCommand: ToggleBreakpoint) { val executionPoint = ExecutionPoint(debugRemoteCommand.script, debugRemoteCommand.line) val storedBreakpoint = breakpoints[executionPoint] @@ -209,12 +227,14 @@ class DebuggerExecutionListener( varargs: Varargs, stack: Array, ) { + callDepth++ callstack(globals.running).onCall(c, varargs, stack) onCall(c) } override fun onCall(f: LuaFunction) { + callDepth++ callstack(globals.running).onCall(f) (f as? LuaClosure)?.run { onCall(this) } } @@ -254,8 +274,21 @@ class DebuggerExecutionListener( currentExecutionPoint.pc = pc currentExecutionPoint.line = line - if (advanceByStep && line != advanceByStepCurrentLine) { - pauseExecution(currentExecutionPoint.script, line) + when (resumeMode) { + ResumeMode.STEP_INTO -> if (line != advanceByStepCurrentLine) { + pauseExecution(currentExecutionPoint.script, line) + } + + ResumeMode.STEP_OVER -> if ( + callDepth <= advanceByStepCallDepth && + currentExecutionPoint.script == advanceByStepCurrentScript && + line != advanceByStepCurrentLine + ) { + pauseExecution(currentExecutionPoint.script, line) + } + + ResumeMode.RESUME -> { // no stepping pause + } } breakpoints.values.forEach { breakpoint -> @@ -287,13 +320,9 @@ class DebuggerExecutionListener( } /** - * Evaluates a Lua condition in the current execution context. + * Evaluates a Lua expression in the current execution context and returns the raw LuaValue. */ - private fun evaluateCondition( - condition: String, - scriptName: String, - line: Int, - ): Boolean { + private fun evaluateLuaExpression(expression: String): LuaValue { val frames = callstack(globals.running).getCallFrames() // Collect upvalues @@ -330,15 +359,54 @@ class DebuggerExecutionListener( } } - // Evaluate condition - appendLine("return ($condition)") + // Evaluate expression + appendLine("return ($expression)") } // Execute the script - val result = globals.load(script).call() - return result.toboolean() + return globals.load(script).call() + } + + /** + * Evaluates a Lua condition in the current execution context. + */ + private fun evaluateCondition( + condition: String, + scriptName: String, + line: Int, + ): Boolean = evaluateLuaExpression(condition).toboolean() + + /** + * Evaluates an arbitrary Lua expression and sends the result back to the debugger. + */ + private suspend fun evaluateExpression(cmd: EvaluateExpression) { + try { + val result = evaluateLuaExpression(cmd.expression) + val formatted = formatValue(result) + val resultStr = luaValueToDisplayString(formatted) + engineCommandSender.send(EvaluationResult(result = resultStr)) + } catch (e: Exception) { + engineCommandSender.send(EvaluationResult(result = "", error = e.message ?: "Unknown error")) + } } + /** + * Converts a formatted [com.github.minigdx.tiny.cli.debug.LuaValue] to a readable display string. + */ + private fun luaValueToDisplayString( + value: com.github.minigdx.tiny.cli.debug.LuaValue, + indent: String = "", + ): String = + when (value) { + is Primitive -> value.value + is Dictionary -> { + val entries = value.entries.entries.joinToString("\n") { (k, v) -> + "$indent $k = ${luaValueToDisplayString(v, "$indent ")}" + } + "{\n$entries\n$indent}" + } + } + /** * Converts a LuaValue to its string representation for script generation. */ @@ -367,6 +435,7 @@ class DebuggerExecutionListener( } "{${entries.joinToString(", ")}}" } + else -> "nil" // For functions and other complex types } } @@ -401,6 +470,7 @@ class DebuggerExecutionListener( } override fun onReturn() { + callDepth-- callstack(globals.running).onReturn() val frame = callstack(globals.running).getCurrentFrame() @@ -525,6 +595,8 @@ class DebuggerExecutionListener( .map { it.varname } return locvars.zip(stack!!) { name, value -> LuaValue.varargsOf(name, value) } + // Remove internal local variables "(for generator)", "(for state)", "(for control)" + .filter { !it.arg(1).tojstring().startsWith("(") } } } } diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/ui/TinyDebuggerUI.kt b/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/ui/TinyDebuggerUI.kt deleted file mode 100644 index a49ae2b1..00000000 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/ui/TinyDebuggerUI.kt +++ /dev/null @@ -1,801 +0,0 @@ -package com.github.minigdx.tiny.cli.ui - -import com.github.minigdx.tiny.cli.config.GameParameters -import com.github.minigdx.tiny.cli.debug.BreakpointHit -import com.github.minigdx.tiny.cli.debug.CurrentBreakpoints -import com.github.minigdx.tiny.cli.debug.DebugRemoteCommand -import com.github.minigdx.tiny.cli.debug.Disconnect -import com.github.minigdx.tiny.cli.debug.EngineRemoteCommand -import com.github.minigdx.tiny.cli.debug.LuaValue -import com.github.minigdx.tiny.cli.debug.Reload -import com.github.minigdx.tiny.cli.debug.RequestBreakpoints -import com.github.minigdx.tiny.cli.debug.ResumeExecution -import com.github.minigdx.tiny.cli.debug.ToggleBreakpoint -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.launch -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea -import org.fife.ui.rsyntaxtextarea.SyntaxConstants -import org.fife.ui.rtextarea.Gutter -import org.fife.ui.rtextarea.IconRowEvent -import org.fife.ui.rtextarea.IconRowListener -import org.fife.ui.rtextarea.LineNumberList -import org.fife.ui.rtextarea.RTextArea -import org.fife.ui.rtextarea.RTextScrollPane -import java.awt.BorderLayout -import java.awt.Color -import java.awt.Component -import java.awt.Dimension -import java.awt.Point -import java.awt.event.MouseAdapter -import java.awt.event.MouseEvent -import java.awt.event.MouseListener -import java.awt.image.BufferedImage -import java.io.File -import javax.imageio.ImageIO -import javax.swing.Box -import javax.swing.BoxLayout -import javax.swing.Icon -import javax.swing.ImageIcon -import javax.swing.JButton -import javax.swing.JFrame -import javax.swing.JLabel -import javax.swing.JOptionPane -import javax.swing.JPanel -import javax.swing.JScrollPane -import javax.swing.JTabbedPane -import javax.swing.JTable -import javax.swing.JTree -import javax.swing.SwingUtilities -import javax.swing.table.DefaultTableModel -import javax.swing.text.BadLocationException -import javax.swing.text.DefaultHighlighter -import javax.swing.tree.DefaultMutableTreeNode -import javax.swing.tree.DefaultTreeModel - -/** - * [TinyDebuggerUI] is the main class of the debugger. - * - * It's a Swing application that will display: - * - the source code of the game - * - the local variables of the game when a breakpoint is hit - * - * The debugger can: - * - add/remove breakpoints - * - display the local variables - * - * The debugger is connected to the game engine via two channels: - * - [debugCommandSender] to send commands to the engine - * - [engineCommandReceiver] to receive commands from the engine - */ - -class TinyDebuggerUI( - private val debugCommandSender: SendChannel, - private val engineCommandReceiver: ReceiveChannel, - private var gameParameters: GameParameters, -) : JFrame("\uD83E\uDDF8 Tiny Debugger") { - private val tabbedPane = JTabbedPane() - - // Custom table model to store variable values - private val tableModel = object : DefaultTableModel(arrayOf("Name", "Value"), 0) { - override fun isCellEditable( - row: Int, - column: Int, - ): Boolean { - // Make all cells non-editable - return false - } - } - private val table = JTable(tableModel).apply { - setDefaultRenderer(Object::class.java, VariableCellRenderer()) - - // Add a mouse listener to handle clicks on dictionary cells - addMouseListener(object : MouseAdapter() { - override fun mouseClicked(e: MouseEvent) { - val row = rowAtPoint(e.point) - val column = columnAtPoint(e.point) - - if (row >= 0 && column == 1) { // Only for the value column - val name = getValueAt(row, 0) as String - val luaValue = variableValues[name] - - if (luaValue is LuaValue.Dictionary) { - showDictionaryDialog(name, luaValue) - } - } - } - }) - } - - private val textAreas: MutableMap = mutableMapOf() - - // Store variable values for rendering - private val variableValues: MutableMap = mutableMapOf() - - // Store breakpoint conditions for each script and line - private val breakpointConditions: MutableMap, String> = mutableMapOf() - - // Store active breakpoints for each script and line - private val activeBreakpoints: MutableSet> = mutableSetOf() - - // Track condition error states to prevent redundant updates - private val breakpointConditionErrors: MutableMap, String?> = mutableMapOf() - - private val io = CoroutineScope(Dispatchers.IO) - - init { - defaultCloseOperation = EXIT_ON_CLOSE - size = Dimension(800, 600) - contentPane.layout = BoxLayout(contentPane, BoxLayout.X_AXIS) - - add( - tabbedPane.apply { - preferredSize = Dimension(600, 600) - }, - ) - add( - JPanel(BorderLayout()).apply { - add(toolbar(), BorderLayout.PAGE_START) - add(JScrollPane(table), BorderLayout.CENTER) - }.apply { - preferredSize = Dimension(200, 600) - }, - ) - - io.launch { - val scriptsContent = - gameParameters.getAllScripts() - .map { it to File(it).readText() } - - SwingUtilities.invokeLater { - val breakpointIcon = - ImageIO.read(TinyDebuggerUI::class.java.getResource("/icons/flag_square.png")) - .let { recolorImage(it, LIGHT_RED) } - .getScaledInstance(16, 16, 0) - .let { ImageIcon(it) } - - scriptsContent.forEach { (scriptName, scriptContent) -> - addScriptTab(scriptName, scriptContent, breakpointIcon) - } - - // Request current breakpoints from the game engine - io.launch { - debugCommandSender.send(RequestBreakpoints) - } - } - - for (command in engineCommandReceiver) { - when (command) { - is BreakpointHit -> { - SwingUtilities.invokeLater { - val textArea = textAreas[command.script] - textArea?.setActiveLineRange(command.line - 1, command.line - 1) - textArea?.highlightLine(command.line, LIGHT_RED) - - tableModel.rowCount = 0 - variableValues.clear() - - command.locals.forEach { (name, value) -> - addValueToTable(name, value) - } - command.upValues.forEach { (name, value) -> - addValueToTable(name, value) - } - - // Update visual indicator with condition error information - updateBreakpointVisualIndicator( - command.script, - command.line, - conditionError = command.conditionError, - ) - } - } - - is CurrentBreakpoints -> { - SwingUtilities.invokeLater { - // Clear existing breakpoints in the UI - textAreas.values.forEach { textArea -> - val gutter = (textArea.parent.parent as? RTextScrollPane)?.gutter - gutter?.removeAllTrackingIcons() - } - - // Clear our tracking data structures - activeBreakpoints.clear() - breakpointConditions.clear() - breakpointConditionErrors.clear() - - // Add received breakpoints to the UI - command.breakpoints.forEach { breakpointInfo -> - val textArea = textAreas[breakpointInfo.script] - if (textArea != null && breakpointInfo.enabled) { - val gutter = (textArea.parent.parent as? RTextScrollPane)?.gutter - gutter?.toggleBookmark(breakpointInfo.line - 1) - - // Restore our tracking data - val breakpointKey = Pair(breakpointInfo.script, breakpointInfo.line) - activeBreakpoints.add(breakpointKey) - - // Restore the condition if it exists - breakpointInfo.condition?.let { condition -> - breakpointConditions[breakpointKey] = condition - } - - // Restore visual indicator for conditional breakpoints - updateBreakpointVisualIndicator(breakpointInfo.script, breakpointInfo.line) - } - } - } - } - - is Reload -> - SwingUtilities.invokeLater { - val textArea = textAreas[command.script]!! - textArea.text = File(command.script).readText() - } - } - } - } - } - - private fun toolbar(): Component { - val iconDisconnect = - ImageIO.read(TinyDebuggerUI::class.java.getResource("/icons/character_remove.png")) - .let { recolorImage(it, LIGHT_GREY) } - .getScaledInstance(24, 24, 0) - .let { ImageIcon(it) } - - val iconResume = - ImageIO.read(TinyDebuggerUI::class.java.getResource("/icons/pawn_right.png")) - .let { recolorImage(it, LIGHT_GREY) } - .getScaledInstance(24, 24, 0) - .let { ImageIcon(it) } - - val iconStep = - ImageIO.read(TinyDebuggerUI::class.java.getResource("/icons/pawn_skip.png")) - .let { recolorImage(it, LIGHT_GREY) } - .getScaledInstance(24, 24, 0) - .let { ImageIcon(it) } - - return JPanel().apply { - contentPane.layout = BoxLayout(contentPane, BoxLayout.X_AXIS) - add( - JButton(iconDisconnect).apply { - toolTipText = "Disconnect from the game" - preferredSize = Dimension(32, 32) - addActionListener { - io.launch { - debugCommandSender.send(Disconnect) - textAreas.values.forEach { - it.highlighter.removeAllHighlights() - } - } - } - }, - ) - add( - JButton(iconResume).apply { - toolTipText = "Resume execution until the next breakpoint" - preferredSize = Dimension(32, 32) - addActionListener { - io.launch { - debugCommandSender.send(ResumeExecution()) - textAreas.values.forEach { - it.highlighter.removeAllHighlights() - } - } - } - }, - ) - add( - JButton(iconStep).apply { - toolTipText = "Step over the current line" - preferredSize = Dimension(32, 32) - addActionListener { - io.launch { - // Goes to the next line - debugCommandSender.send(ResumeExecution(advanceByStep = true)) - textAreas.values.forEach { - it.highlighter.removeAllHighlights() - } - } - } - }, - ) - } - } - - private fun RSyntaxTextArea.highlightLine( - lineNumber: Int, - color: Color, - ) { - try { - // Convert line number to offset - val start = this.getLineStartOffset(lineNumber - 1) - val end = this.getLineEndOffset(lineNumber - 1) - - // Highlight the line - this.highlighter.addHighlight(start, end, DefaultHighlighter.DefaultHighlightPainter(color)) - - // Set the focus on that line - this.caretPosition = start - } catch (e: Exception) { - e.printStackTrace() - } - } - - private fun addScriptTab( - scriptName: String, - scriptContent: String, - bookmarkIcon: Icon, - ) { - val textArea = - RSyntaxTextArea(20, 60).apply { - syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_LUA - isCodeFoldingEnabled = true - isEditable = false - text = scriptContent - highlightCurrentLine = false - } - textAreas[scriptName] = textArea - - val scrollPane = RTextScrollPane(textArea) - scrollPane.gutter.bookmarkIcon = bookmarkIcon - scrollPane.gutter.isBookmarkingEnabled = true - scrollPane.gutter.addIconRowListener(GutterListener(scriptName)) - scrollPane.gutter.addLineNumberListener(LineNumberListener(scriptName, scrollPane)) - - val panel = - JPanel(BorderLayout()).apply { - add(scrollPane, BorderLayout.CENTER) - } - - tabbedPane.addTab(scriptName, panel) - } - - private fun Gutter.addLineNumberListener(mouseListener: MouseListener) { - val lineNumberList = Gutter::class.java.getDeclaredField("lineNumberList") - lineNumberList.isAccessible = true - val lineNumber = lineNumberList.get(this) as LineNumberList - lineNumber.addMouseListener(mouseListener) - } - - inner class LineNumberListener( - private val scriptName: String, - private val scrollPane: RTextScrollPane, - ) : MouseAdapter() { - override fun mouseClicked(e: MouseEvent) { - val l = viewToModelLine(scrollPane.textArea, e.point) - scrollPane.gutter.toggleBookmark(l) - - if (e.button == MouseEvent.BUTTON3) { // Right click - if (l >= 0) { - val breakpointKey = Pair(scriptName, l + 1) - if (activeBreakpoints.contains(breakpointKey)) { - // Right-clicked on an existing breakpoint - showConditionDialog(scriptName, l + 1) - } - } - } - } - - @Throws(BadLocationException::class) - private fun viewToModelLine( - textArea: RTextArea, - p: Point, - ): Int { - val offs: Int = textArea.viewToModel2D(p) - return if (offs > -1) textArea.getLineOfOffset(offs) else -1 - } - } - - inner class GutterListener(private val scriptName: String) : IconRowListener { - override fun bookmarkAdded(e: IconRowEvent) { - val line = e.line + 1 - val breakpointKey = Pair(scriptName, line) - activeBreakpoints.add(breakpointKey) - - val condition = breakpointConditions[breakpointKey] - io.launch { - debugCommandSender.send(ToggleBreakpoint(scriptName, line, true, condition)) - } - } - - override fun bookmarkRemoved(e: IconRowEvent) { - val line = e.line + 1 - val breakpointKey = Pair(scriptName, line) - activeBreakpoints.remove(breakpointKey) - breakpointConditions.remove(breakpointKey) - - io.launch { - debugCommandSender.send(ToggleBreakpoint(scriptName, line, false)) - } - } - } - - /** - * Shows a dialog to input/edit a condition for a breakpoint. - */ - private fun showConditionDialog( - scriptName: String, - line: Int, - ) { - val currentCondition = breakpointConditions[Pair(scriptName, line)] ?: "" - - val condition = JOptionPane.showInputDialog( - this, - "Enter Lua condition for breakpoint at line $line:\n(Leave empty to remove condition)", - "Conditional Breakpoint", - JOptionPane.QUESTION_MESSAGE, - null, - null, - currentCondition, - ) as String? - - if (condition != null) { - if (condition.trim().isEmpty()) { - // Remove condition - breakpointConditions.remove(Pair(scriptName, line)) - io.launch { - debugCommandSender.send(ToggleBreakpoint(scriptName, line, true, null)) - } - } else { - // Set/update condition - breakpointConditions[Pair(scriptName, line)] = condition.trim() - io.launch { - debugCommandSender.send(ToggleBreakpoint(scriptName, line, true, condition.trim())) - } - } - - // Update visual indicator - updateBreakpointVisualIndicator(scriptName, line) - } - } - - /** - * Updates the visual indicator for a breakpoint based on whether it has a condition. - */ - private fun updateBreakpointVisualIndicator( - scriptName: String, - line: Int, - condition: String? = null, - conditionError: String? = null, - ) { - val textArea = textAreas[scriptName] ?: return - val breakpointKey = Pair(scriptName, line) - val storedCondition = condition ?: breakpointConditions[breakpointKey] - - // Check if the condition error has changed to avoid redundant updates - val previousConditionError = breakpointConditionErrors[breakpointKey] - if (conditionError == previousConditionError && condition == null) { - // No change in condition error state, skip update - return - } - - // Update the stored condition error state - breakpointConditionErrors[breakpointKey] = conditionError - - // Remove any existing condition comments from this line - removeConditionComment(textArea, line) - - // Remove any existing background highlighting for condition errors - removeConditionErrorHighlight(textArea, line) - - if (storedCondition != null) { - if (conditionError != null) { - // Condition evaluation failed - use boom emoji and highlight background - addConditionComment(textArea, line, "💥 $storedCondition (error: $conditionError)") - highlightConditionError(textArea, line) - } else { - // Condition is valid - use bug emoji - addConditionComment(textArea, line, "🐛 $storedCondition") - } - } - } - - /** - * Adds a Lua comment with emoji and condition to the end of the specified line. - */ - private fun addConditionComment( - textArea: RSyntaxTextArea, - line: Int, - comment: String, - ) { - try { - val lineIndex = line - 1 // Convert to 0-based index - if (lineIndex < 0 || lineIndex >= textArea.lineCount) return - - val lineStart = textArea.getLineStartOffset(lineIndex) - val lineEnd = textArea.getLineEndOffset(lineIndex) - val lineText = textArea.getText(lineStart, lineEnd - lineStart) - - // Check if line already has our condition comment - if (lineText.contains("-- 🐛") || lineText.contains("-- 💥")) { - return // Comment already exists - } - - // Remove trailing newline if present - val cleanLineText = lineText.trimEnd('\n', '\r') - val newLineText = "$cleanLineText -- $comment\n" - - textArea.replaceRange(newLineText, lineStart, lineEnd) - } catch (e: BadLocationException) { - // Ignore if line doesn't exist - } - } - - /** - * Removes condition comments from the specified line. - */ - private fun removeConditionComment( - textArea: RSyntaxTextArea, - line: Int, - ) { - try { - val lineIndex = line - 1 // Convert to 0-based index - if (lineIndex < 0 || lineIndex >= textArea.lineCount) return - - val lineStart = textArea.getLineStartOffset(lineIndex) - val lineEnd = textArea.getLineEndOffset(lineIndex) - val lineText = textArea.getText(lineStart, lineEnd - lineStart) - - // Remove condition comments (both bug and boom emojis) - val cleanedText = lineText - .replace(Regex("\\s*-- 🐛[^\\n\\r]*"), "") - .replace(Regex("\\s*-- 💥[^\\n\\r]*"), "") - - if (cleanedText != lineText) { - textArea.replaceRange(cleanedText, lineStart, lineEnd) - } - } catch (e: BadLocationException) { - // Ignore if line doesn't exist - } - } - - /** - * Highlights the line with light yellow background for condition errors. - */ - private fun highlightConditionError( - textArea: RSyntaxTextArea, - line: Int, - ) { - try { - val lineIndex = line - 1 // Convert to 0-based index - if (lineIndex < 0 || lineIndex >= textArea.lineCount) return - - val lineStart = textArea.getLineStartOffset(lineIndex) - val lineEnd = textArea.getLineEndOffset(lineIndex) - - val highlighter = textArea.highlighter - val lightYellow = Color(255, 255, 224) // Light yellow background - highlighter.addHighlight(lineStart, lineEnd - 1, DefaultHighlighter.DefaultHighlightPainter(lightYellow)) - } catch (e: BadLocationException) { - // Ignore if line doesn't exist - } - } - - /** - * Removes condition error highlighting from the specified line. - */ - private fun removeConditionErrorHighlight( - textArea: RSyntaxTextArea, - line: Int, - ) { - try { - val lineIndex = line - 1 // Convert to 0-based index - if (lineIndex < 0 || lineIndex >= textArea.lineCount) return - - val lineStart = textArea.getLineStartOffset(lineIndex) - val lineEnd = textArea.getLineEndOffset(lineIndex) - - val highlighter = textArea.highlighter - val highlights = highlighter.highlights - - // Remove highlights that match our line range and are light yellow - highlights.forEach { highlight -> - if (highlight.startOffset >= lineStart && highlight.endOffset <= lineEnd) { - val painter = highlight.painter - if (painter is DefaultHighlighter.DefaultHighlightPainter) { - // Check if it's our light yellow highlight - val lightYellow = Color(255, 255, 224) - try { - highlighter.removeHighlight(highlight) - } catch (e: Exception) { - // Ignore removal errors - } - } - } - } - } catch (e: BadLocationException) { - // Ignore if line doesn't exist - } - } - - /** - * Creates a new BufferedImage by replacing pixels of a specific color (like white) - * in the source image with a target color, while preserving transparency. - * - * @param sourceImage The original BufferedImage. - * @param targetColor The Color object representing the new color. - * @return A new BufferedImage with the specified color replaced. - */ - private fun recolorImage( - sourceImage: BufferedImage, - targetColor: Color, - ): BufferedImage { - if (sourceImage.width <= 0 || sourceImage.height <= 0) { - throw IllegalArgumentException("Image size is invalid") - } - - val width = sourceImage.width - val height = sourceImage.height - - // Create a new image with the same dimensions and support for transparency (ARGB) - val newImage = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) - - // Iterate through each pixel - for (y in 0 until height) { - for (x in 0 until width) { - val originalPixelARGB = sourceImage.getRGB(x, y) // Get pixel data including alpha - val originalAlpha = (originalPixelARGB shr 24) and 0xFF - - // Check if the RGB portion matches the color to replace - // We compare ignoring the original alpha channel using a mask - if (originalAlpha == 0x00) { - newImage.setRGB(x, y, originalPixelARGB) - } else { - // Create new pixel value: (alpha << 24) | (red << 16) | (green << 8) | blue - val newPixelARGB = - (originalAlpha shl 24) or - (targetColor.red shl 16) or - (targetColor.green shl 8) or - targetColor.blue - newImage.setRGB(x, y, newPixelARGB) - } - } - } - - return newImage - } - - /** - * Custom cell renderer that can display either text or a tree structure for dictionaries. - */ - private inner class VariableCellRenderer : javax.swing.table.DefaultTableCellRenderer() { - override fun getTableCellRendererComponent( - table: JTable, - value: Any?, - isSelected: Boolean, - hasFocus: Boolean, - row: Int, - column: Int, - ): Component { - if (column == 0) { - return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) - } - - val name = table.getValueAt(row, 0) as String - val luaValue = variableValues[name] - - return when (luaValue) { - is LuaValue.Dictionary -> { - // Create a panel with a button and label - val panel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - background = if (isSelected) table.selectionBackground else table.background - } - - // Add the button with a mouse listener instead of action listener - val button = JButton("+").apply { - val dimension = Dimension(16, 16) - preferredSize = dimension - minimumSize = dimension - maximumSize = dimension - isFocusable = false // Prevent focus which can interfere with events - isRequestFocusEnabled = false - } - - // Add a mouse listener to handle clicks - button.addMouseListener(object : MouseAdapter() { - override fun mouseClicked(e: MouseEvent) { - showDictionaryDialog(name, luaValue) - } - }) - - panel.add(button) - panel.add(JLabel("Dictionary (${luaValue.entries.size} entries)")) - panel.add(Box.createHorizontalGlue()) - - panel - } - - is LuaValue.Primitive -> { - super.getTableCellRendererComponent(table, luaValue.value, isSelected, hasFocus, row, column) - } - - null -> { - super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column) - } - } - } - } - - /** - * Shows a dialog with a tree view of a dictionary. - */ - private fun showDictionaryDialog( - name: String, - dictionary: LuaValue.Dictionary, - ) { - // Use JDialog instead of JFrame to make it modal - val dialog = javax.swing.JDialog(this, "Dictionary: $name", true) - dialog.size = Dimension(400, 300) - dialog.layout = BorderLayout() - dialog.defaultCloseOperation = javax.swing.WindowConstants.DISPOSE_ON_CLOSE - - val root = DefaultMutableTreeNode(name) - populateTreeNode(root, dictionary) - - val tree = JTree(DefaultTreeModel(root)) - tree.isRootVisible = true - tree.showsRootHandles = true - - // Add a close button at the bottom - val closeButton = JButton("Close") - closeButton.addActionListener { dialog.dispose() } - - val buttonPanel = JPanel() - buttonPanel.add(closeButton) - - dialog.add(JScrollPane(tree), BorderLayout.CENTER) - dialog.add(buttonPanel, BorderLayout.SOUTH) - - // Center the dialog on the parent window - dialog.setLocationRelativeTo(this) - - // Make the dialog visible - dialog.isVisible = true - } - - /** - * Recursively populates a tree node with the entries from a dictionary. - */ - private fun populateTreeNode( - node: DefaultMutableTreeNode, - dictionary: LuaValue.Dictionary, - ) { - dictionary.entries.forEach { (key, value) -> - when (value) { - is LuaValue.Primitive -> { - val childNode = DefaultMutableTreeNode("$key: ${value.value}") - node.add(childNode) - } - - is LuaValue.Dictionary -> { - val childNode = DefaultMutableTreeNode(key) - node.add(childNode) - populateTreeNode(childNode, value) - } - } - } - } - - /** - * Adds a value to the table, handling both primitive values and dictionaries. - */ - private fun addValueToTable( - name: String, - value: LuaValue, - ) { - variableValues[name] = value - tableModel.addRow(arrayOf(name, "")) - } - - companion object { - private val LIGHT_RED = Color(255, 102, 102, 100) - private val LIGHT_GREY = Color(151, 151, 151, 100) - private val LIGHT_BLUE = Color(102, 153, 255, 100) // For conditional breakpoints - private val LIGHT_ORANGE = Color(255, 165, 0, 100) // For condition errors - } -} diff --git a/tiny-cli/src/main/resources/icons/CREDITS.txt b/tiny-cli/src/main/resources/icons/CREDITS.txt deleted file mode 100644 index 30f738e8..00000000 --- a/tiny-cli/src/main/resources/icons/CREDITS.txt +++ /dev/null @@ -1,21 +0,0 @@ -================================= -THIRD-PARTY ASSET CREDITS -================================= - -This application utilizes graphical assets, including icons, provided by third-party creators. -We deeply appreciate their contribution to the open/creative community. -Proper attribution for these assets is provided below. - ---------------------------------- -ICON CREDITS ---------------------------------- - -Item: icons located in the icons folder -Author/Creator: Kenney -Source URL: https://kenney.nl/assets/board-game-icons -License: Creative Commons CC0 -License URL: https://creativecommons.org/publicdomain/zero/1.0/ - -================================= - -Please consult the specific license URL(s) listed above for the full terms and conditions governing the use of each asset. diff --git a/tiny-cli/src/main/resources/icons/character_remove.png b/tiny-cli/src/main/resources/icons/character_remove.png deleted file mode 100644 index 8a3734c1..00000000 Binary files a/tiny-cli/src/main/resources/icons/character_remove.png and /dev/null differ diff --git a/tiny-cli/src/main/resources/icons/flag_square.png b/tiny-cli/src/main/resources/icons/flag_square.png deleted file mode 100644 index 1854d267..00000000 Binary files a/tiny-cli/src/main/resources/icons/flag_square.png and /dev/null differ diff --git a/tiny-cli/src/main/resources/icons/flip_head.png b/tiny-cli/src/main/resources/icons/flip_head.png deleted file mode 100644 index 01a1fba7..00000000 Binary files a/tiny-cli/src/main/resources/icons/flip_head.png and /dev/null differ diff --git a/tiny-cli/src/main/resources/icons/pawn_right.png b/tiny-cli/src/main/resources/icons/pawn_right.png deleted file mode 100644 index 73726c27..00000000 Binary files a/tiny-cli/src/main/resources/icons/pawn_right.png and /dev/null differ diff --git a/tiny-cli/src/main/resources/icons/pawn_skip.png b/tiny-cli/src/main/resources/icons/pawn_skip.png deleted file mode 100644 index 030a4449..00000000 Binary files a/tiny-cli/src/main/resources/icons/pawn_skip.png and /dev/null differ diff --git a/tiny-cli/src/main/resources/sfx/_tiny.json b/tiny-cli/src/main/resources/sfx/_tiny.json index 87a8283d..47398326 100644 --- a/tiny-cli/src/main/resources/sfx/_tiny.json +++ b/tiny-cli/src/main/resources/sfx/_tiny.json @@ -1,7 +1,7 @@ { "version": "V1", - "id": "b9bfec40-57eb-45bb-9e9a-435edb19c17e", "name": "Tiny SFX Sequencer", + "id": "b9bfec40-57eb-45bb-9e9a-435edb19c17e", "resolution": { "width": 384, "height": 256 @@ -12,56 +12,98 @@ }, "zoom": 2, "colors": [ - "#28282E", - "#1D2B53", - "#6C5671", - "#87A889", - "#F98284", - "#FFA300", - "#B0A9E4", - "#DEA38B", - "#ACCCE4", + "#414D66", + "#7284AA", + "#F76666", + "#A891D4", + "#8FABD0", + "#8CE8AB", "#FEAAE4", - "#D9C8BF", "#FFC384", - "#B0EB93", - "#FFCCAA", - "#E9F59D", - "#FFE6C6", - "#FFF7A0", + "#FFEA86", + "#DEE7FC", "#FFF1E8", - "#FFF7E4" + "#FFFFFF", + "#6EB887" ], "scripts": [ - "instrument-editor.lua", - "sfx-editor.lua", + "tiny-instrument-editor.lua", + "tiny-sfx-editor.lua", + "tiny-music-editor.lua", + "sfx-templates.lua", + "music-templates.lua", + "editor-base.lua", "wire.lua", - "test-game.lua", - "game.lua", "mouse.lua", + "layers.lua", "widgets.lua", - "music-editor.lua", + "widgets/icons.lua", "widgets/Envelop.lua", - "widgets/MatrixSelector.lua", - "widgets/ModeSwitch.lua", + "widgets/Dropdown.lua", "widgets/Knob.lua", "widgets/Checkbox.lua", "widgets/Fader.lua", - "widgets/MenuItem.lua", "widgets/Keyboard.lua", - "widgets/Help.lua", - "widgets/Button.lua" + "widgets/Button.lua", + "widgets/Modal.lua", + "widgets/Panel.lua", + "widgets/TextButton.lua", + "widgets/TextInput.lua", + "widgets/Speaker.lua", + "widgets/Counter.lua", + "widgets/utils.lua" ], "spritesheets": [ "sfx-spritesheet.png", - "sfx.png" + "sfx.png", + "sprite-sheet.png" ], "levels": [ + "tiny-music-editor.ldtk", "sfx-editor.ldtk", "editor.ldtk" ], - "sounds": [ - "test.sfx" - ], - "hideMouseCursor": true + "hideMouseCursor": true, + "fonts": [ + { + "name": "monogram", + "spritesheet": "monogram-bitmap.png", + "banks": [ + { + "name": "ascii", + "width": 6, + "height": 12, + "characters": [ + " !\"#$%&'()*+,-./", + "0123456789:;<=>?", + "@ABCDEFGHIJKLMNO", + "PQRSTUVWXYZ[\\]^_", + "`abcdefghijklmno↧↨", + "pqrstuvwxyz{|}~…↔↕", + " ↑↓", + " ←→" + ] + } + ] + }, + { + "name": "monogram-italic", + "spritesheet": "monogram-italic-bitmap.png", + "banks": [ + { + "name": "ascii", + "width": 6, + "height": 12, + "characters": [ + " !\"#$%&'()*+,-./", + "0123456789:;<=>?", + "@ABCDEFGHIJKLMNO", + "PQRSTUVWXYZ[\\]^_", + "`abcdefghijklmno", + "pqrstuvwxyz{|}~" + ] + } + ] + } + ] } \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/_tiny.stub.lua b/tiny-cli/src/main/resources/sfx/_tiny.stub.lua index cd9c1c23..d8a60cf8 100644 --- a/tiny-cli/src/main/resources/sfx/_tiny.stub.lua +++ b/tiny-cli/src/main/resources/sfx/_tiny.stub.lua @@ -1,117 +1,155 @@ -- DO NOT EDIT // DO NOT EDIT // DO NOT EDIT // DO NOT EDIT // DO NOT EDIT -- Tiny stub lua file generated automatically -- The file is used only to help Lua editors with autocomplete --- +-- -- An error, an issue? Please consult https://github.com/minigdx/tiny +-- +-- COLOR SYSTEM: +-- Color 0 = TRANSPARENT. Never use 0 if you want something visible. +-- Colors 1..N are defined in the _tiny.json configuration file ("colors" array). +-- The first hex color in the array is index 1, the second is index 2, etc. +-- Typical palette example (PICO-8 style, 16 colors): +-- 1 = black, 2 = dark blue, 3 = dark purple, 4 = dark green, +-- 5 = brown, 6 = dark grey, 7 = light grey, 8 = white, +-- 9 = red, 10 = orange, 11 = yellow, 12 = green, +-- 13 = blue, 14 = lavender, 15 = pink, 16 = peach +-- Check _tiny.json "colors" array for the actual palette of the current game. +-- +-- DEFAULT COLORS: +-- gfx.cls() without arguments clears to the closest color to black (#000000). +-- print() without a color argument uses the closest color to white (#FFFFFF). +-- To ensure visibility: use a color index DIFFERENT from the cls() color. +-- Common safe pattern: gfx.cls(1) then draw with colors >= 2. +-- +-- COMMON MISTAKES: +-- WRONG: shape.circlef(10, 10, 10, 0) -- color 0 is transparent, nothing visible! +-- WRONG: gfx.cls(1) then shape.circlef(10, 10, 10, 1) -- same color as background! +-- RIGHT: gfx.cls(1) then shape.circlef(10, 10, 10, 8) -- visible: white circle on black ---- Access to controllers like touch/mouse events or accessing which key is pressed by the user. -ctrl = {} ---- Get coordinates of the current touch/mouse. If the mouse/touch is out-of the screen, the coordinates will be the last mouse position/touch. The function return those coordinates as a table {x, y}. A sprite can be draw directly on the mouse position by passing the sprite number. ---- @overload fun(): any -- Get the mouse coordinates. ---- @overload fun(sprN: any): any -- Get the mouse coordinate and draw a sprite on those coordinates. -ctrl.touch = function() end ---- Return true if the key was pressed during the last frame. If you need to check that the key is still pressed, see `ctrl.pressing` instead. ---- @overload fun(key: any): any -- Is the key was pressed? -ctrl.pressed = function() end ---- Return true if the key is still pressed. ---- @overload fun(key: any): any -- Is the key is still pressed? -ctrl.pressing = function() end ---- Return the position of the touch (as `{x, y}`)if the screen was touched or the mouse button was pressed during the last frame. `nil` otherwise. ---- The touch can be : ---- ---- - 0: left click or one finger ---- - 1: right click or two fingers ---- - 2: middle click or three fingers ---- ---- If you need to check that the touch/mouse button is still active, see `ctrl.touching` instead. ---- @overload fun(touch: any): any -- Is the screen was touched or mouse button was pressed? -ctrl.touched = function() end ---- Return the position of the touch (as `{x, y}`)if the screen is still touched or the mouse button is still pressed. `nil` otherwise. ---- The touch can be : ---- ---- - 0: left click or one finger ---- - 1: right click or two fingers ---- - 2: middle click or three fingers ---- ---- ---- @overload fun(touch: any): any -- Is the screen is still touched or mouse button is still pressed? -ctrl.touching = function() end +--- Vector2 manipulation library. +vec2 = {} +--- Create a vector 2 as a table { x, y }. +--- @overload fun(x: number, y: number): any -- Create a vector 2 as a table { x, y }. +--- @overload fun(vec2: table): any -- Create a vector 2 as a table { x, y } using another vector 2. +vec2.create = function() end +--- Add vector2 to another vector2 +--- @overload fun(v1: table, v2: table): any -- Add a vector 2 {x, y} to another vector 2 {x, y} +--- @overload fun(x1: number, y1: number, x2: number, y2: number): any -- Add a destructured vector 2 to another destructured vector 2 +vec2.add = function() end +--- Subtract another vector from another vector +--- @overload fun(v1: table, v2: table): any -- Subtract a vector 2 {x, y} from another vector 2 {x, y} +--- @overload fun(x1: number, y1: number, x2: number, y2: number): any -- Subtract a destructured vector 2 from another destructured vector 2 +vec2.sub = function() end +--- Dot product between two vectors +--- @overload fun(v1: table, v2: table): any -- Dot product between a vector 2 {x, y} and another vector 2 {x, y} +--- @overload fun(x1: number, y1: number, x2: number, y2: number): any -- Dot product between a destructured vector 2 and another destructured vector 2 +vec2.dot = function() end +--- Calculate the magnitude (length) of a vector +--- @overload fun(x: number, y: number): any -- Calculate the magnitude (length) of a vector 2 {x, y} +--- @overload fun(v1: table): any -- Calculate the magnitude (length) of a vector 2 {x, y} +vec2.mag = function() end +--- Normalize a vector +--- @overload fun(x: number, y: number): any -- Normalize a vector 2 {x, y} +--- @overload fun(v1: table): any -- Normalize a vector 2 {x, y} +vec2.nor = function() end +--- Cross product +--- @overload fun(v1: table, v2: table): any -- Cross product between a vector 2 {x, y} and another vector 2 {x, y} +--- @overload fun(x1: number, y1: number, x2: number, y2: number): any -- Cross product between a destructured vector 2 and another destructured vector 2 +vec2.crs = function() end +--- Scale a vector +--- @overload fun(x: number, y: number, scl: number): any -- Scale a vector 2 {x, y} using the factor scl +--- @overload fun(v1: table, scl: number): any -- Scale a vector 2 {x, y} using the factor scl +vec2.scl = function() end ---- Helpers to debug your game by drawing or printing information on screen. -debug = {} ---- Enable or disable debug feature. ---- @overload fun(enabled: any): any -- Enable or disable debug by passing true to enable, false to disable. ---- @overload fun(): any -- Return true if debug is enabled. False otherwise. -debug.enabled = function() end ---- Display a table. ---- @overload fun(table: any): any -- Display a table. -debug.table = function() end ---- Log a message on the screen. ---- @overload fun(str: any): any -- Log a message on the screen. -debug.log = function() end ---- Log a message into the console. ---- @overload fun(str: any): any -- Log a message into the console. -debug.console = function() end ---- Draw a rectangle on the screen ---- @overload fun(x: any, y: any, width: any, height: any, color: any): any -- Draw a debug rectangle. ---- @overload fun(rect: any): any -- Draw a debug rectangle. ---- @overload fun(rect: any, color: any): any -- Draw a debug rectangle using a rectangle and a color. -debug.rect = function() end ---- Draw a point on the screen ---- @overload fun(x: any, y: any, color: any): any -- Draw a debug point. ---- @overload fun(point: any): any -- Draw a debug point. ---- @overload fun(point: any, color: any): any -- Draw a debug point. -debug.point = function() end ---- Draw a point on the screen ---- @overload fun(x1: any, y1: any, x2: any, y2: any, color: any): any -- Draw a debug line. ---- @overload fun(v1: any, v2: any): any -- Draw a debug line. ---- @overload fun(v1: any, v2: any, color: any): any -- Draw a debug line. -debug.line = function() end +--- Sound API to play/loop/stop a sound. +--- A sound can be created using the sound editor, using the command line `tiny-cli sfx `. +--- +--- WARNING: Because of browser behaviour, a sound can *only* be played only after the first +--- user interaction. +--- +--- Avoid to start a music or a sound at the beginning of the game. +--- Before it, force the player to hit a key or click by adding an interactive menu +--- or by starting the sound as soon as the player is moving. +--- +sound = {} +--- Play a sfx. +--- @overload fun(sfx_index: number, loop: boolean): any -- Play a sfx at sfx_index. The sfx can be looped. +sound.sfx = function() end +--- Play a music +--- @overload fun(music_index: number, loop: boolean): any -- Play the music at the index music_index. The music can be looped. +sound.music = function() end +--- Play a note by an instrument until it's stopped +--- @overload fun(note_name: string, instrument_index: number): any -- Play the note note_name using the instrument at instrument_index +sound.note = function() end ---- Access to graphical API like updating the color palette or applying a dithering pattern. -gfx = {} ---- clear the screen ---- @overload fun(): any -- Clear the screen with a default color. ---- @overload fun(color: any): any -- Clear the screen with a color. -gfx.cls = function() end ---- Set the color index at the coordinate (x,y). ---- @overload fun(x: any, y: any, color: any): any -- set the color index at the coordinate (x,y). -gfx.pset = function() end ---- Get the color index at the coordinate (x,y). ---- @overload fun(x: any, y: any): any -- get the color index at the coordinate (x,y). -gfx.pget = function() end ---- Transform the current frame buffer into a spritesheeet. ---- ---- - If the index of the spritesheet already exist, the spritesheet will be replaced ---- - If the index of the spritesheet doesn't exist, a new spritesheet at this index will be created ---- - If the index of the spritesheet is negative, a new spritesheet will be created at the last positive index. ---- ---- @overload fun(sheet: any): any -- Copy the current frame buffer to an new or existing sheet index. -gfx.to_sheet = function() end ---- Change a color from the palette to another color. ---- @overload fun(): any -- Reset all previous color changes. ---- @overload fun(a: any, b: any): any -- Replace the color a for the color b. -gfx.pal = function() end ---- Move the game camera. ---- @overload fun(): any -- Reset the game camera to it's default position (0,0). ---- @overload fun(x: any, y: any): any -- Set game camera to the position x, y. -gfx.camera = function() end ---- Apply a dithering pattern on every new draw call. The pattern is using the bits value of a 2 octet value. The first bits is the one on the far left and represent the pixel of the top left of a 4x4 matrix. The last bit is the pixel from the bottom right of this matrix. ---- @overload fun(): any -- Reset dithering pattern. The previous dithering pattern is returned. ---- @overload fun(pattern: any): any -- Apply dithering pattern. The previous dithering pattern is returned. -gfx.dither = function() end ---- Clip the draw surface (ie: limit the drawing area). ---- @overload fun(): any -- Reset the clip and draw on the fullscreen. ---- @overload fun(x: any, y: any, width: any, height: any): any -- Clip and limit the drawing area. -gfx.clip = function() end +--- Access map created with LDTk ( https://ldtk.io/ ). +map = {} +--- Set the current level to use. +--- @overload fun(): any -- Return the index of the current level. +--- @overload fun(level: any): any -- Set the current level to use. The level can be an index, the name or the id defined by LDTK. Return the previous index level or NIL if the new level is invalid. +map.level = function() end +--- Get the list of layers from the actual level. +--- @overload fun(layer_index: any): any -- Get the layer at the specified index or name from the actual level. The layer in the front is 0. +--- @overload fun(): any -- Get the list of layers from the actual level. +map.layer = function() end +--- Convert cell coordinates cx, cy into map screen coordinates x, y. +--- @overload fun(arg1: any, arg2: any): any -- Convert the cell coordinates into coordinates as a table [x,y]. +--- @overload fun(cell: table): any -- Convert the cell coordinates from a table {cx,cy} into screen coordinates as a table {x,y}. +map.from = function() end +--- Convert screen coordinates x, y into map cell coordinates {cx, cy}. +--- For example, coordinates of the player can be converted to cell coordinates to access the flag of the tile matching the player coordinates. +--- @overload fun(x: any, y: any): any -- Convert the coordinates into cell coordinates as a table {cx = cx,cy = cy}. +--- @overload fun(coordinates: table): any -- Convert the coordinates from a table {x,y} into cell coordinates as a table {cx,cy}. +map.to = function() end +--- Get the flag from a tile, using cell coordinates. +--- @overload fun(cx: number, cy: number): any -- Get the flag from the tile at the coordinate cx,cy. +--- @overload fun(cx: number, cy: number, layer: any): any -- Get the flag from the tile at the coordinate cx,cy from a specific layer. +map.cflag = function() end +--- Get the flag from a tile, using screen coordinates. +--- @overload fun(x: number, y: number): any -- Get the flag from the tile at the coordinate x,y. +--- @overload fun(x: number, y: number, layer: any): any -- Get the flag from the tile at the coordinate x,y from a specific layer. +map.flag = function() end +--- Table with all entities by type (ie: `map.entities["player"]`). +--- +--- ``` +--- local entities = map.entities() +--- local players = entities["Player"] +--- for entity in all(players) do +--- shape.rectf(entity.x, entity.y, entity.width, entity.height, 8) -- display an entity using a rectangle +--- end +--- [...] +--- entity.fields -- access custom field of the entity +--- ``` +--- +--- @overload fun(): any -- Get all entities from all entities layer as a table, with an entry per type. +--- @overload fun(a: any): any -- Get all entities from the specific layer as a table, with an entry per type. +map.entities = function() end +--- Draw map tiles on the screen. +--- @overload fun(): any -- Draw all active layers on the screen. +--- @overload fun(index: any): any -- Draw the layer with the name or the index on the screen. +map.draw = function() end + + +--- TODO +sfx = {} +--- Save the actual music in the current sfx file. +--- @overload fun(): any -- Save the actual music in the current sfx file. +sfx.save = function() end +--- Access instrument using its index or its name. +--- @overload fun(a: any): any -- Access instrument using its index or its name. +--- @overload fun(a: any, b: any): any -- Access instrument using its index or its name. Create it if the instrument is missing and the flag is true. +sfx.instrument = function() end +--- Access sfx using its index or its name. +--- @overload fun(arg: any): any -- Access sfx using its index or its name. +sfx.sfx = function() end --- Easing functions to 'juice' a game. Interpolation to juice your game. ---- All interpolations available: ---- +--- All interpolations available: +--- --- - pow2, pow3, pow4, pow5, --- - powIn2, powIn3, powIn4, powIn5, --- - powOut2, powOut3, powOut4, powOut5, @@ -122,333 +160,327 @@ gfx.clip = function() end --- - bounce, bounceIn, bounceOut, --- - exp10, expIn10, expOut10, --- - exp5, expIn5, expOut5, ---- - linear +--- - linear juice = {} ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.pow2 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.pow3 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.pow4 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.pow5 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powIn2 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powIn3 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powIn4 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powIn5 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powOut2 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powOut3 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powOut4 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.powOut5 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.sine = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.sineIn = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.sineOut = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.circle = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.circleIn = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.circleOut = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.elastic = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.elasticIn = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.elasticOut = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.swing = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.swingIn = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.swingOut = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.bounce = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.bounceIn = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.bounceOut = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.exp10 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.expIn10 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.expOut10 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.exp5 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.expIn5 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.expOut5 = function() end ---- ---- @overload fun(progress: any): any -- Give a percentage (progress) of the interpolation ---- @overload fun(start: any, end: any, progress: any): any -- Interpolate the value given a start and an end value. +--- +--- @overload fun(progress: number): any -- Give a percentage (progress) of the interpolation +--- @overload fun(start: number, end: number, progress: number): any -- Interpolate the value given a start and an end value. juice.linear = function() end ---- List of the available keys. To be used with ctrl. ---- ---- - `keys.up`, `keys.down`, `keys.left`, `keys.right` for directions. ---- - `keys.a` to `keys.z` and `keys.0` to `keys.9` for letters and numbers. ---- - `keys.space` and `keys.enter` for other keys. ---- -keys = {} - - ---- Access map created with LDTk ( https://ldtk.io/ ). -map = {} ---- Set the current level to use. ---- @overload fun(): any -- Return the index of the current level. ---- @overload fun(level: any): any -- Set the current level to use. The level can be an index or the id defined by LDTK. Return the previous index level. -map.level = function() end ---- Get the list of layers from the actual level. ---- @overload fun(layer_index: any): any -- Get the layer at the specified index or name from the actual level. The layer in the front is 0. ---- @overload fun(): any -- Get the list of layers from the actual level. -map.layer = function() end ---- Convert cell coordinates cx, cy into map screen coordinates x, y. ---- @overload fun(arg1: any, arg2: any): any -- Convert the cell coordinates into coordinates as a table [x,y]. ---- @overload fun(cell: any): any -- Convert the cell coordinates from a table [cx,cy] into screen coordinates as a table [x,y]. -map.from = function() end ---- Convert screen coordinates x, y into map cell coordinates cx, cy. ---- For example, coordinates of the player can be converted to cell coordinates to access the flag of the tile matching the player coordinates. ---- @overload fun(x: any, y: any): any -- Convert the coordinates into cell coordinates as a table [cx,cy]. ---- @overload fun(coordinates: any): any -- Convert the coordinates from a table [x,y] into cell coordinates as a table [cx,cy]. -map.to = function() end ---- Get the flag from a tile, using cell coordinates. ---- @overload fun(cx: any, cy: any): any -- Get the flag from the tile at the coordinate cx,cy. ---- @overload fun(cx: any, cy: any, layer: any): any -- Get the flag from the tile at the coordinate cx,cy from a specific layer. -map.cflag = function() end ---- Get the flag from a tile, using screen coordinates. ---- @overload fun(x: any, y: any): any -- Get the flag from the tile at the coordinate x,y. ---- @overload fun(x: any, y: any, layer: any): any -- Get the flag from the tile at the coordinate x,y from a specific layer. -map.flag = function() end ---- Table with all entities by type (ie: `map.entities["player"]`). ---- ---- ``` ---- local entities = map.entities() ---- local players = entities["Player"] ---- for entity in all(players) do ---- shape.rectf(entity.x, entity.y, entity.width, entity.height, 8) -- display an entity using a rectangle ---- end ---- [...] ---- entity.fields -- access custom field of the entity ---- ``` ---- ---- @overload fun(): any -- Get all entities from all entities layer as a table, with an entry per type. ---- @overload fun(a: any): any -- Get all entities from the specific layer as a table, with an entry per type. -map.entities = function() end ---- Draw map tiles on the screen. ---- @overload fun(): any -- Draw all active layers on the screen. ---- @overload fun(index: any): any -- Draw the layer with the name or the index on the screen. -map.draw = function() end +--- Access to controllers like touch/mouse events or accessing which key is pressed by the user. +ctrl = {} +--- Get coordinates of the current touch/mouse. If the mouse/touch is out-of the screen, the coordinates will be the last mouse position/touch. The function return those coordinates as a table {x, y}. A sprite can be draw directly on the mouse position by passing the sprite number. +--- @overload fun(): any -- Get the mouse coordinates. +--- @overload fun(sprN: number): any -- Get the mouse coordinate and draw a sprite on those coordinates. +ctrl.touch = function() end +--- Return true if the key was pressed during the last frame. If you need to check that the key is still pressed, see `ctrl.pressing` instead. +--- @overload fun(key: number): any -- Is the key was pressed? +ctrl.pressed = function() end +--- Return true if the key is still pressed. +--- @overload fun(key: number): any -- Is the key is still pressed? +ctrl.pressing = function() end +--- Return the position of the touch (as `{x, y}`)if the screen was touched or the mouse button was pressed during the last frame. `nil` otherwise. +--- The touch can be : +--- +--- - 0: left click or one finger +--- - 1: right click or two fingers +--- - 2: middle click or three fingers +--- +--- If you need to check that the touch/mouse button is still active, see `ctrl.touching` instead. +--- @overload fun(touch: number): any -- Is the screen was touched or mouse button was pressed? +ctrl.touched = function() end +--- Return the position of the touch (as `{x, y}`)if the screen is still touched or the mouse button is still pressed. `nil` otherwise. +--- The touch can be : +--- +--- - 0: left click or one finger +--- - 1: right click or two fingers +--- - 2: middle click or three fingers +--- +--- +--- @overload fun(touch: number): any -- Is the screen is still touched or mouse button is still pressed? +ctrl.touching = function() end ---- Math functions. Please note that standard Lua math methods are also available. -math = {} ---- Return the sign of the number: -1 if negative. 1 otherwise. ---- @overload fun(number: any): any -- Return the sign of the number. -math.sign = function() end ---- Clamp the value between 2 values. ---- @overload fun(a: any, value: any, b: any): any -- Clamp the value between a and b. If a is greater than b, then b will be returned. -math.clamp = function() end ---- Compute the distance between two points. ---- @overload fun(x1: any, y1: any, x2: any, y2: any): any -- Distance between (x1, y1) and (x2, y2). -math.dst = function() end ---- Compute the distance between two points not squared. Use this method to know if an coordinate is closer than another. ---- @overload fun(x1: any, y1: any, x2: any, y2: any): any -- Distance not squared between (x1, y1) and (x2, y2). -math.dst2 = function() end ---- Generate random values ---- @overload fun(): any -- Generate a random int (negative or positive value) ---- @overload fun(until: any): any -- Generate a random value between 1 until the argument. If a table is passed, it'll return a random element of the table. ---- @overload fun(a: any, b: any): any -- Generate a random value between a and b. -math.rnd = function() end ---- Check if two (r)ectangles overlaps. ---- @overload fun(rect1: any, rect2: any): any -- Check if the rectangle rect1 overlaps with the rectangle rect2. -math.roverlap = function() end ---- Perlin noise. The random generated value is between 0.0 and 1.0. ---- @overload fun(x: any, y: any, z: any): any -- Generate a random value regarding the parameters x,y and z. -math.perlin = function() end +--- Tiny Lib which offer offer the current frame (`tiny.frame`), the current time (`tiny.time`), delta time (`tiny.dt`), game dimensions (`tiny.width`, `tiny.height`), platform information (`tiny.platform`) and to switch to another script using `exit`. +tiny = {} +--- Delta time between two frame. As Tiny is a fixed frame engine, it's always equal to 1/60 +tiny.dt = any +--- Time elapsed since the start of the game. +tiny.t = any +--- Number of frames elapsed since the start of the game. +tiny.frame = any +--- Width of the game in pixels. +tiny.width = any +--- Height of the game in pixels. +tiny.height = any +--- Current platform: 1 for desktop, 2 for web. +tiny.platform = any +--- Exit the actual script to switch to another one. The next script to use is identified by it's index. The index of the script is the index of it in the list of scripts from the `_tiny.json` file.The first script is at the index 0. +--- @overload fun(scriptIndex: any): any -- Exit the actual script to switch to another one. +tiny.exit = function() end ---- List all notes from C0 to B8. Please note that bemols are the note with b (ie: Gb2) while sharps are the note with s (ie: As3). -notes = {} +--- Standard library. +--- Create new instance of a class by creating a new table and setting the metatable. It allow to create kind of Object Oriented Programming. +--- +--- +--- @overload fun(class: table): any -- Create new instance of class. +--- @overload fun(class: table, default: table): any -- Create new instance of class using default values. +function new() end +--- Add *all key/value* from the table `source` to the table `dest`. +--- @overload fun(source: table, dest: table): any -- Merge source into dest. +function merge() end +--- Append *all values* from the table `source` to the table `dest`. +--- @overload fun(source: table, dest: table): any -- Copy source into dest. +function append() end +--- Iterate over values of a table. +--- +--- - If you want to iterate over keys, use `pairs(table)`. +--- - If you want to iterate over index, use `ipairs(table)`. +--- - If you want to iterate in reverse, use `rpairs(table)`. +--- +--- @overload fun(table: any): any -- Iterate over the values of the table +function all() end +--- Iterate over values of a table in reverse order. The iterator return an index and the value. The method is useful to remove elements from a table while iterating on it. +--- @overload fun(table: any): any -- Iterate over the values of the table +function rpairs() end +--- Print on the screen a string. Default color is the closest to white (#FFFFFF) in the palette. To ensure visibility, use a color that contrasts with the cls() background color. +--- @overload fun(str: string): any -- print on the screen a string at (0,0) with the default color (closest to white). +--- @overload fun(str: string, x: number, y: number): any -- print on the screen a string with the default color (closest to white). +--- @overload fun(str: string, x: number, y: number, color: number): any -- print on the screen a string with a specific color index (1 to N). WARNING: 0 is transparent and will draw nothing visible. +function print() end ---- Sound API to play/loop/stop a sound. ---- A sound can be an SFX sound, generated using the tiny-cli sfx command or a MIDI file. ---- Please note that a SFX sound will produce the same sound whatever platform and whatever computer ---- as the sound is generated. ---- ---- A MIDI sound will depend of the MIDI synthesizer available on the machine. ---- ---- WARNING: Because of browser behaviour, a sound can *only* be played only after the first ---- user interaction. ---- ---- Avoid to start a music or a sound at the beginning of the game. ---- Before it, force the player to hit a key or click by adding an interactive menu ---- or by starting the sound as soon as the player is moving. ---- -sfx = {} ---- Access instrument using its index or its name. ---- @overload fun(arg: any): any -- Access instrument using its index or its name. -sfx.instrument = function() end ---- Generate and play a sine wave sound. ---- @overload fun(note: any, duration: any, volume: any): any -- Generate and play a sound using one note. -sfx.sine = function() end ---- Generate and play a sawtooth wave sound. ---- @overload fun(note: any, duration: any, volume: any): any -- Generate and play a sound using one note. -sfx.sawtooth = function() end ---- Generate and play a square wave sound. ---- @overload fun(note: any, duration: any, volume: any): any -- Generate and play a sound using one note. -sfx.square = function() end ---- Generate and play a triangle wave sound. ---- @overload fun(note: any, duration: any, volume: any): any -- Generate and play a sound using one note. -sfx.triangle = function() end ---- Generate and play a noise wave sound. ---- @overload fun(note: any, duration: any, volume: any): any -- Generate and play a sound using one note. -sfx.noise = function() end ---- Generate and play a pulse wave sound. ---- @overload fun(note: any, duration: any, volume: any): any -- Generate and play a sound using one note. -sfx.pulse = function() end ---- Play a sound by it's index. The index of a sound is given by it's position in the sounds field from the `_tiny.json` file.The first sound is at the index 0. ---- @overload fun(): any -- Play the sound at the index 0. ---- @overload fun(sound: any): any -- Play the sound by it's index. -sfx.play = function() end ---- Play a sound and loop over it. ---- @overload fun(): any -- Play the sound at the index 0. ---- @overload fun(sound: any): any -- Play the sound by it's index. -sfx.loop = function() end ---- Stop a sound. ---- @overload fun(): any -- Stop the sound at the index 0. ---- @overload fun(sound: any): any -- Stop the sound by it's index. -sfx.stop = function() end +--- Access to graphical API like updating the color palette or applying a dithering pattern. +gfx = {} +--- Switch to another draw mode. +--- +--- - 0: default. +--- - 1: drawing with transparent (ie: can erase part of the screen) +--- - 2: drawing a stencil that will be use with the next mode +--- - 3: drawing using a stencil test (ie: drawing only in the stencil) +--- - 4: drawing using a stencil test (ie: drawing everywhere except in the stencil) +--- +--- @overload fun(): any -- Return the actual mode. Switch back to the default mode. +--- @overload fun(mode: number): any -- Switch to another draw mode. Return the previous mode. +gfx.draw_mode = function() end +--- Clear the screen. When called without arguments, clears with the color closest to black (#000000) in the palette. To ensure visibility, always draw with a color index DIFFERENT from the cls() color. +--- @overload fun(): any -- Clear the screen with the color closest to black (#000000) in the palette. +--- @overload fun(color: number): any -- Clear the screen with the given color index (1 to N). Color 0 clears to transparent. +gfx.cls = function() end +--- Set the color index at the coordinate (x,y). +--- @overload fun(x: number, y: number, color: number): any -- set the color index at the coordinate (x,y). +gfx.pset = function() end +--- Get the color index at the coordinate (x,y). +--- @overload fun(x: number, y: number): any -- get the color index at the coordinate (x,y). +gfx.pget = function() end +--- Transform the current frame buffer into a spritesheeet. +--- +--- - If the index of the spritesheet already exist, the spritesheet will be replaced +--- - If the index of the spritesheet doesn't exist, a new spritesheet at this index will be created +--- - If the index of the spritesheet is negative, a new spritesheet will be created at the last positive index. +--- +--- @overload fun(sheet: any): any -- Copy the current frame buffer to an new or existing sheet index. +gfx.to_sheet = function() end +--- Change a color from the palette to another color. +--- @overload fun(): any -- Reset all previous color changes. +--- @overload fun(a: number, b: number): any -- Replace the color a for the color b. Both are color indices (1 to N). +gfx.pal = function() end +--- Move the game camera. +--- @overload fun(): any -- Reset the game camera to it's default position (0,0). +--- @overload fun(x: number, y: number): any -- Set game camera to the position x, y. +gfx.camera = function() end +--- Apply a dithering pattern on every new draw call. The pattern is using the bits value of a 2 octet value. The first bits is the one on the far left and represent the pixel of the top left of a 4x4 matrix. The last bit is the pixel from the bottom right of this matrix. +--- @overload fun(): any -- Reset dithering pattern. The previous dithering pattern is returned. +--- @overload fun(pattern: number): any -- Apply dithering pattern. The previous dithering pattern is returned. +gfx.dither = function() end +--- Clip the draw surface (ie: limit the drawing area). +--- @overload fun(): any -- Reset the clip and draw on the fullscreen. +--- @overload fun(x: number, y: number, width: number, height: number): any -- Clip and limit the drawing area. +gfx.clip = function() end --- Shape API to draw...shapes. Those shapes can be circle, rectangle, line or oval.All shapes can be draw filed or not filed. shape = {} --- Draw a rectangle. ---- @overload fun(x: any, y: any, width: any, height: any, color: any): any -- Draw a rectangle. ---- @overload fun(rect: any): any -- Draw a rectangle. ---- @overload fun(rect: any, color: any): any -- Draw a rectangle using a rectangle and a color. +--- @overload fun(x: number, y: number, width: number, height: number, color: number): any -- Draw a rectangle. Color is a palette index (1 to N). WARNING: 0 is transparent. +--- @overload fun(rect: table): any -- Draw a rectangle from a table {x, y, width, height, color}. +--- @overload fun(rect: table, color: number): any -- Draw a rectangle using a table {x, y, width, height} and a color index (1 to N). shape.rect = function() end ---- Draw an oval. ---- @overload fun(centerX: any, centerY: any, radiusX: any, radiusY: any): any -- Draw an oval using the default color. ---- @overload fun(centerX: any, centerY: any, radiusX: any, radiusY: any, color: any): any -- Draw an oval using the specified color. -shape.oval = function() end ---- Draw an oval filled. ---- @overload fun(centerX: any, centerY: any, radiusX: any, radiusY: any): any -- Draw a filled oval using the default color. ---- @overload fun(centerX: any, centerY: any, radiusX: any, radiusY: any, color: any): any -- Draw a filled oval using the specified color. -shape.ovalf = function() end --- Draw a filled rectangle. ---- @overload fun(x: any, y: any, width: any, height: any, color: any): any -- Draw a filled rectangle. ---- @overload fun(rect: any): any -- Draw a filled rectangle. ---- @overload fun(rect: any, color: any): any -- Draw a filled rectangle using a rectangle and a color. +--- @overload fun(x: number, y: number, width: number, height: number, color: number): any -- Draw a filled rectangle. Color is a palette index (1 to N). WARNING: 0 is transparent. +--- @overload fun(rect: table): any -- Draw a filled rectangle from a table {x, y, width, height, color}. +--- @overload fun(rect: table, color: number): any -- Draw a filled rectangle using a table {x, y, width, height} and a color index (1 to N). shape.rectf = function() end --- Draw a filled circle. ---- @overload fun(centerX: any, centerY: any, radius: any, color: any): any -- Draw a circle at the coordinate (centerX, centerY) with the radius and the color. +--- @overload fun(centerX: number, centerY: number, radius: number, color: number): any -- Draw a filled circle at (centerX, centerY) with the radius and color index (1 to N). WARNING: 0 is transparent. shape.circlef = function() end --- Draw a line. ---- @overload fun(x0: any, y0: any, x1: any, y2: any, color: any): any -- Draw a line. ---- @overload fun(x0: any, y0: any, x1: any, y1: any): any -- Draw a line with a default color. +--- @overload fun(x0: number, y0: number, x1: number, y1: number, color: number): any -- Draw a line with the given color index (1 to N). WARNING: 0 is transparent. +--- @overload fun(x0: number, y0: number, x1: number, y1: number): any -- Draw a line with a default color (closest to white). shape.line = function() end ---- Draw a circle. ---- @overload fun(a: any, b: any, c: any): any -- Draw a circle with the default color. ---- @overload fun(centerX: any, centerY: any, radius: any, color: any): any -- Draw a circle. +--- Draw a circle (outline only). +--- @overload fun(centerX: number, centerY: number, radius: number): any -- Draw a circle outline with the default color (closest to white). +--- @overload fun(centerX: number, centerY: number, radius: number, color: number): any -- Draw a circle outline with the given color index (1 to N). WARNING: 0 is transparent. shape.circle = function() end --- Draw a filled triangle using the coordinates of (x1, y1), (x2, y2) and (x3, y3) and color. ---- @overload fun(x1: any, y1: any, x2: any, y2: any, x3: any, y3: any, color: any): any -- Draw a filled triangle using the coordinates of (x1, y1), (x2, y2) and (x3, y3). +--- @overload fun(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, color: number): any -- Draw a filled triangle. Color is a palette index (1 to N). WARNING: 0 is transparent. shape.trianglef = function() end ---- Draw a triangle using the coordinates of (x1, y1), (x2, y2) and (x3, y3) and color. ---- @overload fun(x1: any, y1: any, x2: any, y2: any, x3: any, y3: any, color: any): any -- Draw a triangle using the coordinates of (x1, y1), (x2, y2) and (x3, y3). +--- Draw a triangle (outline only) using the coordinates of (x1, y1), (x2, y2) and (x3, y3) and color. +--- @overload fun(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, color: number): any -- Draw a triangle outline. Color is a palette index (1 to N). WARNING: 0 is transparent. shape.triangle = function() end ---- Draw a gradient using dithering, only from color c1 to color c2. ---- @overload fun(x: any, y: any, width: any, height: any, color1: any, color2: any, is_horizontal: any): any -- Draw a gradient using dithering, only from color c1 to color c2. +--- Draw a gradient using dithering, from color1 to color2. +--- @overload fun(x: number, y: number, width: number, height: number, color1: number, color2: number, is_horizontal: boolean): any -- Draw a gradient. color1 and color2 are palette indices (1 to N). is_horizontal: true for left-to-right, false for top-to-bottom. shape.gradient = function() end +--- Helpers to log information in the console. +console = {} +--- Log a message into the console. +--- @overload fun(str: any): any -- Log a message into the console. +console.log = function() end + + --- Sprite API to draw or update sprites. spr = {} --- Get the color index at the coordinate (x,y) from the current spritesheet. ---- @overload fun(x: any, y: any): any -- get the color index at the coordinate (x,y) from the current spritesheet. +--- @overload fun(x: number, y: number): any -- get the color index at the coordinate (x,y) from the current spritesheet. spr.pget = function() end --- Set the color index at the coordinate (x,y) in the current spritesheet. ---- @overload fun(x: any, y: any, color: any): any -- Set the color index at the coordinate (x,y) in the current spritesheet. +--- @overload fun(x: number, y: number, color: number): any -- Set the color index at the coordinate (x,y) in the current spritesheet. spr.pset = function() end --- Switch to another spritesheet. The index of the spritesheet is given by it's position in the spritesheets field from the `_tiny.json` file.The first spritesheet is at the index 0. It retuns the previous spritesheet. The spritesheet can also be referenced by its filename. --- @overload fun(): any -- Switch to the first spritesheet @@ -456,8 +488,8 @@ spr.pset = function() end spr.sheet = function() end --- S(uper) Draw a fragment from the spritesheet. --- @overload fun(): any -- Draw the full spritesheet at default coordinate (0, 0) ---- @overload fun(x: any, y: any): any -- Draw the full spritesheet at coordinate (x, y) ---- @overload fun(x: any, y: any, sprX: any, sprY: any): any -- Draw the full spritesheet at coordinate (x, y) from the sprite (sprX, sprY) +--- @overload fun(x: number, y: number): any -- Draw the full spritesheet at coordinate (x, y) +--- @overload fun(x: number, y: number, sprX: number, sprY: number): any -- Draw the full spritesheet at coordinate (x, y) from the sprite (sprX, sprY) --- @overload fun(x: any, y: any, sprX: any, sprY: any, width: any, height: any, flipX: any, flipY: any): any -- Draw a fragment from the spritesheet at the coordinate (x, y) from the sprite (sprX, sprY) with the width and height. spr.sdraw = function() end --- Draw a sprite. @@ -467,112 +499,134 @@ spr.sdraw = function() end spr.draw = function() end ---- Standard library. ---- Create new instance of a class by creating a new table and setting the metatable. It allow to create kind of Object Oriented Programming. ---- ---- ---- @overload fun(class: any): any -- Create new instance of class. ---- @overload fun(class: any, default: any): any -- Create new instance of class using default values. -function new() end ---- Add *all key/value* from the table `source` to the table `dest`. ---- @overload fun(source: any, dest: any): any -- Merge source into dest. -function merge() end ---- Append *all values* from the table `source` to the table `dest`. ---- @overload fun(source: any, dest: any): any -- Copy source into dest. -function append() end ---- Iterate over values of a table. ---- ---- - If you want to iterate over keys, use `pairs(table)`. ---- - If you want to iterate over index, use `ipairs(table)`. ---- - If you want to iterate in reverse, use `rpairs(table)`. ---- ---- @overload fun(table: any): any -- Iterate over the values of the table -function all() end ---- Iterate over values of a table in reverse order. The iterator return an index and the value. The method is useful to remove elements from a table while iterating on it. ---- @overload fun(table: any): any -- Iterate over the values of the table -function rpairs() end ---- Print on the screen a string. ---- @overload fun(str: any): any -- print on the screen a string at (0,0) with a default color. ---- @overload fun(str: any, x: any, y: any): any -- print on the screen a string with a default color. ---- @overload fun(str: any, x: any, y: any, color: any): any -- print on the screen a string with a specific color. -function print() end - - ---- Test method utilities used when tests are run. See link:#_the_tiny_cli_run_command[Run command] -test = {} ---- Assert that `expected` and `actual` are equals ---- @overload fun(expected: any, actual: any): any -- Assert that `expected` and `actual` are equals -test.eq = function() end ---- Assert that `expected` and `actual` are __not__ equals ---- @overload fun(expected: any, actual: any): any -- Assert that `expected` and `actual` are not equals -test.neq = function() end ---- Assert that `actual` is true ---- @overload fun(actual: any): any -- Assert that `actual` is true -test.t = function() end ---- Assert that `actual` is false ---- @overload fun(actual: any): any -- Assert that `actual` is false -test.t = function() end ---- Create a new `test` named `name` ---- @overload fun(name: any, test: any): any -- Create a new `test` named `name` -test.create = function() end - - ---- Tiny Lib which offer offer the current frame (`tiny.frame`), the current time (`tiny.time`), delta time (`tiny.dt`) and to switch to another script using `exit`. -tiny = {} ---- Exit the actual script to switch to another one. The next script to use is identified by it's index. The index of the script is the index of it in the list of scripts from the `_tiny.json` file.The first script is at the index 0. ---- @overload fun(scriptIndex: any): any -- Exit the actual script to switch to another one. -tiny.exit = function() end +--- List of the available keys. To be used with ctrl. +--- +--- - `keys.up`, `keys.down`, `keys.left`, `keys.right` for directions. +--- - `keys.a` to `keys.z` and `keys.0` to `keys.9` for letters and numbers. +--- - `keys.space` and `keys.enter` for other keys. +--- +keys = {} +--- the key a +keys.a = any +--- the key b +keys.b = any +--- the key c +keys.c = any +--- the key d +keys.d = any +--- the key e +keys.e = any +--- the key f +keys.f = any +--- the key g +keys.g = any +--- the key h +keys.h = any +--- the key i +keys.i = any +--- the key j +keys.j = any +--- the key k +keys.k = any +--- the key l +keys.l = any +--- the key m +keys.m = any +--- the key n +keys.n = any +--- the key o +keys.o = any +--- the key p +keys.p = any +--- the key q +keys.q = any +--- the key r +keys.r = any +--- the key s +keys.s = any +--- the key t +keys.t = any +--- the key u +keys.u = any +--- the key v +keys.v = any +--- the key w +keys.w = any +--- the key x +keys.x = any +--- the key y +keys.y = any +--- the key z +keys.z = any +--- the key space +keys.space = any +--- the key arrow up +keys.up = any +--- the key arrow down +keys.down = any +--- the key left down +keys.left = any +--- the key right down +keys.right = any +--- the key enter down +keys.enter = any +--- the key shift down +keys.shift = any +--- the key ctrl down +keys.ctrl = any +--- the key alt down +keys.alt = any +--- the key delete +keys.delete = any + + +--- Floppy allow you to get or save user Lua structure. +floppy = {} +--- Save the content into a local file, on desktop or in the local storage on the web platform. +--- @overload fun(name: string, content: any): any -- Save the content into the file name. +floppy.put = function() end +--- Load and get the content of the file name +--- @overload fun(name: string): any -- Load and get the content of the file name +floppy.get = function() end ---- Vector2 manipulation library. -vec2 = {} ---- Create a vector 2 as a table { x, y }. ---- @overload fun(x: any, y: any): any -- Create a vector 2 as a table { x, y }. ---- @overload fun(vec2: any): any -- Create a vector 2 as a table { x, y } using another vector 2. -vec2.create = function() end ---- Add vector2 to another vector2 ---- @overload fun(v1: any, v2: any): any -- Add a vector 2 {x, y} to another vector 2 {x, y} ---- @overload fun(x1: any, y1: any, x2: any, y2: any): any -- Add a destructured vector 2 to another destructured vector 2 -vec2.add = function() end ---- Subtract another vector from another vector ---- @overload fun(v1: any, v2: any): any -- Subtract a vector 2 {x, y} from another vector 2 {x, y} ---- @overload fun(x1: any, y1: any, x2: any, y2: any): any -- Subtract a destructured vector 2 from another destructured vector 2 -vec2.sub = function() end ---- Dot product between two vectors ---- @overload fun(v1: any, v2: any): any -- Dot product between a vector 2 {x, y} and another vector 2 {x, y} ---- @overload fun(x1: any, y1: any, x2: any, y2: any): any -- Dot product between a destructured vector 2 and another destructured vector 2 -vec2.dot = function() end ---- Calculate the magnitude (length) of a vector ---- @overload fun(x: any, y: any): any -- Calculate the magnitude (length) of a vector 2 {x, y} ---- @overload fun(v1: any): any -- Calculate the magnitude (length) of a vector 2 {x, y} -vec2.mag = function() end ---- Normalize a vector ---- @overload fun(x: any, y: any): any -- Normalize a vector 2 {x, y} ---- @overload fun(v1: any): any -- Normalize a vector 2 {x, y} -vec2.nor = function() end ---- Cross product ---- @overload fun(v1: any, v2: any): any -- Cross product between a vector 2 {x, y} and another vector 2 {x, y} ---- @overload fun(x1: any, y1: any, x2: any, y2: any): any -- Cross product between a destructured vector 2 and another destructured vector 2 -vec2.crs = function() end ---- Scale a vector ---- @overload fun(x: any, y: any, scl: any): any -- Scale a vector 2 {x, y} using the factor scl ---- @overload fun(v1: any, scl: any): any -- Scale a vector 2 {x, y} using the factor scl -vec2.scl = function() end +--- List all notes from C0 to B8. Please note that bemols are the note with b (ie: Gb2) while sharps are the note with s (ie: As3). +notes = {} +--- Get the name of a note regarding the note index (ie: C0 = 0, Cs0 = 1, ...) +--- @overload fun(note_index: number): any -- Get the name of a note regarding the note index (ie: C0 = 0, Cs0 = 1, ...) +notes.note = function() end ---- Workspace manipulation library. It allows you to save/load/download files -ws = {} ---- Save the content into a local file, on desktop or in the local storage on the web platform. ---- @overload fun(name: any, content: any): any -- Save the content into the file name. -ws.save = function() end ---- Load and get the content of the file name ---- @overload fun(name: any): any -- Load and get the content of the file name -ws.load = function() end ---- Create a local file. The name is generated so the name is unique. ---- @overload fun(prefix: any, extension: any): any -- Create a local file with the prefix and the extension. The name of the file created. -ws.create = function() end ---- List all files available in the workspace. ---- @overload fun(): any -- List all files available in the workspace. ---- @overload fun(extension: any): any -- List all files available in the workspace and filter by the file extension. -ws.list = function() end +--- Math functions. Please note that standard Lua math methods are also available. +math = {} +--- value of pi (~3.14) +math.pi = any +--- positive infinity value. +math.huge = any +--- Return the sign of the number: -1 if negative. 1 otherwise. +--- @overload fun(number: number): any -- Return the sign of the number. +math.sign = function() end +--- Calculate the angle in radians between the positive x-axis and the point (x, y). +--- @overload fun(y: number, x: number): any -- Calculate the angle for the point (x, y). Please note the argument order: y then x. +math.atan2 = function() end +--- Clamp the value between 2 values. +--- @overload fun(a: number, value: number, b: number): any -- Clamp the value between a and b. If a is greater than b, then b will be returned. +math.clamp = function() end +--- Compute the distance between two points. +--- @overload fun(x1: number, y1: number, x2: number, y2: number): any -- Distance between (x1, y1) and (x2, y2). +math.dst = function() end +--- Compute the distance between two points not squared. Use this method to know if an coordinate is closer than another. +--- @overload fun(x1: number, y1: number, x2: number, y2: number): any -- Distance not squared between (x1, y1) and (x2, y2). +math.dst2 = function() end +--- Generate random values +--- @overload fun(): any -- Generate a random int (negative or positive value) +--- @overload fun(until: any): any -- Generate a random value between 1 until the argument. If a table is passed, it'll return a random element of the table. +--- @overload fun(a: number, b: number): any -- Generate a random value between a and b. +math.rnd = function() end +--- Check if two (r)ectangles overlaps. +--- @overload fun(rect1: table, rect2: table): any -- Check if the rectangle rect1 overlaps with the rectangle rect2. +math.roverlap = function() end +--- Perlin noise. The random generated value is between 0.0 and 1.0. +--- @overload fun(x: number, y: number, z: number): any -- Generate a random value regarding the parameters x,y and z. +math.perlin = function() end diff --git a/tiny-cli/src/main/resources/sfx/editor-base.lua b/tiny-cli/src/main/resources/sfx/editor-base.lua new file mode 100644 index 00000000..170db419 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/editor-base.lua @@ -0,0 +1,235 @@ +local widgets = require("widgets") +local mouse = require("mouse") + +local EditorBase = {} + +-- Widget loading: panels +EditorBase.init_panels = function(entities, all_widgets) + for p in all(entities["Panel"]) do + local panel = widgets:create_panel(p) + table.insert(all_widgets, panel) + end +end + +-- Widget loading: text buttons +-- Returns a table of text buttons keyed by Action field (e.g. { Save = button }) +EditorBase.init_text_buttons = function(entities, all_widgets) + local buttons_by_action = {} + for tb in all(entities["TextButton"]) do + local text_button = widgets:create_text_button(tb) + if text_button.fields and text_button.fields.Action then + buttons_by_action[text_button.fields.Action] = text_button + end + table.insert(all_widgets, text_button) + end + return buttons_by_action +end + +-- Widget loading: speakers +EditorBase.init_speakers = function(entities, all_widgets, speaker_widgets) + for s in all(entities["Speaker"]) do + local speaker = widgets:create_speaker(s) + table.insert(all_widgets, speaker) + table.insert(speaker_widgets, speaker) + end +end + + +-- Modal creation from Button entities. +-- config.modal_sizes: optional table of { ModalName = {x, y, width, height} } +-- config.on_open: function(modal_name) returning the value to pass to modal:open() +-- config.on_name_validate: function(value) called when NameModal validates +-- Returns modals_by_name table +EditorBase.init_buttons = function(entities, all_widgets, config) + local modals_by_name = {} + local default_size = { x = 96, y = 64, width = 192, height = 128 } + local modal_sizes = config.modal_sizes or {} + + for b in all(entities["Button"]) do + local button = widgets:create_button(b) + + if button.fields.Modal then + local modal_name = button.fields.Modal + + if not modals_by_name[modal_name] then + local size = modal_sizes[modal_name] or default_size + local modal = widgets:create_modal({ + x = size.x, + y = size.y, + width = size.width, + height = size.height, + level_name = modal_name, + fields = {}, + }) + modals_by_name[modal_name] = modal + end + + button.on_change = function() + local target = modals_by_name[modal_name] + if target then + target:open(config.on_open(modal_name)) + end + end + end + + table.insert(all_widgets, button) + end + + -- Wire NameModal with dropdown rename + local name_modal = modals_by_name["NameModal"] + if name_modal and config.on_name_validate then + name_modal.on_validate = function(self, value) + config.on_name_validate(value) + end + end + + return modals_by_name +end + +-- Dropdown populated from indexed entities. +-- config.count: number of items +-- config.fetch: function(i) returning entity with .name +-- config.label: fallback label prefix (e.g. "Instrument") +-- config.on_select: function(index) called on selection change +-- config.layer_manager: optional, for dropdown overlay management +-- Returns the dropdown widget +EditorBase.init_entity_dropdown = function(entities, all_widgets, config) + local dropdown_widget = nil + + for d in all(entities["Dropdown"]) do + local dropdown = widgets:create_dropdown(d) + + -- Match the right dropdown: use width filter if provided, otherwise first empty one + local is_target = false + if config.min_width then + is_target = (dropdown_widget == nil and dropdown.width >= config.min_width) + else + is_target = (dropdown_widget == nil and #dropdown.options == 0) + end + + if is_target then + if #dropdown.options == 0 then + for i = 0, config.count - 1 do + local entity = config.fetch(i) + local name = entity.name or (config.label .. " " .. i) + table.insert(dropdown.options, "[" .. i .. "] " .. name) + end + dropdown:_init() + end + + dropdown.on_change = function(self) + config.on_select(self.selected - 1) + end + + if config.layer_manager then + local layer_manager = config.layer_manager + local original_update = dropdown._update + dropdown._update = function(self) + local was_open = self.open + original_update(self) + if self.open and not was_open then + layer_manager:set_overlay(self) + elseif not self.open and was_open then + layer_manager:set_overlay(nil) + end + end + end + + dropdown_widget = dropdown + end + + table.insert(all_widgets, dropdown) + end + + return dropdown_widget +end + +-- Update the dropdown display after a rename +EditorBase.update_dropdown_name = function(dropdown_widget, value) + if dropdown_widget then + local idx = dropdown_widget.selected + dropdown_widget.options[idx] = "[" .. (idx - 1) .. "] " .. value + dropdown_widget:_init() + end +end + +-- Save dirty tracking: wraps on_change on all widgets and on_validate on modals +-- to detect unsaved changes. save_button.on_change triggers save. +-- Returns a state table { dirty, next_shake_time } +EditorBase.init_save_reminder = function(all_widgets, save_button, modals_by_name) + local save_state = { dirty = false, next_shake_time = 0 } + + local mark_dirty = function() + save_state.dirty = true + save_state.next_shake_time = tiny.t + 15 + end + + save_button.on_change = function() + sfx.save() + save_state.dirty = false + end + + for _, w in ipairs(all_widgets) do + if w ~= save_button then + local orig = w.on_change + if orig then + w.on_change = function(self, ...) + mark_dirty() + return orig(self, ...) + end + end + end + end + + for _, modal in pairs(modals_by_name) do + local orig_validate = modal.on_validate + if orig_validate then + modal.on_validate = function(self, ...) + mark_dirty() + return orig_validate(self, ...) + end + end + end + + return save_state +end + +-- Update save reminder: shakes the save button periodically when dirty +EditorBase.update_save_reminder = function(save_button, save_state) + if save_state.dirty and save_button and tiny.t >= save_state.next_shake_time then + save_button:shake() + save_state.next_shake_time = tiny.t + 15 + end +end + +-- Update loop: mouse + modal-or-widgets +EditorBase.update = function(modals_by_name, update_widgets_fn) + mouse._update(function() end, function() end, function() end) + + local active_modal + for _, modal in pairs(modals_by_name) do + if modal.visible then + active_modal = modal + break + end + end + + if active_modal then + active_modal:_update() + else + update_widgets_fn() + end +end + +-- Draw loop: cls + background + widgets + modals + mouse +EditorBase.draw = function(draw_widgets_fn, modals_by_name) + gfx.cls() + map.draw("Background") + draw_widgets_fn() + for _, modal in pairs(modals_by_name) do + modal:_draw() + end + mouse._draw() +end + +return EditorBase diff --git a/tiny-cli/src/main/resources/sfx/game.lua b/tiny-cli/src/main/resources/sfx/game.lua index e7cf655b..aca13c3e 100644 --- a/tiny-cli/src/main/resources/sfx/game.lua +++ b/tiny-cli/src/main/resources/sfx/game.lua @@ -214,7 +214,7 @@ editor.on_active_tab = function(current, prev) if prev ~= nil then -- update the model of the previous tab before switching. local score = editor.generate_score(prev.content) - debug.console(score) + console.log(score) prev.content = sfx.to_table(score) end diff --git a/tiny-cli/src/main/resources/sfx/instrument-editor.lua b/tiny-cli/src/main/resources/sfx/instrument-editor.lua deleted file mode 100644 index 18532a3c..00000000 --- a/tiny-cli/src/main/resources/sfx/instrument-editor.lua +++ /dev/null @@ -1,302 +0,0 @@ -local widgets = require("widgets") -local mouse = require("mouse") -local wire = require("wire") -local MatrixSelector = require("widgets/MatrixSelector") -local ModeSwitch = require("widgets/ModeSwitch") - -local m = { - widgets = {} -} - -local state = { - instrument = nil, - next_note_on = nil, - next_note_off = nil -} - -local on_press = function() - state.instrument.note_on("C4") -end - -local on_release = function() - state.instrument.note_off("C4") -end - -local on_press_repeat = function() - state.next_note_on = 0 - state.next_note_off = nil -end - -local on_release_repeat = function() - state.next_note_on = nil -end - -local on_repeat_update = function() - if state.next_note_off then - state.next_note_off = state.next_note_off - tiny.dt - if state.next_note_off < 0 then - state.instrument.note_off("C4") - state.next_note_off = nil - - if state.next_note_on then - state.next_note_on = state.instrument.release + tiny.dt - end - end - end - - if state.next_note_on and state.next_note_on >= 0 then - state.next_note_on = state.next_note_on - tiny.dt - if state.next_note_on < 0 then - state.instrument.note_on("C4") - state.next_note_off = state.instrument.attack + state.instrument.decay - end - end -end - -function _init_knob(entities) - for k in all(entities["Knob"]) do - local knob = widgets:create_knob(k) - table.insert(m.widgets, knob) - end -end - -function _init_envelop(entities) - for k in all(entities["Envelop"]) do - local envelop = widgets:create_envelop(k) - - local widget = wire.find_widget(m.widgets, envelop.fields.Attack) - widget.on_press = on_press_repeat - widget.on_release = on_release_repeat - wire.bind(state, "instrument.attack", widget, "value") - wire.bind(state, "instrument.attack", envelop, "attack") - - widget = wire.find_widget(m.widgets, envelop.fields.Decay) - widget.on_press = on_press_repeat - widget.on_release = on_release_repeat - wire.bind(state, "instrument.decay", widget, "value") - wire.bind(state, "instrument.decay", envelop, "decay") - - widget = wire.find_widget(m.widgets, envelop.fields.Sustain) - widget.on_press = on_press - widget.on_release = on_release - wire.bind(state, "instrument.sustain", widget, "value") - wire.bind(state, "instrument.sustain", envelop, "sustain") - - widget = wire.find_widget(m.widgets, envelop.fields.Release) - widget.on_press = on_press_repeat - widget.on_release = on_release_repeat - wire.bind(state, "instrument.release", widget, "value") - wire.bind(state, "instrument.release", envelop, "release") - table.insert(m.widgets, envelop) - end -end - -function _init_instrument_matrix(entities) - for matrix in all(entities["MatrixSelector"]) do - local widget = new(MatrixSelector, matrix) - widget:_init() - wire.sync(state, "instrument.index", widget, "value") - wire.listen(widget, "value", function(source, value) - state.instrument = sfx.instrument(value, true) - end) - wire.sync(state, "instrument.all", widget, "active_indices") - table.insert(m.widgets, widget) - end -end - -function _init_sweep(entities) - for effect in all(entities["Sweep"]) do - local active = wire.find_widget(m.widgets, effect.fields.Enabled) - local acceleration = wire.find_widget(m.widgets, effect.fields.Acceleration) - local sweep = wire.find_widget(m.widgets, effect.fields.Sweep) - - acceleration.on_press = on_press - acceleration.on_release = on_release - sweep.on_press = on_press - sweep.on_release = on_release - - -- Use manual sync with correct modes for checkboxes - wire.bind(state, "instrument.sweep.active", active, "value") - wire.bind(state, "instrument.sweep.acceleration", acceleration, "value") - wire.bind(state, "instrument.sweep.sweep", sweep, "value") - end -end - -function _init_vibrato(entities) - for effect in all(entities["Vibrato"]) do - local active = wire.find_widget(m.widgets, effect.fields.Enabled) - local frequency = wire.find_widget(m.widgets, effect.fields.Frequency) - local depth = wire.find_widget(m.widgets, effect.fields.Depth) - - frequency.on_press = on_press - frequency.on_release = on_release - depth.on_press = on_press - depth.on_release = on_release - - -- Use manual sync with correct modes for checkboxes - wire.bind(state, "instrument.vibrato.active", active, "value") - - wire.bind(state, "instrument.vibrato.frequency", frequency, "value") - wire.bind(state, "instrument.vibrato.depth", depth, "value") - end -end - -function _init_keyboard(entities) - local currentNote - local playNote = function(_, value) - if value and currentNote == nil then - state.instrument.note_on(value) - currentNote = value - elseif value and currentNote ~= nil then - state.instrument.note_on(value) - state.instrument.note_off(currentNote) - currentNote = value - elseif not value then - state.instrument.note_off(currentNote) - currentNote = nil - end - end - - for k in all(entities["Keyboard"]) do - local label = widgets:create_keyboard(k) - wire.listen(label, "value", playNote) - table.insert(m.widgets, label) - end -end - -function _init_wave_type(entities) - local buttonToWave = function(type) - local result = {} - result.from_widget = function(source, target, value) - return type - end - result.to_widget = function(source, target, value) - if value == type then - return 2 - else - return 0 - end - end - return result - end - - for b in all(entities["WaveTypeSelector"]) do - local sine = wire.find_widget(m.widgets, b.fields.Sine) - wire.bind(state, "instrument.wave", sine, "status", buttonToWave("SINE")) - local square = wire.find_widget(m.widgets, b.fields.Square) - wire.bind(state, "instrument.wave", square, "status", buttonToWave("SQUARE")) - local pulse = wire.find_widget(m.widgets, b.fields.Pulse) - wire.bind(state, "instrument.wave", pulse, "status", buttonToWave("PULSE")) - local triangle = wire.find_widget(m.widgets, b.fields.Triangle) - wire.bind(state, "instrument.wave", triangle, "status", buttonToWave("TRIANGLE")) - local noise = wire.find_widget(m.widgets, b.fields.Noise) - wire.bind(state, "instrument.wave", noise, "status", buttonToWave("NOISE")) - local sawtooth = wire.find_widget(m.widgets, b.fields.Sawtooth) - wire.bind(state, "instrument.wave", sawtooth, "status", buttonToWave("SAW_TOOTH")) - end -end - -local overlays = { - Sine = { x = 16, y = 16 }, - Pulse = { x = 32, y = 16 }, - Noise = { x = 48, y = 16 }, - Sawtooth = { x = 64, y = 16 }, - Triangle = { x = 80, y = 16 }, - Square = { x = 96, y = 16 }, -} - -function _init_buttons(entities) - for b in all(entities["Button"]) do - local button = widgets:create_button(b) - button.overlay = overlays[button.fields.WaveType] - table.insert(m.widgets, button) - end -end - -function _init_editor_mode(entities) - for mode in all(entities["EditorMode"]) do - local modeSwitch = widgets:create_mode_switch_component(mode) - modeSwitch.selected_index = 0 - table.insert(m.widgets, modeSwitch) - end -end - -function _init_checkbox(entities) - for mode in all(entities["Checkbox"]) do - local button = widgets:create_checkbox(mode) - table.insert(m.widgets, button) - end -end - -function _init_harmonics(entities) - - for mode in all(entities["Harmonics"]) do - for index, harmonic in ipairs(mode.fields.Harmonics) do - local knob = wire.find_widget(m.widgets, harmonic) - knob.on_press = on_press - knob.on_release = on_release - wire.bind(state, "instrument.harmonics." .. index, knob, "value") - end - end -end - -function _init_mode_switch(entities) - for mode in all(entities["ModeButton"]) do - local button = new(ModeSwitch, mode) - table.insert(m.widgets, button) - end -end - -function _init() - map.level("InstrumentEditor") - local entities = map.entities() - state.instrument = sfx.instrument(1) - - _init_knob(entities) - _init_checkbox(entities) - _init_buttons(entities) - _init_envelop(entities) - _init_instrument_matrix(entities) - _init_wave_type(entities) - _init_editor_mode(entities) - _init_sweep(entities) - _init_vibrato(entities) - _init_keyboard(entities) - _init_harmonics(entities) - _init_mode_switch(entities) - - -- force setting correct values - if (state.on_change) then - state:on_change() - end -end - -function _update() - mouse._update(function() - end, function() - end, function() - end) - - if (ctrl.pressed(keys.space)) then - state.instrument = sfx.instrument((state.instrument.index + 1) % 4) - if (state.on_change) then - state:on_change() - end - end - - for w in all(m.widgets) do - w:_update() - end - - - on_repeat_update() -end - -function _draw() - map.draw() - - for w in all(m.widgets) do - w:_draw() - end - mouse._draw() -end diff --git a/tiny-cli/src/main/resources/sfx/layers.lua b/tiny-cli/src/main/resources/sfx/layers.lua new file mode 100644 index 00000000..6cc40360 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/layers.lua @@ -0,0 +1,71 @@ +local LayerManager = {} + +LayerManager.create = function() + return { + layers = {}, -- ordered list of {name, tiles, widgets, always} + active = nil, -- name of the active layer + overlay = nil, -- widget drawn last (e.g., open dropdown) + + register = function(self, name, opts) + table.insert(self.layers, { + name = name, + tiles = opts.tiles, + widgets = opts.widgets or {}, + always = opts.always or false, + }) + end, + + switch = function(self, name) + self.active = name + end, + + set_overlay = function(self, widget) + self.overlay = widget + end, + + draw_base = function(self) + for _, layer in ipairs(self.layers) do + if layer.always then + if layer.tiles then + map.draw(layer.tiles) + end + for widget in all(layer.widgets) do + if widget ~= self.overlay then + widget:_draw() + end + end + end + end + end, + + draw_active = function(self) + for _, layer in ipairs(self.layers) do + if not layer.always and layer.name == self.active then + if layer.tiles then + map.draw(layer.tiles) + end + for widget in all(layer.widgets) do + if widget ~= self.overlay then + widget:_draw() + end + end + end + end + if self.overlay then + self.overlay:_draw() + end + end, + + update_widgets = function(self) + for _, layer in ipairs(self.layers) do + if layer.always or layer.name == self.active then + for widget in all(layer.widgets) do + widget:_update() + end + end + end + end, + } +end + +return LayerManager diff --git a/tiny-cli/src/main/resources/sfx/monogram-bitmap.png b/tiny-cli/src/main/resources/sfx/monogram-bitmap.png new file mode 100644 index 00000000..059b3388 Binary files /dev/null and b/tiny-cli/src/main/resources/sfx/monogram-bitmap.png differ diff --git a/tiny-cli/src/main/resources/sfx/monogram-italic-bitmap.png b/tiny-cli/src/main/resources/sfx/monogram-italic-bitmap.png new file mode 100644 index 00000000..8cdadacc Binary files /dev/null and b/tiny-cli/src/main/resources/sfx/monogram-italic-bitmap.png differ diff --git a/tiny-cli/src/main/resources/sfx/music-editor.lua b/tiny-cli/src/main/resources/sfx/music-editor.lua deleted file mode 100644 index 9f4902b8..00000000 --- a/tiny-cli/src/main/resources/sfx/music-editor.lua +++ /dev/null @@ -1,316 +0,0 @@ -local widgets = require("widgets") -local mouse = require("mouse") -local wire = require("wire") - -local m = { - widgets = {} -} - -local Cursor = { - tracki = 1, - fieldi = 1, - fields = { - { x = 0, width = 11, name = "notei" }, - { x = 12, width = 8, name = "octave" }, - { x = 22, width = 14, name = "volume" }, - { x = 39, width = 9, name = "mode" }, - { x = 51, width = 13, name = "instrument" }, - }, - track = nil, -- widget of the track - tracks = {}, -- all tracks, to pass to the next one, ... - beati = 0, - beat_max = 20, -- number of beats displayed - height = 10, - width = 72, - key_repeat_count = { - [keys.down] = 0, - [keys.up] = 0, - [keys.left] = 0, - [keys.right] = 0 - }, - editor = false, -- editor mode activation -} - -Cursor._update = function(self) - self:_move_cursor() - self.track = self.tracks[self.tracki] - - local step = self.track.height / self.beat_max - self.x = self.track.x - self.y = self.track.y + math.floor(self.beati) * step -end - -Cursor._move_cursor = function(self) - local key_repeat_delay_frames = 10 - function easeKeys(key, on_pressed, on_pressing) - if ctrl.pressed(key) then - on_pressed() - self.key_repeat_count[key] = key_repeat_delay_frames - elseif ctrl.pressing(key) then - if self.key_repeat_count[key] > 0 then - self.key_repeat_count[key] = self.key_repeat_count[key] - 1 - else - on_pressed() - self.key_repeat_count[key] = key_repeat_delay_frames / 5 - end - else - self.key_repeat_count[key] = 0 - end - end - - if self.editor then - easeKeys(keys.down, function() - if self.fields[self.fieldi].name == "notei" then - -- Special handling for note field - local beat = self.track.track.beats[self.beati + 1] - local current_notei = beat.notei or 0 - - if current_notei == 1 then - -- C0 (lowest note) - -- Set to repeat previous note (null) - beat.notei = nil - elseif beat.notei == nil then - -- Set to note off (silence) - beat.notei = -1 - else - -- Normal decrement - self.track:change(self.beati + 1, self.fields[self.fieldi].name, -1) - end - else - -- Normal field handling - self.track:change(self.beati + 1, self.fields[self.fieldi].name, -1) - end - end) - easeKeys(keys.up, function() - if self.fields[self.fieldi].name == "notei" then - -- Special handling for note field - local beat = self.track.track.beats[self.beati + 1] - - if beat.notei == -1 then - -- Note off (silence) - -- Set to repeat previous note (null) - beat.notei = nil - elseif beat.notei == nil then - -- Set to C0 (first note) - beat.notei = 1 - else - -- Normal increment - self.track:change(self.beati + 1, self.fields[self.fieldi].name, 1) - end - else - -- Normal field handling - self.track:change(self.beati + 1, self.fields[self.fieldi].name, 1) - end - end) - - -- Handle delete key to set note to null (repeat previous) - if ctrl.pressed(keys.delete) and self.fields[self.fieldi].name == "notei" then - local beat = self.track.track.beats[self.beati + 1] - beat.notei = nil - end - else - easeKeys(keys.down, function() - self.beati = math.floor(self.beati) + 1 - end) - easeKeys(keys.up, function() - self.beati = math.floor(self.beati) - 1 - end) - end - - easeKeys(keys.left, function() - self.fieldi = math.floor(self.fieldi) - 1 - end) - easeKeys(keys.right, function() - self.fieldi = math.floor(self.fieldi) + 1 - end) - - -- switch tracks - if (self.fieldi > #self.fields) then - self.tracki = self.tracki + 1 - if (self.tracki <= #self.tracks) then - self.fieldi = 1 - end - elseif (self.fieldi < 1) then - self.tracki = self.tracki - 1 - if (self.tracki >= 1) then - self.fieldi = #self.fields - end - end - self.fieldi = math.clamp(1, self.fieldi, #self.fields) - self.tracki = math.clamp(1, self.tracki, #self.tracks) - self.beati = math.clamp(0, self.beati, self.beat_max - 1) - - self.track = self.tracks[self.tracki] - - if ctrl.pressed(keys.enter) then - -- editor mode - self.editor = not self.editor - end -end - -Cursor._draw = function(self) - - local index = math.floor(self.fieldi) - local field_x = self.x + self.fields[index].x - local field_w = self.fields[index].width - - shape.rect(field_x, self.y, field_w, self.height, 9) - shape.rect(self.x, self.y, self.width, self.height, 9) - - if self.editor then - spr.sdraw(field_x + 2, self.y - 8, 240, 40, 8, 8) - spr.sdraw(field_x + 2, self.y + 8, 240, 40, 8, 8, false, true) - end -end - -local TrackEditor = { - track = nil, -- the actual dictionary of the track - beat_offset = 1 -} - -TrackEditor.change = function(self, beat, name, inc) - self.track.beats[beat][name] = (self.track.beats[beat][name] or 0) + inc -end - -TrackEditor._update = function(self) - -end - -TrackEditor._draw = function(self) - print("N O VV M I", self.x + 2, self.y - 8) - local offset = self.beat_offset - - -- todo: introduce offset if the user is going down in the list - for i = 1, 20 do - local y = (i - offset) * 10 + (self.y + 3) - print(string.format("%02x", i), self.x - 10, y) - end - for i, beat in ipairs(self.track.beats) do - if i >= offset and i < offset + 20 then - local y = (i - offset) * 10 + (self.y + 3) - - -- Check note state - if beat.notei == nil then - -- Repeat previous note (null) - print("-- . .. . .", self.x + 2, y) - elseif beat.notei == -1 then - -- Note off (silence) - print("== . .. . .", self.x + 2, y) - elseif beat.note == nil then - -- Empty beat - print(".. . .. . .", self.x + 2, y) - else - -- Normal note - local note = beat.note - if (#note == 1) then - note = note .. " " - end - local mode - if beat.mode >= 1 then - mode = "R" - else - mode = "L" - end - - local instrument = beat.instrument or "." - if instrument ~= "." then - instrument = string.format("%01x", instrument) - end - - print( - note .. - " " .. beat.octave .. - " " .. string.format("%02x", beat.volume) .. - " " .. mode .. " " .. instrument, self.x + 2, y - ) - end - - end - end - - -- border - shape.rect(self.x, self.y, self.width, self.height, 10) -end - -function _init() - m.widgets = {} - - map.level("MusicEditor") - - local entities = map.entities() - for mode in all(entities["EditorMode"]) do - local modeSwitch = widgets:create_mode_switch_component(mode) - modeSwitch.selected_index = 2 - table.insert(m.widgets, modeSwitch) - end - - for k in all(entities["Knob"]) do - local knob = widgets:create_knob(k) - -- knob.on_hover = on_menu_item_hover - table.insert(m.widgets, knob) - - if knob.fields.Label == "BPM" then - - end - end - - for c in all(entities["Button"]) do - local button = widgets:create_button(c) - - if button.fields.Type == "SINE" then - button.on_change = function() - sfx.export() - end - end - table.insert(m.widgets, button) - end - - local tracks = {} - for mode in all(entities["TrackEditor"]) do - local track = new(TrackEditor, mode) - local volume = wire.find_widget(m.widgets, track.fields.Volume) - - track.track = sfx.track(track.fields.Track) - - wire.bind(volume, "value", track, "track.volume") - - table.insert(m.widgets, track) - table.insert(tracks, track) - end - - local cursor = new(Cursor) - cursor.track = tracks[1] - cursor.tracks = tracks - table.insert(m.widgets, cursor) - -end - -function _draw() - map.draw() - - for w in all(m.widgets) do - w:_draw() - end - mouse._draw() -end - -local mhandler = nil - -function _update() - mouse._update(function() - end, function() - end, function() - end) - - if (ctrl.pressed(keys.space)) then - if mhandler then - mhandler.stop() - end - - mhandler = sfx.music(0) - end - - for w in all(m.widgets) do - w:_update() - end -end \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/music-templates.lua b/tiny-cli/src/main/resources/sfx/music-templates.lua new file mode 100644 index 00000000..43b29f46 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/music-templates.lua @@ -0,0 +1,241 @@ +local M = {} + +local note_names = { "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" } + +local function pick(t) + return t[math.random(1, #t)] +end + +local function rand_int(min, max) + return math.random(min, max) +end + +local function make_note(semitone) + semitone = math.max(0, math.min(semitone, 95)) + local octave = math.floor(semitone / 12) + local note_index = (semitone % 12) + 1 + return note_names[note_index] .. octave +end + +local function note_name_to_index(name) + for i, n in ipairs(note_names) do + if n == name then return i - 1 end + end + return 0 +end + +M.scales = { + Major = { 0, 2, 4, 5, 7, 9, 11 }, + Minor = { 0, 2, 3, 5, 7, 8, 10 }, + ["Penta Maj"] = { 0, 2, 4, 7, 9 }, + ["Penta Min"] = { 0, 3, 5, 7, 10 }, + Dorian = { 0, 2, 3, 5, 7, 9, 10 }, + Mixolydian = { 0, 2, 4, 5, 7, 9, 10 }, +} + +M.scale_names = { "Major", "Minor", "Penta Maj", "Penta Min", "Dorian", "Mixolydian" } + +M.root_notes = { "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" } + +M.progressions = { + Classic = { 1, 5, 6, 4 }, + Melancholy = { 6, 4, 1, 5 }, + Dreamy = { 1, 4, 6, 5 }, + Tense = { 1, 7, 6, 5 }, + Upbeat = { 1, 4, 5, 4 }, + Cycle = { 2, 5, 1, 4 }, +} + +M.progression_names = { "Classic", "Melancholy", "Dreamy", "Tense", "Upbeat", "Cycle" } + +M.drum_patterns = { + Rock = { + kick = { 1, 0, 0, 0, 1, 0, 0, 0 }, + snare = { 0, 0, 1, 0, 0, 0, 1, 0 }, + hihat = { 1, 1, 1, 1, 1, 1, 1, 1 }, + }, + Dance = { + kick = { 1, 0, 1, 0, 1, 0, 1, 0 }, + snare = { 0, 0, 1, 0, 0, 0, 1, 0 }, + hihat = { 0, 1, 0, 1, 0, 1, 0, 1 }, + }, + Halftime = { + kick = { 1, 0, 0, 0, 0, 0, 0, 0 }, + snare = { 0, 0, 0, 0, 1, 0, 0, 0 }, + hihat = { 1, 0, 1, 0, 1, 0, 1, 0 }, + }, + Funky = { + kick = { 1, 0, 0, 1, 0, 0, 1, 0 }, + snare = { 0, 0, 1, 0, 0, 1, 0, 0 }, + hihat = { 1, 1, 0, 1, 1, 0, 1, 1 }, + }, + March = { + kick = { 1, 0, 1, 0, 1, 0, 1, 0 }, + snare = { 0, 1, 0, 1, 0, 1, 0, 1 }, + hihat = { 0, 0, 0, 0, 0, 0, 0, 0 }, + }, + Sparse = { + kick = { 1, 0, 0, 0, 0, 0, 0, 0 }, + snare = { 0, 0, 0, 0, 1, 0, 0, 0 }, + hihat = { 0, 0, 1, 0, 0, 0, 1, 0 }, + }, +} + +M.drum_pattern_names = { "Rock", "Dance", "Halftime", "Funky", "March", "Sparse" } + +M.lead_styles = { "Stepwise", "Arpeggiated", "Bouncy", "Sparse", "Random" } + +local function build_scale_notes(root_name, scale, octave) + local root_semi = note_name_to_index(root_name) + octave * 12 + local notes = {} + for _, interval in ipairs(scale) do + table.insert(notes, root_semi + interval) + end + return notes +end + +local function chord_root_semitone(root_name, scale, degree, octave) + local root_semi = note_name_to_index(root_name) + octave * 12 + local idx = ((degree - 1) % #scale) + 1 + return root_semi + scale[idx] +end + +local function build_chord_notes(root_name, scale, degree, octave) + local root = chord_root_semitone(root_name, scale, degree, octave) + local third_idx = ((degree - 1 + 2) % #scale) + 1 + local fifth_idx = ((degree - 1 + 4) % #scale) + 1 + local third = note_name_to_index(root_name) + octave * 12 + scale[third_idx] + local fifth = note_name_to_index(root_name) + octave * 12 + scale[fifth_idx] + if third <= root then third = third + 12 end + if fifth <= root then fifth = fifth + 12 end + return { root, third, fifth } +end + +local function generate_chords(track, config) + local scale = M.scales[config.scale_name] + local progression = M.progressions[config.progression_name] + track.clear() + track.instrument = config.chord_instrument or 0 + track.volume = config.chord_volume or 0.6 + + for bar = 0, 3 do + local degree = progression[(bar % #progression) + 1] + local chord = build_chord_notes(config.root, scale, degree, 3) + for i = 0, 7 do + local beat = bar * 8 + i + if beat < 33 then + local note_idx = (i % #chord) + 1 + track.set_note({ beat = beat, note = make_note(chord[note_idx]), volume = 0.5 }) + end + end + end +end + +local function generate_bass(track, config) + local scale = M.scales[config.scale_name] + local progression = M.progressions[config.progression_name] + track.clear() + track.instrument = config.bass_instrument or 4 + track.volume = config.bass_volume or 0.8 + + for bar = 0, 3 do + local degree = progression[(bar % #progression) + 1] + local root = chord_root_semitone(config.root, scale, degree, 2) + for i = 0, 7 do + local beat = bar * 8 + i + if beat < 33 then + if i == 0 or i == 4 then + track.set_note({ beat = beat, note = make_note(root), volume = 0.7 }) + elseif i == 2 or i == 6 then + local fifth_idx = ((degree - 1 + 4) % #scale) + 1 + local fifth = note_name_to_index(config.root) + 2 * 12 + scale[fifth_idx] + track.set_note({ beat = beat, note = make_note(fifth), volume = 0.5 }) + end + end + end + end +end + +local function generate_lead(track, config) + local scale = M.scales[config.scale_name] + local style = config.lead_style or "Stepwise" + track.clear() + track.instrument = config.lead_instrument or 5 + track.volume = config.lead_volume or 0.5 + + local scale_notes = build_scale_notes(config.root, scale, 4) + local pos = rand_int(1, #scale_notes) + + for beat = 0, 32 do + local play = false + if style == "Stepwise" then + play = true + pos = pos + pick({ -1, 0, 1 }) + elseif style == "Arpeggiated" then + play = true + pos = pos + pick({ 1, 2 }) + elseif style == "Bouncy" then + play = true + pos = pos + pick({ -2, -1, 1, 2, 3 }) + elseif style == "Sparse" then + play = (beat % 2 == 0) and (math.random() > 0.3) + if play then pos = pos + pick({ -1, 0, 1 }) end + elseif style == "Random" then + play = math.random() > 0.25 + if play then pos = rand_int(1, #scale_notes) end + end + + if pos < 1 then pos = pos + #scale_notes end + if pos > #scale_notes then pos = ((pos - 1) % #scale_notes) + 1 end + + if play and beat < 33 then + local semi = scale_notes[pos] + if semi > 95 then semi = semi - 12 end + if semi < 0 then semi = semi + 12 end + track.set_note({ beat = beat, note = make_note(semi), volume = 0.4 + math.random() * 0.2 }) + end + end +end + +local function generate_drums(track, config) + local pattern = M.drum_patterns[config.drum_pattern or "Rock"] + track.clear() + track.instrument = config.drum_instrument or 3 + track.volume = config.drum_volume or 0.7 + + local kick_note = "C2" + local snare_note = "C4" + local hihat_note = "C6" + + for bar = 0, 3 do + for i = 0, 7 do + local beat = bar * 8 + i + local pi = (i % #pattern.kick) + 1 + if beat < 33 then + if pattern.kick[pi] == 1 then + track.set_note({ beat = beat, note = kick_note, volume = 0.7 }) + elseif pattern.snare[pi] == 1 then + track.set_note({ beat = beat, note = snare_note, volume = 0.6 }) + elseif pattern.hihat[pi] == 1 then + track.set_note({ beat = beat, note = hihat_note, volume = 0.35 }) + end + end + end + end +end + +M.generate = function(seq, config) + seq.tempo = config.bpm or 120 + + local track0 = seq.track(0) + local track1 = seq.track(1) + local track2 = seq.track(2) + local track3 = seq.track(3) + + generate_chords(track0, config) + generate_bass(track1, config) + generate_lead(track2, config) + generate_drums(track3, config) +end + +return M diff --git a/tiny-cli/src/main/resources/sfx/sfx-editor.ldtk b/tiny-cli/src/main/resources/sfx/sfx-editor.ldtk index 772bd9f0..ba64a756 100644 --- a/tiny-cli/src/main/resources/sfx/sfx-editor.ldtk +++ b/tiny-cli/src/main/resources/sfx/sfx-editor.ldtk @@ -11,7 +11,7 @@ "iid": "89bab230-5e50-11f0-9e1e-fb2ec5447df7", "jsonVersion": "1.5.3", "appBuildId": 473703, - "nextUid": 101, + "nextUid": 105, "identifierStyle": "Capitalize", "toc": [], "worldLayout": "Free", @@ -2590,6 +2590,79 @@ "tilesetUid": null } ] + }, + { + "identifier": "Dropdown", + "uid": 103, + "tags": [], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 32, + "height": 8, + "resizableX": true, + "resizableY": true, + "minWidth": 8, + "maxWidth": null, + "minHeight": 8, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#C0CBDC", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Options", + "doc": null, + "__type": "Array", + "uid": 104, + "type": "F_String", + "isArray": true, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] } ], "tilesets": [ { @@ -2611,7 +2684,7 @@ "savedSelections": [], "cachedPixelData": { "opaqueTiles": "0000000000000011000000000000011100000000000000110000000000000111000000000000000000000000000001110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000001111111111110000000100000000000011111111111100000001000000000000000000000000000000010000000000000000000000000000000100000000000011111111111100000001000000000000111111111111000000010000000000000000000000000000000100000000000", - "averageColors": "bcbc0000bcdddccdbccddcbdbdbaddbabcebdcdabedcdedcbeeddeedfffdffec6cba6cba6cba0000000000000000000000000000000000000000fa98f988fa9800000000dbcdfbcddcbdfbadddbafda9dcdafbd9dedcfedcdeedfeedfacdfae91edb6cba1edb0000000000000000000000000000000000000000f988f657f988abaa00001f880f881fb81fb828a800000ff91ff91acd0acd1fad1fad000000000000000000000000000000000000000000000000000000000000fa98f988fa98000000000f882f881fb82fb818a838a81ff92ff90acd1acd1fad4fad0000000000000000000000000000000000000000000000000000000000006edb4edb6cbaaa992889aebb0000addb0000ddccdcaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000004edb00004988000000000000000000000000165727792657000000000000000000000000000000000000000000000000000000000000000000000000000000006cba49886988579600007ccc9cdd7cdb9cdb7cbc9ccd7dba9eba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009cddbcdd9cebbceb9ccdbccd9ebabfba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000657000016570000065716570000165700000657000000000000000000000000f2230000000000000000000000000000000000000000000000000000165700001657000000000000000000000000000000001657000016570000000000000000f65600000000000000000000000000000000000000000000000000009a763b863cbb8aaa00000000000000000000000000000000000000000000000000000000f79700000000000000000000000000000000000000000000000000003b86ed95abbb3cbb00000000000000000000000000000000000000000000000000000000fd770000000000000000000000000000000000000000000000000000eccdfccdeccdfcbdedbafdbaecdbfcdaeedcfedceeedfeed000000000000000000000000fb870000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9c80000000000000000000000000000000000000000000000000000eeed5bba0000000000000000000000000000000000000000000000000000000000000000fda70000000000000000000000000000000000000000000000000000ead857a70000000000000000000000000000000000000000000000000000000000000000fcd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fdd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f99c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd9c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2230000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f7970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd7700000000000000000000000000000000000000000000bdccd777d777d777d777dedcdddcd777d777d777d777d777d777bdcc000000000000000000000000fb8700000000000000000000000000000000000000000000dedcf887f887f887f887fffdfeedf887f887f887f887f887f887dedc000000000000000000000000f9c800000000000000000000000000000000000000000000dedcfffdfeedfffdfeedfffdfeedfffdfeedfeedfffdfffdfeeddedc000000000000000000000000fda700000000000000000000000000000000000000000000bdccdedcdddcdedcdddcdedcdddcdedcdddcdddcdedcdedcdddcbdcc000000000000000000000000fcd800000000000000000000000000000000000000000000b223d656d777dd88de88dda8dae9dec8dfc8ddcbddcbdcbddcadbfad000000000000000000000000fbaa00000000000000000000000000000000000000000000d223f656f777fd88fe88fda8fae9fec8ffc8fdcbfdcbfcbdfcaddfad000000000000000000000000fdd800000000000000000000000000000000000000000000d223f223f798f8a8fda8fda8fae9fae9fee9fee9fff9fff9ffaddfad000000000000000000000000f99c00000000000000000000000000000000000000000000b223d223d798d8a8dda8dda8dae9dae9dee9dee9dff9dff9dfadbfad000000000000000000000000fd9c00000000000000000000000000000000000000000000" + "averageColors": "bcbc0000bcdddccdbccddcbdbdbaddbabcebdcdabedcdedcbeeddeedfffdffec6cba6cba6cbabcba000000000000000000000000000000000000fa98f988fa9800000000dbcdfbcddcbdfbadddbafda9dcdafbd9dedcfedcdeedfeedfacdfae91edb6cba1edb3edb000000000000000000000000000000000000f988f657f988abaa00001f880f881fb81fb828a800000ff91ff91acd0acd1fad1fad000000000000000000000000000000000000000000000000000000000000fa98f988fa98000000000f882f881fb82fb818a838a81ff92ff90acd1acd1fad4fad0000000000000000000000000000000000000000000000000000000000006edb4edb6cbaaa992889aebb0000addb0000ddccdcaa0000000000000000000000000000000000000000000000000000000000000000000000000000000000004edb00004988000000000000000000000000165727792657000000000000000000000000000000000000000000000000000000000000000000000000000000006cba49886988579600007ccc9cdd7cdb9cdb7cbc9ccd7dba9eba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009cddbcdd9cebbceb9ccdbccd9ebabfba000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000657000016570000065716570000165700000657000000000000000000000000f2230000000000000000000000000000000000000000000000000000165700001657000000000000000000000000000000001657000016570000000000000000f65600000000000000000000000000000000000000000000000000009a753b853cbb8baaabaa3aa9000000000000000000000000000000000000000000000000f79700000000000000000000000000000000000000000000000000003b85ed94abba3cbb2bba9bba000000000000000000000000000000000000000000000000fd770000000000000000000000000000000000000000000000000000eccdfccdeccdfcbdedbafdbaecdbfcdaeedcfedceeedfeed000000000000000000000000fb87000000000000000000000000000000000000000000000000000000000000aa98000000000000000000000000000000000000000000000000000000000000f9c80000000000000000000000000000000000000000000000000000eeed5bbada98000000000000000000000000000000000000000000000000000000000000fda70000000000000000000000000000000000000000000000000000ead857a7dcb9000000000000000000000000000000000000000000000000000000000000fcd8000000000000000000000000000000000000000000000000000000000000da98000000000000000000000000000000000000000000000000000000000000fbaa000000000000000000000000000000000000000000000000000000000000dba9000000000000000000000000000000000000000000000000000000000000fdd8000000000000000000000000000000000000000000000000000000000000dba9000000000000000000000000000000000000000000000000000000000000f99c000000000000000000000000000000000000000000000000000000000000da98000000000000000000000000000000000000000000000000000000000000fd9c000000000000000000000000000000000000000000000000000000000000dcb9000000000000000000000000000000000000000000000000000000000000f223000000000000000000000000000000000000000000000000000000000000da98000000000000000000000000000000000000000000000000000000000000f656000000000000000000000000000000000000000000000000000000000000da98000000000000000000000000000000000000000000000000000000000000f797000000000000000000000000000000000000000000000000000000000000adba000000000000000000000000000000000000000000000000000000000000fd7700000000000000000000000000000000000000000000bdccd777d777d777d777dedcdddcd777d777d777d777d777d777bdcc000000000000000000000000fb8700000000000000000000000000000000000000000000dedcf887f887f887f887fffdfeedf887f887f887f887f887f887dedc000000000000000000000000f9c800000000000000000000000000000000000000000000dedcfffdfeedfffdfeedfffdfeedfffdfeedfeedfffdfffdfeeddedc000000000000000000000000fda700000000000000000000000000000000000000000000bdccdedcdddcdedcdddcdedcdddcdedcdddcdddcdedcdedcdddcbdcc000000000000000000000000fcd800000000000000000000000000000000000000000000b223d656d777dd88de88dda8dae9dec8dfc8ddcbddcbdcbddcadbfad000000000000000000000000fbaa00000000000000000000000000000000000000000000d223f656f777fd88fe88fda8fae9fec8ffc8fdcbfdcbfcbdfcaddfad000000000000000000000000fdd800000000000000000000000000000000000000000000d223f223f798f8a8fda8fda8fae9fae9fee9fee9fff9fff9ffaddfad000000000000000000000000f99c00000000000000000000000000000000000000000000b223d223d798d8a8dda8dda8dae9dae9dee9dee9dff9dff9dfadbfad000000000000000000000000fd9c00000000000000000000000000000000000000000000" } }, { @@ -2633,11 +2706,15 @@ "savedSelections": [], "cachedPixelData": { "opaqueTiles": "0000000100000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "averageColors": "3cbcdbcddcbdddbadcdadedcdeedfddc5cba2cba00000000000000007988f8782baa1f881fb818a81ff91acd2fad00000000000000000000000000005ba9aa9939992ebb2ddb7cab06570000000000000000000000000000000000002dcb398817969cdd9ceb9ccd9eba00000000000000000000000000000000000000000000000006570657065706570657065706570000000074450000000000000000000000007c856bbb00000000000000000000000000007a870000000000000000000000007ccd7cbd7dba7cda7edc7eed0000000000007aa70000000000000000000000009cda000000000000000000000000000000007cb70000000000000000000000000000000000000000000000000000000000007cb90000000000000000000000000000000000000000000000000000000000007b9c00000000000000000000000000000000000000000000000000000000000074450000000000000000000000000000000000000000000000000000000000007a8700000000000000000000dbaae877ecbaebbae877e877dbaa0000000000007aa700000000000000000000deedeeedeeedeeedeedceeeddedc0000000000007cb700000000000000000000d445eb77ee98ecd8eecaedccddad0000000000007cb900000000000000000000d223e8a8eda8eae9eee9efe9dfad0000000000007b9c00000000000000000000" + "averageColors": "3cbcdbcddcbdddbadcdadedcdeedfddc5cba5cba00000000000000007988f8782baa1f881fb818a81ff91acd2fad00000000000000000000000000005ba9aa9939992ebb2ddb7cab06570000000000000000000000000000000000002dcb398817969cdd9ceb9ccd9eba00000000000000000000000000000000000000000000000006570657065706570657065706570000000074450000000000000000000000007c856bba6baa0000000000000000000000007a870000000000000000000000007ccdacbc7dba7cda7edc7eed0000000000007aa70000000000000000000000009cda7ba900000000000000000000000000007cb700000000000000000000000000007a9800000000000000000000000000007cb900000000000000000000000000007ba800000000000000000000000000007b9c00000000000000000000000000007ba90000000000000000000000000000744500000000000000000000000000006ba900000000000000000000000000007a8700000000000000000000dbaae877ecbaebbae877e877dbaa0000000000007aa700000000000000000000deedeeedeeedeeedeedceeeddedc0000000000007cb700000000000000000000d445eb77ee98ecd8eecaedccddad0000000000007cb900000000000000000000d223e8a8eda8eae9eee9efe9dfad0000000000007b9c00000000000000000000" } } ], "enums": [ - { "identifier": "ModeType", "uid": 52, "values": [ { "id": "Instrument", "tileRect": { "tilesetUid": 4, "x": 16, "y": 80, "w": 16, "h": 16 }, "color": 12470831 }, { "id": "Sfx", "tileRect": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, "color": 14120515 } ], "iconTilesetUid": 4, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }, + { "identifier": "ModeType", "uid": 52, "values": [ + { "id": "Instrument", "tileRect": { "tilesetUid": 4, "x": 16, "y": 80, "w": 16, "h": 16 }, "color": 12470831 }, + { "id": "Sfx", "tileRect": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, "color": 14120515 }, + { "id": "Music", "tileRect": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, "color": 15389866 } + ], "iconTilesetUid": 4, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }, { "identifier": "ButtonOverlay", "uid": 53, "values": [ { "id": "Sine", "tileRect": { "tilesetUid": 4, "x": 16, "y": 16, "w": 16, "h": 16 }, "color": 12470831 }, { "id": "Square", "tileRect": { "tilesetUid": 4, "x": 96, "y": 16, "w": 16, "h": 16 }, "color": 14120515 }, @@ -3266,7 +3343,7 @@ }, { "__identifier": "ModeButton", - "__grid": [7,0], + "__grid": [5,0], "__pivot": [0,0], "__tags": [], "__tile": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, @@ -3275,7 +3352,7 @@ "width": 16, "height": 16, "defUid": 51, - "px": [56,0], + "px": [40,0], "fieldInstances": [ { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Sfx", "__tile": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ "id": "V_String", @@ -3283,7 +3360,7 @@ }] }, { "__identifier": "IsSelected", "__type": "Bool", "__value": false, "__tile": null, "defUid": 55, "realEditorValues": [] } ], - "__worldX": 56, + "__worldX": 40, "__worldY": 0 }, { @@ -3544,6 +3621,28 @@ ], "__worldX": 120, "__worldY": 200 + }, + { + "__identifier": "ModeButton", + "__grid": [8,0], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, + "__smartColor": "#3E2731", + "iid": "92acff10-ac70-11f0-b965-79ec8c2bb1c8", + "width": 16, + "height": 16, + "defUid": 51, + "px": [64,0], + "fieldInstances": [ + { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Music", "__tile": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ + "id": "V_String", + "params": ["Music"] + }] }, + { "__identifier": "IsSelected", "__type": "Bool", "__value": false, "__tile": null, "defUid": 55, "realEditorValues": [] } + ], + "__worldX": 64, + "__worldY": 0 } ] }, @@ -3566,44 +3665,7 @@ "visible": true, "optionalRules": [], "intGridCsv": [], - "autoLayerTiles": [ - { "px": [16,0], "src": [240,8], "f": 0, "t": 62, "d": [43,2], "a": 1 }, - { "px": [24,0], "src": [240,8], "f": 0, "t": 62, "d": [43,3], "a": 1 }, - { "px": [56,0], "src": [240,8], "f": 0, "t": 62, "d": [43,7], "a": 1 }, - { "px": [64,0], "src": [240,8], "f": 0, "t": 62, "d": [43,8], "a": 1 }, - { "px": [96,0], "src": [240,8], "f": 0, "t": 62, "d": [43,12], "a": 1 }, - { "px": [104,0], "src": [240,8], "f": 0, "t": 62, "d": [43,13], "a": 1 }, - { "px": [16,8], "src": [240,8], "f": 0, "t": 62, "d": [43,50], "a": 1 }, - { "px": [24,8], "src": [240,8], "f": 0, "t": 62, "d": [43,51], "a": 1 }, - { "px": [56,8], "src": [240,8], "f": 0, "t": 62, "d": [43,55], "a": 1 }, - { "px": [64,8], "src": [240,8], "f": 0, "t": 62, "d": [43,56], "a": 1 }, - { "px": [96,8], "src": [240,8], "f": 0, "t": 62, "d": [43,60], "a": 1 }, - { "px": [104,8], "src": [240,8], "f": 0, "t": 62, "d": [43,61], "a": 1 }, - { "px": [8,0], "src": [128,0], "f": 0, "t": 16, "d": [42,1], "a": 1 }, - { "px": [48,0], "src": [128,0], "f": 0, "t": 16, "d": [42,6], "a": 1 }, - { "px": [88,0], "src": [128,0], "f": 0, "t": 16, "d": [42,11], "a": 1 }, - { "px": [8,8], "src": [128,0], "f": 0, "t": 16, "d": [42,49], "a": 1 }, - { "px": [48,8], "src": [128,0], "f": 0, "t": 16, "d": [42,54], "a": 1 }, - { "px": [88,8], "src": [128,0], "f": 0, "t": 16, "d": [42,59], "a": 1 }, - { "px": [16,16], "src": [136,8], "f": 0, "t": 49, "d": [41,98], "a": 1 }, - { "px": [24,16], "src": [136,8], "f": 0, "t": 49, "d": [41,99], "a": 1 }, - { "px": [56,16], "src": [136,8], "f": 0, "t": 49, "d": [41,103], "a": 1 }, - { "px": [64,16], "src": [136,8], "f": 0, "t": 49, "d": [41,104], "a": 1 }, - { "px": [96,16], "src": [136,8], "f": 0, "t": 49, "d": [41,108], "a": 1 }, - { "px": [104,16], "src": [136,8], "f": 0, "t": 49, "d": [41,109], "a": 1 }, - { "px": [32,0], "src": [144,0], "f": 0, "t": 18, "d": [40,4], "a": 1 }, - { "px": [72,0], "src": [144,0], "f": 0, "t": 18, "d": [40,9], "a": 1 }, - { "px": [112,0], "src": [144,0], "f": 0, "t": 18, "d": [40,14], "a": 1 }, - { "px": [32,8], "src": [144,0], "f": 0, "t": 18, "d": [40,52], "a": 1 }, - { "px": [72,8], "src": [144,0], "f": 0, "t": 18, "d": [40,57], "a": 1 }, - { "px": [112,8], "src": [144,0], "f": 0, "t": 18, "d": [40,62], "a": 1 }, - { "px": [8,16], "src": [128,8], "f": 0, "t": 48, "d": [38,97], "a": 1 }, - { "px": [48,16], "src": [128,8], "f": 0, "t": 48, "d": [38,102], "a": 1 }, - { "px": [88,16], "src": [128,8], "f": 0, "t": 48, "d": [38,107], "a": 1 }, - { "px": [32,16], "src": [144,8], "f": 0, "t": 50, "d": [37,100], "a": 1 }, - { "px": [72,16], "src": [144,8], "f": 0, "t": 50, "d": [37,105], "a": 1 }, - { "px": [112,16], "src": [144,8], "f": 0, "t": 50, "d": [37,110], "a": 1 } - ], + "autoLayerTiles": [], "seed": 779283, "overrideTilesetUid": null, "gridTiles": [], @@ -4468,10 +4530,10 @@ "visible": true, "optionalRules": [], "intGridCsv": [ - 0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,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,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,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,0,1,1,1,1,0,1,1,1, - 1,0,1,1,1,1,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,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,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, + 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,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,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,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,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,0,0,0,0,0,0,0,0,0,0, @@ -4614,15 +4676,19 @@ "gridTiles": [ { "px": [0,0], "src": [232,0], "f": 0, "t": 29, "d": [0], "a": 1 }, { "px": [8,0], "src": [240,0], "f": 0, "t": 30, "d": [1], "a": 1 }, + { "px": [8,0], "src": [128,0], "f": 0, "t": 16, "d": [1], "a": 1 }, { "px": [16,0], "src": [240,0], "f": 0, "t": 30, "d": [2], "a": 1 }, { "px": [24,0], "src": [240,0], "f": 0, "t": 30, "d": [3], "a": 1 }, { "px": [32,0], "src": [240,0], "f": 0, "t": 30, "d": [4], "a": 1 }, + { "px": [32,0], "src": [152,0], "f": 0, "t": 19, "d": [4], "a": 1 }, { "px": [40,0], "src": [240,0], "f": 0, "t": 30, "d": [5], "a": 1 }, { "px": [48,0], "src": [240,0], "f": 0, "t": 30, "d": [6], "a": 1 }, { "px": [56,0], "src": [240,0], "f": 0, "t": 30, "d": [7], "a": 1 }, + { "px": [56,0], "src": [152,0], "f": 0, "t": 19, "d": [7], "a": 1 }, { "px": [64,0], "src": [240,0], "f": 0, "t": 30, "d": [8], "a": 1 }, { "px": [72,0], "src": [240,0], "f": 0, "t": 30, "d": [9], "a": 1 }, { "px": [80,0], "src": [240,0], "f": 0, "t": 30, "d": [10], "a": 1 }, + { "px": [80,0], "src": [144,0], "f": 0, "t": 18, "d": [10], "a": 1 }, { "px": [88,0], "src": [240,0], "f": 0, "t": 30, "d": [11], "a": 1 }, { "px": [96,0], "src": [240,0], "f": 0, "t": 30, "d": [12], "a": 1 }, { "px": [104,0], "src": [240,0], "f": 0, "t": 30, "d": [13], "a": 1 }, @@ -4662,15 +4728,19 @@ { "px": [376,0], "src": [248,0], "f": 0, "t": 31, "d": [47], "a": 1 }, { "px": [0,8], "src": [232,8], "f": 0, "t": 61, "d": [48], "a": 1 }, { "px": [8,8], "src": [240,8], "f": 0, "t": 62, "d": [49], "a": 1 }, + { "px": [8,8], "src": [128,0], "f": 0, "t": 16, "d": [49], "a": 1 }, { "px": [16,8], "src": [240,8], "f": 0, "t": 62, "d": [50], "a": 1 }, { "px": [24,8], "src": [240,8], "f": 0, "t": 62, "d": [51], "a": 1 }, { "px": [32,8], "src": [240,8], "f": 0, "t": 62, "d": [52], "a": 1 }, + { "px": [32,8], "src": [152,0], "f": 0, "t": 19, "d": [52], "a": 1 }, { "px": [40,8], "src": [240,8], "f": 0, "t": 62, "d": [53], "a": 1 }, { "px": [48,8], "src": [240,8], "f": 0, "t": 62, "d": [54], "a": 1 }, { "px": [56,8], "src": [240,8], "f": 0, "t": 62, "d": [55], "a": 1 }, + { "px": [56,8], "src": [152,0], "f": 0, "t": 19, "d": [55], "a": 1 }, { "px": [64,8], "src": [240,8], "f": 0, "t": 62, "d": [56], "a": 1 }, { "px": [72,8], "src": [240,8], "f": 0, "t": 62, "d": [57], "a": 1 }, { "px": [80,8], "src": [240,8], "f": 0, "t": 62, "d": [58], "a": 1 }, + { "px": [80,8], "src": [144,0], "f": 0, "t": 18, "d": [58], "a": 1 }, { "px": [88,8], "src": [240,8], "f": 0, "t": 62, "d": [59], "a": 1 }, { "px": [96,8], "src": [240,8], "f": 0, "t": 62, "d": [60], "a": 1 }, { "px": [104,8], "src": [240,8], "f": 0, "t": 62, "d": [61], "a": 1 }, @@ -4710,15 +4780,26 @@ { "px": [376,8], "src": [248,8], "f": 0, "t": 63, "d": [95], "a": 1 }, { "px": [0,16], "src": [232,8], "f": 0, "t": 61, "d": [96], "a": 1 }, { "px": [8,16], "src": [240,8], "f": 0, "t": 62, "d": [97], "a": 1 }, + { "px": [8,16], "src": [128,8], "f": 0, "t": 48, "d": [97], "a": 1 }, { "px": [16,16], "src": [240,8], "f": 0, "t": 62, "d": [98], "a": 1 }, + { "px": [16,16], "src": [136,8], "f": 0, "t": 49, "d": [98], "a": 1 }, { "px": [24,16], "src": [240,8], "f": 0, "t": 62, "d": [99], "a": 1 }, + { "px": [24,16], "src": [136,8], "f": 0, "t": 49, "d": [99], "a": 1 }, { "px": [32,16], "src": [240,8], "f": 0, "t": 62, "d": [100], "a": 1 }, + { "px": [32,16], "src": [144,8], "f": 0, "t": 50, "d": [100], "a": 1 }, + { "px": [32,16], "src": [152,8], "f": 0, "t": 51, "d": [100], "a": 1 }, { "px": [40,16], "src": [240,8], "f": 0, "t": 62, "d": [101], "a": 1 }, + { "px": [40,16], "src": [136,8], "f": 0, "t": 49, "d": [101], "a": 1 }, { "px": [48,16], "src": [240,8], "f": 0, "t": 62, "d": [102], "a": 1 }, + { "px": [48,16], "src": [136,8], "f": 0, "t": 49, "d": [102], "a": 1 }, { "px": [56,16], "src": [240,8], "f": 0, "t": 62, "d": [103], "a": 1 }, + { "px": [56,16], "src": [152,8], "f": 0, "t": 51, "d": [103], "a": 1 }, { "px": [64,16], "src": [240,8], "f": 0, "t": 62, "d": [104], "a": 1 }, + { "px": [64,16], "src": [136,8], "f": 0, "t": 49, "d": [104], "a": 1 }, { "px": [72,16], "src": [240,8], "f": 0, "t": 62, "d": [105], "a": 1 }, + { "px": [72,16], "src": [136,8], "f": 0, "t": 49, "d": [105], "a": 1 }, { "px": [80,16], "src": [240,8], "f": 0, "t": 62, "d": [106], "a": 1 }, + { "px": [80,16], "src": [144,8], "f": 0, "t": 50, "d": [106], "a": 1 }, { "px": [88,16], "src": [240,8], "f": 0, "t": 62, "d": [107], "a": 1 }, { "px": [96,16], "src": [240,8], "f": 0, "t": 62, "d": [108], "a": 1 }, { "px": [104,16], "src": [240,8], "f": 0, "t": 62, "d": [109], "a": 1 }, @@ -6245,7 +6326,7 @@ }, { "__identifier": "ModeButton", - "__grid": [7,0], + "__grid": [5,0], "__pivot": [0,0], "__tags": [], "__tile": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, @@ -6254,7 +6335,7 @@ "width": 16, "height": 16, "defUid": 51, - "px": [56,0], + "px": [40,0], "fieldInstances": [ { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Sfx", "__tile": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ "id": "V_String", @@ -6265,7 +6346,7 @@ "params": [ true ] }] } ], - "__worldX": 472, + "__worldX": 456, "__worldY": 0 }, { @@ -6499,6 +6580,28 @@ }] }], "__worldX": 536, "__worldY": 8 + }, + { + "__identifier": "ModeButton", + "__grid": [8,0], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, + "__smartColor": "#3E2731", + "iid": "7b923b60-ac70-11f0-b965-0f74af3d5fb5", + "width": 16, + "height": 16, + "defUid": 51, + "px": [64,0], + "fieldInstances": [ + { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Music", "__tile": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ + "id": "V_String", + "params": ["Music"] + }] }, + { "__identifier": "IsSelected", "__type": "Bool", "__value": false, "__tile": null, "defUid": 55, "realEditorValues": [] } + ], + "__worldX": 480, + "__worldY": 0 } ] }, @@ -6521,44 +6624,7 @@ "visible": true, "optionalRules": [], "intGridCsv": [], - "autoLayerTiles": [ - { "px": [16,0], "src": [240,8], "f": 0, "t": 62, "d": [43,2], "a": 1 }, - { "px": [24,0], "src": [240,8], "f": 0, "t": 62, "d": [43,3], "a": 1 }, - { "px": [56,0], "src": [240,8], "f": 0, "t": 62, "d": [43,7], "a": 1 }, - { "px": [64,0], "src": [240,8], "f": 0, "t": 62, "d": [43,8], "a": 1 }, - { "px": [96,0], "src": [240,8], "f": 0, "t": 62, "d": [43,12], "a": 1 }, - { "px": [104,0], "src": [240,8], "f": 0, "t": 62, "d": [43,13], "a": 1 }, - { "px": [16,8], "src": [240,8], "f": 0, "t": 62, "d": [43,50], "a": 1 }, - { "px": [24,8], "src": [240,8], "f": 0, "t": 62, "d": [43,51], "a": 1 }, - { "px": [56,8], "src": [240,8], "f": 0, "t": 62, "d": [43,55], "a": 1 }, - { "px": [64,8], "src": [240,8], "f": 0, "t": 62, "d": [43,56], "a": 1 }, - { "px": [96,8], "src": [240,8], "f": 0, "t": 62, "d": [43,60], "a": 1 }, - { "px": [104,8], "src": [240,8], "f": 0, "t": 62, "d": [43,61], "a": 1 }, - { "px": [8,0], "src": [128,0], "f": 0, "t": 16, "d": [42,1], "a": 1 }, - { "px": [48,0], "src": [128,0], "f": 0, "t": 16, "d": [42,6], "a": 1 }, - { "px": [88,0], "src": [128,0], "f": 0, "t": 16, "d": [42,11], "a": 1 }, - { "px": [8,8], "src": [128,0], "f": 0, "t": 16, "d": [42,49], "a": 1 }, - { "px": [48,8], "src": [128,0], "f": 0, "t": 16, "d": [42,54], "a": 1 }, - { "px": [88,8], "src": [128,0], "f": 0, "t": 16, "d": [42,59], "a": 1 }, - { "px": [16,16], "src": [136,8], "f": 0, "t": 49, "d": [41,98], "a": 1 }, - { "px": [24,16], "src": [136,8], "f": 0, "t": 49, "d": [41,99], "a": 1 }, - { "px": [56,16], "src": [136,8], "f": 0, "t": 49, "d": [41,103], "a": 1 }, - { "px": [64,16], "src": [136,8], "f": 0, "t": 49, "d": [41,104], "a": 1 }, - { "px": [96,16], "src": [136,8], "f": 0, "t": 49, "d": [41,108], "a": 1 }, - { "px": [104,16], "src": [136,8], "f": 0, "t": 49, "d": [41,109], "a": 1 }, - { "px": [32,0], "src": [144,0], "f": 0, "t": 18, "d": [40,4], "a": 1 }, - { "px": [72,0], "src": [144,0], "f": 0, "t": 18, "d": [40,9], "a": 1 }, - { "px": [112,0], "src": [144,0], "f": 0, "t": 18, "d": [40,14], "a": 1 }, - { "px": [32,8], "src": [144,0], "f": 0, "t": 18, "d": [40,52], "a": 1 }, - { "px": [72,8], "src": [144,0], "f": 0, "t": 18, "d": [40,57], "a": 1 }, - { "px": [112,8], "src": [144,0], "f": 0, "t": 18, "d": [40,62], "a": 1 }, - { "px": [8,16], "src": [128,8], "f": 0, "t": 48, "d": [38,97], "a": 1 }, - { "px": [48,16], "src": [128,8], "f": 0, "t": 48, "d": [38,102], "a": 1 }, - { "px": [88,16], "src": [128,8], "f": 0, "t": 48, "d": [38,107], "a": 1 }, - { "px": [32,16], "src": [144,8], "f": 0, "t": 50, "d": [37,100], "a": 1 }, - { "px": [72,16], "src": [144,8], "f": 0, "t": 50, "d": [37,105], "a": 1 }, - { "px": [112,16], "src": [144,8], "f": 0, "t": 50, "d": [37,110], "a": 1 } - ], + "autoLayerTiles": [{ "px": [16,0], "src": [144,8], "f": 0, "t": 50, "d": [37,2], "a": 1 }], "seed": 779283, "overrideTilesetUid": null, "gridTiles": [], @@ -7661,10 +7727,10 @@ "visible": true, "optionalRules": [], "intGridCsv": [ - 0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,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,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,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,0,1,1,1,1,0,1,1,1, - 1,0,1,1,1,1,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,1,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,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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0, @@ -7807,15 +7873,20 @@ "gridTiles": [ { "px": [0,0], "src": [232,0], "f": 0, "t": 29, "d": [0], "a": 1 }, { "px": [8,0], "src": [240,0], "f": 0, "t": 30, "d": [1], "a": 1 }, + { "px": [8,0], "src": [128,0], "f": 0, "t": 16, "d": [1], "a": 1 }, { "px": [16,0], "src": [240,0], "f": 0, "t": 30, "d": [2], "a": 1 }, { "px": [24,0], "src": [240,0], "f": 0, "t": 30, "d": [3], "a": 1 }, { "px": [32,0], "src": [240,0], "f": 0, "t": 30, "d": [4], "a": 1 }, + { "px": [32,0], "src": [144,0], "f": 0, "t": 18, "d": [4], "a": 1 }, + { "px": [32,0], "src": [152,0], "f": 0, "t": 19, "d": [4], "a": 1 }, { "px": [40,0], "src": [240,0], "f": 0, "t": 30, "d": [5], "a": 1 }, { "px": [48,0], "src": [240,0], "f": 0, "t": 30, "d": [6], "a": 1 }, { "px": [56,0], "src": [240,0], "f": 0, "t": 30, "d": [7], "a": 1 }, + { "px": [56,0], "src": [152,0], "f": 0, "t": 19, "d": [7], "a": 1 }, { "px": [64,0], "src": [240,0], "f": 0, "t": 30, "d": [8], "a": 1 }, { "px": [72,0], "src": [240,0], "f": 0, "t": 30, "d": [9], "a": 1 }, { "px": [80,0], "src": [240,0], "f": 0, "t": 30, "d": [10], "a": 1 }, + { "px": [80,0], "src": [144,0], "f": 0, "t": 18, "d": [10], "a": 1 }, { "px": [88,0], "src": [240,0], "f": 0, "t": 30, "d": [11], "a": 1 }, { "px": [96,0], "src": [240,0], "f": 0, "t": 30, "d": [12], "a": 1 }, { "px": [104,0], "src": [240,0], "f": 0, "t": 30, "d": [13], "a": 1 }, @@ -7855,15 +7926,19 @@ { "px": [376,0], "src": [248,0], "f": 0, "t": 31, "d": [47], "a": 1 }, { "px": [0,8], "src": [232,8], "f": 0, "t": 61, "d": [48], "a": 1 }, { "px": [8,8], "src": [240,8], "f": 0, "t": 62, "d": [49], "a": 1 }, + { "px": [8,8], "src": [128,0], "f": 0, "t": 16, "d": [49], "a": 1 }, { "px": [16,8], "src": [240,8], "f": 0, "t": 62, "d": [50], "a": 1 }, { "px": [24,8], "src": [240,8], "f": 0, "t": 62, "d": [51], "a": 1 }, { "px": [32,8], "src": [240,8], "f": 0, "t": 62, "d": [52], "a": 1 }, + { "px": [32,8], "src": [152,0], "f": 0, "t": 19, "d": [52], "a": 1 }, { "px": [40,8], "src": [240,8], "f": 0, "t": 62, "d": [53], "a": 1 }, { "px": [48,8], "src": [240,8], "f": 0, "t": 62, "d": [54], "a": 1 }, { "px": [56,8], "src": [240,8], "f": 0, "t": 62, "d": [55], "a": 1 }, + { "px": [56,8], "src": [152,0], "f": 0, "t": 19, "d": [55], "a": 1 }, { "px": [64,8], "src": [240,8], "f": 0, "t": 62, "d": [56], "a": 1 }, { "px": [72,8], "src": [240,8], "f": 0, "t": 62, "d": [57], "a": 1 }, { "px": [80,8], "src": [240,8], "f": 0, "t": 62, "d": [58], "a": 1 }, + { "px": [80,8], "src": [144,0], "f": 0, "t": 18, "d": [58], "a": 1 }, { "px": [88,8], "src": [240,8], "f": 0, "t": 62, "d": [59], "a": 1 }, { "px": [96,8], "src": [240,8], "f": 0, "t": 62, "d": [60], "a": 1 }, { "px": [104,8], "src": [240,8], "f": 0, "t": 62, "d": [61], "a": 1 }, @@ -7903,15 +7978,25 @@ { "px": [376,8], "src": [248,8], "f": 0, "t": 63, "d": [95], "a": 1 }, { "px": [0,16], "src": [232,8], "f": 0, "t": 61, "d": [96], "a": 1 }, { "px": [8,16], "src": [240,8], "f": 0, "t": 62, "d": [97], "a": 1 }, + { "px": [8,16], "src": [128,8], "f": 0, "t": 48, "d": [97], "a": 1 }, { "px": [16,16], "src": [240,8], "f": 0, "t": 62, "d": [98], "a": 1 }, + { "px": [16,16], "src": [136,8], "f": 0, "t": 49, "d": [98], "a": 1 }, { "px": [24,16], "src": [240,8], "f": 0, "t": 62, "d": [99], "a": 1 }, + { "px": [24,16], "src": [136,8], "f": 0, "t": 49, "d": [99], "a": 1 }, { "px": [32,16], "src": [240,8], "f": 0, "t": 62, "d": [100], "a": 1 }, + { "px": [32,16], "src": [152,8], "f": 0, "t": 51, "d": [100], "a": 1 }, { "px": [40,16], "src": [240,8], "f": 0, "t": 62, "d": [101], "a": 1 }, + { "px": [40,16], "src": [136,8], "f": 0, "t": 49, "d": [101], "a": 1 }, { "px": [48,16], "src": [240,8], "f": 0, "t": 62, "d": [102], "a": 1 }, + { "px": [48,16], "src": [136,8], "f": 0, "t": 49, "d": [102], "a": 1 }, { "px": [56,16], "src": [240,8], "f": 0, "t": 62, "d": [103], "a": 1 }, + { "px": [56,16], "src": [152,8], "f": 0, "t": 51, "d": [103], "a": 1 }, { "px": [64,16], "src": [240,8], "f": 0, "t": 62, "d": [104], "a": 1 }, + { "px": [64,16], "src": [136,8], "f": 0, "t": 49, "d": [104], "a": 1 }, { "px": [72,16], "src": [240,8], "f": 0, "t": 62, "d": [105], "a": 1 }, + { "px": [72,16], "src": [136,8], "f": 0, "t": 49, "d": [105], "a": 1 }, { "px": [80,16], "src": [240,8], "f": 0, "t": 62, "d": [106], "a": 1 }, + { "px": [80,16], "src": [144,8], "f": 0, "t": 50, "d": [106], "a": 1 }, { "px": [88,16], "src": [240,8], "f": 0, "t": 62, "d": [107], "a": 1 }, { "px": [96,16], "src": [240,8], "f": 0, "t": 62, "d": [108], "a": 1 }, { "px": [104,16], "src": [240,8], "f": 0, "t": 62, "d": [109], "a": 1 }, @@ -9345,6 +9430,3032 @@ "entityInstances": [] } ], + "__neighbours": [{ "levelIid": "02014050-ac70-11f0-b965-b77bcb31d2fb", "dir": "e" }] + }, + { + "identifier": "MusicEditor", + "iid": "02014050-ac70-11f0-b965-b77bcb31d2fb", + "uid": 101, + "worldX": 800, + "worldY": 0, + "worldDepth": 0, + "pxWid": 384, + "pxHei": 256, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "02014051-ac70-11f0-b965-7f1e80bb9fba", + "levelId": 101, + "layerDefUid": 50, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 2345701, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "ModeButton", + "__grid": [2,0], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 4, "x": 16, "y": 80, "w": 16, "h": 16 }, + "__smartColor": "#3E2731", + "iid": "63de3300-ac70-11f0-b965-a3afa9fa9c49", + "width": 16, + "height": 16, + "defUid": 51, + "px": [16,0], + "fieldInstances": [ + { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Instrument", "__tile": { "tilesetUid": 4, "x": 16, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ + "id": "V_String", + "params": ["Instrument"] + }] }, + { "__identifier": "IsSelected", "__type": "Bool", "__value": false, "__tile": null, "defUid": 55, "realEditorValues": [] } + ], + "__worldX": 816, + "__worldY": 0 + }, + { + "__identifier": "ModeButton", + "__grid": [5,0], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, + "__smartColor": "#3E2731", + "iid": "646defe0-ac70-11f0-b965-178011f5a02f", + "width": 16, + "height": 16, + "defUid": 51, + "px": [40,0], + "fieldInstances": [ + { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Sfx", "__tile": { "tilesetUid": 4, "x": 32, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ + "id": "V_String", + "params": ["Sfx"] + }] }, + { "__identifier": "IsSelected", "__type": "Bool", "__value": false, "__tile": null, "defUid": 55, "realEditorValues": [] } + ], + "__worldX": 840, + "__worldY": 0 + }, + { + "__identifier": "ModeButton", + "__grid": [8,0], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, + "__smartColor": "#3E2731", + "iid": "64e91350-ac70-11f0-b965-15d054181927", + "width": 16, + "height": 16, + "defUid": 51, + "px": [64,0], + "fieldInstances": [ + { "__identifier": "ModeType", "__type": "LocalEnum.ModeType", "__value": "Music", "__tile": { "tilesetUid": 4, "x": 48, "y": 80, "w": 16, "h": 16 }, "defUid": 54, "realEditorValues": [{ + "id": "V_String", + "params": ["Music"] + }] }, + { "__identifier": "IsSelected", "__type": "Bool", "__value": true, "__tile": null, "defUid": 55, "realEditorValues": [{ + "id": "V_Bool", + "params": [ true ] + }] } + ], + "__worldX": 864, + "__worldY": 0 + } + ] + }, + { + "__identifier": "MenuLayer", + "__type": "AutoLayer", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "02014052-ac70-11f0-b965-3d995a7c4d2b", + "levelId": 101, + "layerDefUid": 29, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 3294692, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "WidgetLayer", + "__type": "AutoLayer", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "02014053-ac70-11f0-b965-5b47e975b3ba", + "levelId": 101, + "layerDefUid": 5, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [ + { "px": [104,32], "src": [240,32], "f": 0, "t": 158, "d": [15,205], "a": 1 }, + { "px": [112,32], "src": [240,32], "f": 0, "t": 158, "d": [15,206], "a": 1 }, + { "px": [120,32], "src": [240,32], "f": 0, "t": 158, "d": [15,207], "a": 1 }, + { "px": [128,32], "src": [240,32], "f": 0, "t": 158, "d": [15,208], "a": 1 }, + { "px": [136,32], "src": [240,32], "f": 0, "t": 158, "d": [15,209], "a": 1 }, + { "px": [144,32], "src": [240,32], "f": 0, "t": 158, "d": [15,210], "a": 1 }, + { "px": [152,32], "src": [240,32], "f": 0, "t": 158, "d": [15,211], "a": 1 }, + { "px": [160,32], "src": [240,32], "f": 0, "t": 158, "d": [15,212], "a": 1 }, + { "px": [168,32], "src": [240,32], "f": 0, "t": 158, "d": [15,213], "a": 1 }, + { "px": [176,32], "src": [240,32], "f": 0, "t": 158, "d": [15,214], "a": 1 }, + { "px": [184,32], "src": [240,32], "f": 0, "t": 158, "d": [15,215], "a": 1 }, + { "px": [192,32], "src": [240,32], "f": 0, "t": 158, "d": [15,216], "a": 1 }, + { "px": [200,32], "src": [240,32], "f": 0, "t": 158, "d": [15,217], "a": 1 }, + { "px": [208,32], "src": [240,32], "f": 0, "t": 158, "d": [15,218], "a": 1 }, + { "px": [216,32], "src": [240,32], "f": 0, "t": 158, "d": [15,219], "a": 1 }, + { "px": [224,32], "src": [240,32], "f": 0, "t": 158, "d": [15,220], "a": 1 }, + { "px": [232,32], "src": [240,32], "f": 0, "t": 158, "d": [15,221], "a": 1 }, + { "px": [240,32], "src": [240,32], "f": 0, "t": 158, "d": [15,222], "a": 1 }, + { "px": [248,32], "src": [240,32], "f": 0, "t": 158, "d": [15,223], "a": 1 }, + { "px": [256,32], "src": [240,32], "f": 0, "t": 158, "d": [15,224], "a": 1 }, + { "px": [264,32], "src": [240,32], "f": 0, "t": 158, "d": [15,225], "a": 1 }, + { "px": [272,32], "src": [240,32], "f": 0, "t": 158, "d": [15,226], "a": 1 }, + { "px": [280,32], "src": [240,32], "f": 0, "t": 158, "d": [15,227], "a": 1 }, + { "px": [288,32], "src": [240,32], "f": 0, "t": 158, "d": [15,228], "a": 1 }, + { "px": [296,32], "src": [240,32], "f": 0, "t": 158, "d": [15,229], "a": 1 }, + { "px": [304,32], "src": [240,32], "f": 0, "t": 158, "d": [15,230], "a": 1 }, + { "px": [336,32], "src": [240,32], "f": 0, "t": 158, "d": [15,234], "a": 1 }, + { "px": [344,32], "src": [240,32], "f": 0, "t": 158, "d": [15,235], "a": 1 }, + { "px": [352,32], "src": [240,32], "f": 0, "t": 158, "d": [15,236], "a": 1 }, + { "px": [360,32], "src": [240,32], "f": 0, "t": 158, "d": [15,237], "a": 1 }, + { "px": [104,40], "src": [240,32], "f": 0, "t": 158, "d": [15,253], "a": 1 }, + { "px": [112,40], "src": [240,32], "f": 0, "t": 158, "d": [15,254], "a": 1 }, + { "px": [120,40], "src": [240,32], "f": 0, "t": 158, "d": [15,255], "a": 1 }, + { "px": [128,40], "src": [240,32], "f": 0, "t": 158, "d": [15,256], "a": 1 }, + { "px": [136,40], "src": [240,32], "f": 0, "t": 158, "d": [15,257], "a": 1 }, + { "px": [144,40], "src": [240,32], "f": 0, "t": 158, "d": [15,258], "a": 1 }, + { "px": [152,40], "src": [240,32], "f": 0, "t": 158, "d": [15,259], "a": 1 }, + { "px": [160,40], "src": [240,32], "f": 0, "t": 158, "d": [15,260], "a": 1 }, + { "px": [168,40], "src": [240,32], "f": 0, "t": 158, "d": [15,261], "a": 1 }, + { "px": [176,40], "src": [240,32], "f": 0, "t": 158, "d": [15,262], "a": 1 }, + { "px": [184,40], "src": [240,32], "f": 0, "t": 158, "d": [15,263], "a": 1 }, + { "px": [192,40], "src": [240,32], "f": 0, "t": 158, "d": [15,264], "a": 1 }, + { "px": [200,40], "src": [240,32], "f": 0, "t": 158, "d": [15,265], "a": 1 }, + { "px": [208,40], "src": [240,32], "f": 0, "t": 158, "d": [15,266], "a": 1 }, + { "px": [216,40], "src": [240,32], "f": 0, "t": 158, "d": [15,267], "a": 1 }, + { "px": [224,40], "src": [240,32], "f": 0, "t": 158, "d": [15,268], "a": 1 }, + { "px": [232,40], "src": [240,32], "f": 0, "t": 158, "d": [15,269], "a": 1 }, + { "px": [240,40], "src": [240,32], "f": 0, "t": 158, "d": [15,270], "a": 1 }, + { "px": [248,40], "src": [240,32], "f": 0, "t": 158, "d": [15,271], "a": 1 }, + { "px": [256,40], "src": [240,32], "f": 0, "t": 158, "d": [15,272], "a": 1 }, + { "px": [264,40], "src": [240,32], "f": 0, "t": 158, "d": [15,273], "a": 1 }, + { "px": [272,40], "src": [240,32], "f": 0, "t": 158, "d": [15,274], "a": 1 }, + { "px": [280,40], "src": [240,32], "f": 0, "t": 158, "d": [15,275], "a": 1 }, + { "px": [288,40], "src": [240,32], "f": 0, "t": 158, "d": [15,276], "a": 1 }, + { "px": [296,40], "src": [240,32], "f": 0, "t": 158, "d": [15,277], "a": 1 }, + { "px": [304,40], "src": [240,32], "f": 0, "t": 158, "d": [15,278], "a": 1 }, + { "px": [336,40], "src": [240,32], "f": 0, "t": 158, "d": [15,282], "a": 1 }, + { "px": [344,40], "src": [240,32], "f": 0, "t": 158, "d": [15,283], "a": 1 }, + { "px": [352,40], "src": [240,32], "f": 0, "t": 158, "d": [15,284], "a": 1 }, + { "px": [360,40], "src": [240,32], "f": 0, "t": 158, "d": [15,285], "a": 1 }, + { "px": [104,48], "src": [240,32], "f": 0, "t": 158, "d": [15,301], "a": 1 }, + { "px": [112,48], "src": [240,32], "f": 0, "t": 158, "d": [15,302], "a": 1 }, + { "px": [120,48], "src": [240,32], "f": 0, "t": 158, "d": [15,303], "a": 1 }, + { "px": [128,48], "src": [240,32], "f": 0, "t": 158, "d": [15,304], "a": 1 }, + { "px": [136,48], "src": [240,32], "f": 0, "t": 158, "d": [15,305], "a": 1 }, + { "px": [144,48], "src": [240,32], "f": 0, "t": 158, "d": [15,306], "a": 1 }, + { "px": [152,48], "src": [240,32], "f": 0, "t": 158, "d": [15,307], "a": 1 }, + { "px": [160,48], "src": [240,32], "f": 0, "t": 158, "d": [15,308], "a": 1 }, + { "px": [168,48], "src": [240,32], "f": 0, "t": 158, "d": [15,309], "a": 1 }, + { "px": [176,48], "src": [240,32], "f": 0, "t": 158, "d": [15,310], "a": 1 }, + { "px": [184,48], "src": [240,32], "f": 0, "t": 158, "d": [15,311], "a": 1 }, + { "px": [192,48], "src": [240,32], "f": 0, "t": 158, "d": [15,312], "a": 1 }, + { "px": [200,48], "src": [240,32], "f": 0, "t": 158, "d": [15,313], "a": 1 }, + { "px": [208,48], "src": [240,32], "f": 0, "t": 158, "d": [15,314], "a": 1 }, + { "px": [216,48], "src": [240,32], "f": 0, "t": 158, "d": [15,315], "a": 1 }, + { "px": [224,48], "src": [240,32], "f": 0, "t": 158, "d": [15,316], "a": 1 }, + { "px": [232,48], "src": [240,32], "f": 0, "t": 158, "d": [15,317], "a": 1 }, + { "px": [240,48], "src": [240,32], "f": 0, "t": 158, "d": [15,318], "a": 1 }, + { "px": [248,48], "src": [240,32], "f": 0, "t": 158, "d": [15,319], "a": 1 }, + { "px": [256,48], "src": [240,32], "f": 0, "t": 158, "d": [15,320], "a": 1 }, + { "px": [264,48], "src": [240,32], "f": 0, "t": 158, "d": [15,321], "a": 1 }, + { "px": [272,48], "src": [240,32], "f": 0, "t": 158, "d": [15,322], "a": 1 }, + { "px": [280,48], "src": [240,32], "f": 0, "t": 158, "d": [15,323], "a": 1 }, + { "px": [288,48], "src": [240,32], "f": 0, "t": 158, "d": [15,324], "a": 1 }, + { "px": [296,48], "src": [240,32], "f": 0, "t": 158, "d": [15,325], "a": 1 }, + { "px": [304,48], "src": [240,32], "f": 0, "t": 158, "d": [15,326], "a": 1 }, + { "px": [336,48], "src": [240,32], "f": 0, "t": 158, "d": [15,330], "a": 1 }, + { "px": [344,48], "src": [240,32], "f": 0, "t": 158, "d": [15,331], "a": 1 }, + { "px": [352,48], "src": [240,32], "f": 0, "t": 158, "d": [15,332], "a": 1 }, + { "px": [360,48], "src": [240,32], "f": 0, "t": 158, "d": [15,333], "a": 1 }, + { "px": [104,56], "src": [240,32], "f": 0, "t": 158, "d": [15,349], "a": 1 }, + { "px": [112,56], "src": [240,32], "f": 0, "t": 158, "d": [15,350], "a": 1 }, + { "px": [120,56], "src": [240,32], "f": 0, "t": 158, "d": [15,351], "a": 1 }, + { "px": [128,56], "src": [240,32], "f": 0, "t": 158, "d": [15,352], "a": 1 }, + { "px": [136,56], "src": [240,32], "f": 0, "t": 158, "d": [15,353], "a": 1 }, + { "px": [144,56], "src": [240,32], "f": 0, "t": 158, "d": [15,354], "a": 1 }, + { "px": [152,56], "src": [240,32], "f": 0, "t": 158, "d": [15,355], "a": 1 }, + { "px": [160,56], "src": [240,32], "f": 0, "t": 158, "d": [15,356], "a": 1 }, + { "px": [168,56], "src": [240,32], "f": 0, "t": 158, "d": [15,357], "a": 1 }, + { "px": [176,56], "src": [240,32], "f": 0, "t": 158, "d": [15,358], "a": 1 }, + { "px": [184,56], "src": [240,32], "f": 0, "t": 158, "d": [15,359], "a": 1 }, + { "px": [192,56], "src": [240,32], "f": 0, "t": 158, "d": [15,360], "a": 1 }, + { "px": [200,56], "src": [240,32], "f": 0, "t": 158, "d": [15,361], "a": 1 }, + { "px": [208,56], "src": [240,32], "f": 0, "t": 158, "d": [15,362], "a": 1 }, + { "px": [216,56], "src": [240,32], "f": 0, "t": 158, "d": [15,363], "a": 1 }, + { "px": [224,56], "src": [240,32], "f": 0, "t": 158, "d": [15,364], "a": 1 }, + { "px": [232,56], "src": [240,32], "f": 0, "t": 158, "d": [15,365], "a": 1 }, + { "px": [240,56], "src": [240,32], "f": 0, "t": 158, "d": [15,366], "a": 1 }, + { "px": [248,56], "src": [240,32], "f": 0, "t": 158, "d": [15,367], "a": 1 }, + { "px": [256,56], "src": [240,32], "f": 0, "t": 158, "d": [15,368], "a": 1 }, + { "px": [264,56], "src": [240,32], "f": 0, "t": 158, "d": [15,369], "a": 1 }, + { "px": [272,56], "src": [240,32], "f": 0, "t": 158, "d": [15,370], "a": 1 }, + { "px": [280,56], "src": [240,32], "f": 0, "t": 158, "d": [15,371], "a": 1 }, + { "px": [288,56], "src": [240,32], "f": 0, "t": 158, "d": [15,372], "a": 1 }, + { "px": [296,56], "src": [240,32], "f": 0, "t": 158, "d": [15,373], "a": 1 }, + { "px": [304,56], "src": [240,32], "f": 0, "t": 158, "d": [15,374], "a": 1 }, + { "px": [336,56], "src": [240,32], "f": 0, "t": 158, "d": [15,378], "a": 1 }, + { "px": [344,56], "src": [240,32], "f": 0, "t": 158, "d": [15,379], "a": 1 }, + { "px": [352,56], "src": [240,32], "f": 0, "t": 158, "d": [15,380], "a": 1 }, + { "px": [360,56], "src": [240,32], "f": 0, "t": 158, "d": [15,381], "a": 1 }, + { "px": [104,64], "src": [240,32], "f": 0, "t": 158, "d": [15,397], "a": 1 }, + { "px": [112,64], "src": [240,32], "f": 0, "t": 158, "d": [15,398], "a": 1 }, + { "px": [120,64], "src": [240,32], "f": 0, "t": 158, "d": [15,399], "a": 1 }, + { "px": [128,64], "src": [240,32], "f": 0, "t": 158, "d": [15,400], "a": 1 }, + { "px": [136,64], "src": [240,32], "f": 0, "t": 158, "d": [15,401], "a": 1 }, + { "px": [144,64], "src": [240,32], "f": 0, "t": 158, "d": [15,402], "a": 1 }, + { "px": [152,64], "src": [240,32], "f": 0, "t": 158, "d": [15,403], "a": 1 }, + { "px": [160,64], "src": [240,32], "f": 0, "t": 158, "d": [15,404], "a": 1 }, + { "px": [168,64], "src": [240,32], "f": 0, "t": 158, "d": [15,405], "a": 1 }, + { "px": [176,64], "src": [240,32], "f": 0, "t": 158, "d": [15,406], "a": 1 }, + { "px": [184,64], "src": [240,32], "f": 0, "t": 158, "d": [15,407], "a": 1 }, + { "px": [192,64], "src": [240,32], "f": 0, "t": 158, "d": [15,408], "a": 1 }, + { "px": [200,64], "src": [240,32], "f": 0, "t": 158, "d": [15,409], "a": 1 }, + { "px": [208,64], "src": [240,32], "f": 0, "t": 158, "d": [15,410], "a": 1 }, + { "px": [216,64], "src": [240,32], "f": 0, "t": 158, "d": [15,411], "a": 1 }, + { "px": [224,64], "src": [240,32], "f": 0, "t": 158, "d": [15,412], "a": 1 }, + { "px": [232,64], "src": [240,32], "f": 0, "t": 158, "d": [15,413], "a": 1 }, + { "px": [240,64], "src": [240,32], "f": 0, "t": 158, "d": [15,414], "a": 1 }, + { "px": [248,64], "src": [240,32], "f": 0, "t": 158, "d": [15,415], "a": 1 }, + { "px": [256,64], "src": [240,32], "f": 0, "t": 158, "d": [15,416], "a": 1 }, + { "px": [264,64], "src": [240,32], "f": 0, "t": 158, "d": [15,417], "a": 1 }, + { "px": [272,64], "src": [240,32], "f": 0, "t": 158, "d": [15,418], "a": 1 }, + { "px": [280,64], "src": [240,32], "f": 0, "t": 158, "d": [15,419], "a": 1 }, + { "px": [288,64], "src": [240,32], "f": 0, "t": 158, "d": [15,420], "a": 1 }, + { "px": [296,64], "src": [240,32], "f": 0, "t": 158, "d": [15,421], "a": 1 }, + { "px": [304,64], "src": [240,32], "f": 0, "t": 158, "d": [15,422], "a": 1 }, + { "px": [336,64], "src": [240,32], "f": 0, "t": 158, "d": [15,426], "a": 1 }, + { "px": [344,64], "src": [240,32], "f": 0, "t": 158, "d": [15,427], "a": 1 }, + { "px": [352,64], "src": [240,32], "f": 0, "t": 158, "d": [15,428], "a": 1 }, + { "px": [360,64], "src": [240,32], "f": 0, "t": 158, "d": [15,429], "a": 1 }, + { "px": [104,72], "src": [240,32], "f": 0, "t": 158, "d": [15,445], "a": 1 }, + { "px": [112,72], "src": [240,32], "f": 0, "t": 158, "d": [15,446], "a": 1 }, + { "px": [120,72], "src": [240,32], "f": 0, "t": 158, "d": [15,447], "a": 1 }, + { "px": [128,72], "src": [240,32], "f": 0, "t": 158, "d": [15,448], "a": 1 }, + { "px": [136,72], "src": [240,32], "f": 0, "t": 158, "d": [15,449], "a": 1 }, + { "px": [144,72], "src": [240,32], "f": 0, "t": 158, "d": [15,450], "a": 1 }, + { "px": [152,72], "src": [240,32], "f": 0, "t": 158, "d": [15,451], "a": 1 }, + { "px": [160,72], "src": [240,32], "f": 0, "t": 158, "d": [15,452], "a": 1 }, + { "px": [168,72], "src": [240,32], "f": 0, "t": 158, "d": [15,453], "a": 1 }, + { "px": [176,72], "src": [240,32], "f": 0, "t": 158, "d": [15,454], "a": 1 }, + { "px": [184,72], "src": [240,32], "f": 0, "t": 158, "d": [15,455], "a": 1 }, + { "px": [192,72], "src": [240,32], "f": 0, "t": 158, "d": [15,456], "a": 1 }, + { "px": [200,72], "src": [240,32], "f": 0, "t": 158, "d": [15,457], "a": 1 }, + { "px": [208,72], "src": [240,32], "f": 0, "t": 158, "d": [15,458], "a": 1 }, + { "px": [216,72], "src": [240,32], "f": 0, "t": 158, "d": [15,459], "a": 1 }, + { "px": [224,72], "src": [240,32], "f": 0, "t": 158, "d": [15,460], "a": 1 }, + { "px": [232,72], "src": [240,32], "f": 0, "t": 158, "d": [15,461], "a": 1 }, + { "px": [240,72], "src": [240,32], "f": 0, "t": 158, "d": [15,462], "a": 1 }, + { "px": [248,72], "src": [240,32], "f": 0, "t": 158, "d": [15,463], "a": 1 }, + { "px": [256,72], "src": [240,32], "f": 0, "t": 158, "d": [15,464], "a": 1 }, + { "px": [264,72], "src": [240,32], "f": 0, "t": 158, "d": [15,465], "a": 1 }, + { "px": [272,72], "src": [240,32], "f": 0, "t": 158, "d": [15,466], "a": 1 }, + { "px": [280,72], "src": [240,32], "f": 0, "t": 158, "d": [15,467], "a": 1 }, + { "px": [288,72], "src": [240,32], "f": 0, "t": 158, "d": [15,468], "a": 1 }, + { "px": [296,72], "src": [240,32], "f": 0, "t": 158, "d": [15,469], "a": 1 }, + { "px": [304,72], "src": [240,32], "f": 0, "t": 158, "d": [15,470], "a": 1 }, + { "px": [336,72], "src": [240,32], "f": 0, "t": 158, "d": [15,474], "a": 1 }, + { "px": [344,72], "src": [240,32], "f": 0, "t": 158, "d": [15,475], "a": 1 }, + { "px": [352,72], "src": [240,32], "f": 0, "t": 158, "d": [15,476], "a": 1 }, + { "px": [360,72], "src": [240,32], "f": 0, "t": 158, "d": [15,477], "a": 1 }, + { "px": [104,80], "src": [240,32], "f": 0, "t": 158, "d": [15,493], "a": 1 }, + { "px": [112,80], "src": [240,32], "f": 0, "t": 158, "d": [15,494], "a": 1 }, + { "px": [120,80], "src": [240,32], "f": 0, "t": 158, "d": [15,495], "a": 1 }, + { "px": [128,80], "src": [240,32], "f": 0, "t": 158, "d": [15,496], "a": 1 }, + { "px": [136,80], "src": [240,32], "f": 0, "t": 158, "d": [15,497], "a": 1 }, + { "px": [144,80], "src": [240,32], "f": 0, "t": 158, "d": [15,498], "a": 1 }, + { "px": [152,80], "src": [240,32], "f": 0, "t": 158, "d": [15,499], "a": 1 }, + { "px": [160,80], "src": [240,32], "f": 0, "t": 158, "d": [15,500], "a": 1 }, + { "px": [168,80], "src": [240,32], "f": 0, "t": 158, "d": [15,501], "a": 1 }, + { "px": [176,80], "src": [240,32], "f": 0, "t": 158, "d": [15,502], "a": 1 }, + { "px": [184,80], "src": [240,32], "f": 0, "t": 158, "d": [15,503], "a": 1 }, + { "px": [192,80], "src": [240,32], "f": 0, "t": 158, "d": [15,504], "a": 1 }, + { "px": [200,80], "src": [240,32], "f": 0, "t": 158, "d": [15,505], "a": 1 }, + { "px": [208,80], "src": [240,32], "f": 0, "t": 158, "d": [15,506], "a": 1 }, + { "px": [216,80], "src": [240,32], "f": 0, "t": 158, "d": [15,507], "a": 1 }, + { "px": [224,80], "src": [240,32], "f": 0, "t": 158, "d": [15,508], "a": 1 }, + { "px": [232,80], "src": [240,32], "f": 0, "t": 158, "d": [15,509], "a": 1 }, + { "px": [240,80], "src": [240,32], "f": 0, "t": 158, "d": [15,510], "a": 1 }, + { "px": [248,80], "src": [240,32], "f": 0, "t": 158, "d": [15,511], "a": 1 }, + { "px": [256,80], "src": [240,32], "f": 0, "t": 158, "d": [15,512], "a": 1 }, + { "px": [264,80], "src": [240,32], "f": 0, "t": 158, "d": [15,513], "a": 1 }, + { "px": [272,80], "src": [240,32], "f": 0, "t": 158, "d": [15,514], "a": 1 }, + { "px": [280,80], "src": [240,32], "f": 0, "t": 158, "d": [15,515], "a": 1 }, + { "px": [288,80], "src": [240,32], "f": 0, "t": 158, "d": [15,516], "a": 1 }, + { "px": [296,80], "src": [240,32], "f": 0, "t": 158, "d": [15,517], "a": 1 }, + { "px": [304,80], "src": [240,32], "f": 0, "t": 158, "d": [15,518], "a": 1 }, + { "px": [336,80], "src": [240,32], "f": 0, "t": 158, "d": [15,522], "a": 1 }, + { "px": [344,80], "src": [240,32], "f": 0, "t": 158, "d": [15,523], "a": 1 }, + { "px": [352,80], "src": [240,32], "f": 0, "t": 158, "d": [15,524], "a": 1 }, + { "px": [360,80], "src": [240,32], "f": 0, "t": 158, "d": [15,525], "a": 1 }, + { "px": [104,88], "src": [240,32], "f": 0, "t": 158, "d": [15,541], "a": 1 }, + { "px": [112,88], "src": [240,32], "f": 0, "t": 158, "d": [15,542], "a": 1 }, + { "px": [120,88], "src": [240,32], "f": 0, "t": 158, "d": [15,543], "a": 1 }, + { "px": [128,88], "src": [240,32], "f": 0, "t": 158, "d": [15,544], "a": 1 }, + { "px": [136,88], "src": [240,32], "f": 0, "t": 158, "d": [15,545], "a": 1 }, + { "px": [144,88], "src": [240,32], "f": 0, "t": 158, "d": [15,546], "a": 1 }, + { "px": [152,88], "src": [240,32], "f": 0, "t": 158, "d": [15,547], "a": 1 }, + { "px": [160,88], "src": [240,32], "f": 0, "t": 158, "d": [15,548], "a": 1 }, + { "px": [168,88], "src": [240,32], "f": 0, "t": 158, "d": [15,549], "a": 1 }, + { "px": [176,88], "src": [240,32], "f": 0, "t": 158, "d": [15,550], "a": 1 }, + { "px": [184,88], "src": [240,32], "f": 0, "t": 158, "d": [15,551], "a": 1 }, + { "px": [192,88], "src": [240,32], "f": 0, "t": 158, "d": [15,552], "a": 1 }, + { "px": [200,88], "src": [240,32], "f": 0, "t": 158, "d": [15,553], "a": 1 }, + { "px": [208,88], "src": [240,32], "f": 0, "t": 158, "d": [15,554], "a": 1 }, + { "px": [216,88], "src": [240,32], "f": 0, "t": 158, "d": [15,555], "a": 1 }, + { "px": [224,88], "src": [240,32], "f": 0, "t": 158, "d": [15,556], "a": 1 }, + { "px": [232,88], "src": [240,32], "f": 0, "t": 158, "d": [15,557], "a": 1 }, + { "px": [240,88], "src": [240,32], "f": 0, "t": 158, "d": [15,558], "a": 1 }, + { "px": [248,88], "src": [240,32], "f": 0, "t": 158, "d": [15,559], "a": 1 }, + { "px": [256,88], "src": [240,32], "f": 0, "t": 158, "d": [15,560], "a": 1 }, + { "px": [264,88], "src": [240,32], "f": 0, "t": 158, "d": [15,561], "a": 1 }, + { "px": [272,88], "src": [240,32], "f": 0, "t": 158, "d": [15,562], "a": 1 }, + { "px": [280,88], "src": [240,32], "f": 0, "t": 158, "d": [15,563], "a": 1 }, + { "px": [288,88], "src": [240,32], "f": 0, "t": 158, "d": [15,564], "a": 1 }, + { "px": [296,88], "src": [240,32], "f": 0, "t": 158, "d": [15,565], "a": 1 }, + { "px": [304,88], "src": [240,32], "f": 0, "t": 158, "d": [15,566], "a": 1 }, + { "px": [336,88], "src": [240,32], "f": 0, "t": 158, "d": [15,570], "a": 1 }, + { "px": [344,88], "src": [240,32], "f": 0, "t": 158, "d": [15,571], "a": 1 }, + { "px": [352,88], "src": [240,32], "f": 0, "t": 158, "d": [15,572], "a": 1 }, + { "px": [360,88], "src": [240,32], "f": 0, "t": 158, "d": [15,573], "a": 1 }, + { "px": [104,96], "src": [240,32], "f": 0, "t": 158, "d": [15,589], "a": 1 }, + { "px": [112,96], "src": [240,32], "f": 0, "t": 158, "d": [15,590], "a": 1 }, + { "px": [120,96], "src": [240,32], "f": 0, "t": 158, "d": [15,591], "a": 1 }, + { "px": [128,96], "src": [240,32], "f": 0, "t": 158, "d": [15,592], "a": 1 }, + { "px": [136,96], "src": [240,32], "f": 0, "t": 158, "d": [15,593], "a": 1 }, + { "px": [144,96], "src": [240,32], "f": 0, "t": 158, "d": [15,594], "a": 1 }, + { "px": [152,96], "src": [240,32], "f": 0, "t": 158, "d": [15,595], "a": 1 }, + { "px": [160,96], "src": [240,32], "f": 0, "t": 158, "d": [15,596], "a": 1 }, + { "px": [168,96], "src": [240,32], "f": 0, "t": 158, "d": [15,597], "a": 1 }, + { "px": [176,96], "src": [240,32], "f": 0, "t": 158, "d": [15,598], "a": 1 }, + { "px": [184,96], "src": [240,32], "f": 0, "t": 158, "d": [15,599], "a": 1 }, + { "px": [192,96], "src": [240,32], "f": 0, "t": 158, "d": [15,600], "a": 1 }, + { "px": [200,96], "src": [240,32], "f": 0, "t": 158, "d": [15,601], "a": 1 }, + { "px": [208,96], "src": [240,32], "f": 0, "t": 158, "d": [15,602], "a": 1 }, + { "px": [216,96], "src": [240,32], "f": 0, "t": 158, "d": [15,603], "a": 1 }, + { "px": [224,96], "src": [240,32], "f": 0, "t": 158, "d": [15,604], "a": 1 }, + { "px": [232,96], "src": [240,32], "f": 0, "t": 158, "d": [15,605], "a": 1 }, + { "px": [240,96], "src": [240,32], "f": 0, "t": 158, "d": [15,606], "a": 1 }, + { "px": [248,96], "src": [240,32], "f": 0, "t": 158, "d": [15,607], "a": 1 }, + { "px": [256,96], "src": [240,32], "f": 0, "t": 158, "d": [15,608], "a": 1 }, + { "px": [264,96], "src": [240,32], "f": 0, "t": 158, "d": [15,609], "a": 1 }, + { "px": [272,96], "src": [240,32], "f": 0, "t": 158, "d": [15,610], "a": 1 }, + { "px": [280,96], "src": [240,32], "f": 0, "t": 158, "d": [15,611], "a": 1 }, + { "px": [288,96], "src": [240,32], "f": 0, "t": 158, "d": [15,612], "a": 1 }, + { "px": [296,96], "src": [240,32], "f": 0, "t": 158, "d": [15,613], "a": 1 }, + { "px": [304,96], "src": [240,32], "f": 0, "t": 158, "d": [15,614], "a": 1 }, + { "px": [336,96], "src": [240,32], "f": 0, "t": 158, "d": [15,618], "a": 1 }, + { "px": [344,96], "src": [240,32], "f": 0, "t": 158, "d": [15,619], "a": 1 }, + { "px": [352,96], "src": [240,32], "f": 0, "t": 158, "d": [15,620], "a": 1 }, + { "px": [360,96], "src": [240,32], "f": 0, "t": 158, "d": [15,621], "a": 1 }, + { "px": [104,104], "src": [240,32], "f": 0, "t": 158, "d": [15,637], "a": 1 }, + { "px": [112,104], "src": [240,32], "f": 0, "t": 158, "d": [15,638], "a": 1 }, + { "px": [120,104], "src": [240,32], "f": 0, "t": 158, "d": [15,639], "a": 1 }, + { "px": [128,104], "src": [240,32], "f": 0, "t": 158, "d": [15,640], "a": 1 }, + { "px": [136,104], "src": [240,32], "f": 0, "t": 158, "d": [15,641], "a": 1 }, + { "px": [144,104], "src": [240,32], "f": 0, "t": 158, "d": [15,642], "a": 1 }, + { "px": [152,104], "src": [240,32], "f": 0, "t": 158, "d": [15,643], "a": 1 }, + { "px": [160,104], "src": [240,32], "f": 0, "t": 158, "d": [15,644], "a": 1 }, + { "px": [168,104], "src": [240,32], "f": 0, "t": 158, "d": [15,645], "a": 1 }, + { "px": [176,104], "src": [240,32], "f": 0, "t": 158, "d": [15,646], "a": 1 }, + { "px": [184,104], "src": [240,32], "f": 0, "t": 158, "d": [15,647], "a": 1 }, + { "px": [192,104], "src": [240,32], "f": 0, "t": 158, "d": [15,648], "a": 1 }, + { "px": [200,104], "src": [240,32], "f": 0, "t": 158, "d": [15,649], "a": 1 }, + { "px": [208,104], "src": [240,32], "f": 0, "t": 158, "d": [15,650], "a": 1 }, + { "px": [216,104], "src": [240,32], "f": 0, "t": 158, "d": [15,651], "a": 1 }, + { "px": [224,104], "src": [240,32], "f": 0, "t": 158, "d": [15,652], "a": 1 }, + { "px": [232,104], "src": [240,32], "f": 0, "t": 158, "d": [15,653], "a": 1 }, + { "px": [240,104], "src": [240,32], "f": 0, "t": 158, "d": [15,654], "a": 1 }, + { "px": [248,104], "src": [240,32], "f": 0, "t": 158, "d": [15,655], "a": 1 }, + { "px": [256,104], "src": [240,32], "f": 0, "t": 158, "d": [15,656], "a": 1 }, + { "px": [264,104], "src": [240,32], "f": 0, "t": 158, "d": [15,657], "a": 1 }, + { "px": [272,104], "src": [240,32], "f": 0, "t": 158, "d": [15,658], "a": 1 }, + { "px": [280,104], "src": [240,32], "f": 0, "t": 158, "d": [15,659], "a": 1 }, + { "px": [288,104], "src": [240,32], "f": 0, "t": 158, "d": [15,660], "a": 1 }, + { "px": [296,104], "src": [240,32], "f": 0, "t": 158, "d": [15,661], "a": 1 }, + { "px": [304,104], "src": [240,32], "f": 0, "t": 158, "d": [15,662], "a": 1 }, + { "px": [336,104], "src": [240,32], "f": 0, "t": 158, "d": [15,666], "a": 1 }, + { "px": [344,104], "src": [240,32], "f": 0, "t": 158, "d": [15,667], "a": 1 }, + { "px": [352,104], "src": [240,32], "f": 0, "t": 158, "d": [15,668], "a": 1 }, + { "px": [360,104], "src": [240,32], "f": 0, "t": 158, "d": [15,669], "a": 1 }, + { "px": [104,112], "src": [240,32], "f": 0, "t": 158, "d": [15,685], "a": 1 }, + { "px": [112,112], "src": [240,32], "f": 0, "t": 158, "d": [15,686], "a": 1 }, + { "px": [120,112], "src": [240,32], "f": 0, "t": 158, "d": [15,687], "a": 1 }, + { "px": [128,112], "src": [240,32], "f": 0, "t": 158, "d": [15,688], "a": 1 }, + { "px": [136,112], "src": [240,32], "f": 0, "t": 158, "d": [15,689], "a": 1 }, + { "px": [144,112], "src": [240,32], "f": 0, "t": 158, "d": [15,690], "a": 1 }, + { "px": [152,112], "src": [240,32], "f": 0, "t": 158, "d": [15,691], "a": 1 }, + { "px": [160,112], "src": [240,32], "f": 0, "t": 158, "d": [15,692], "a": 1 }, + { "px": [168,112], "src": [240,32], "f": 0, "t": 158, "d": [15,693], "a": 1 }, + { "px": [176,112], "src": [240,32], "f": 0, "t": 158, "d": [15,694], "a": 1 }, + { "px": [184,112], "src": [240,32], "f": 0, "t": 158, "d": [15,695], "a": 1 }, + { "px": [192,112], "src": [240,32], "f": 0, "t": 158, "d": [15,696], "a": 1 }, + { "px": [200,112], "src": [240,32], "f": 0, "t": 158, "d": [15,697], "a": 1 }, + { "px": [208,112], "src": [240,32], "f": 0, "t": 158, "d": [15,698], "a": 1 }, + { "px": [216,112], "src": [240,32], "f": 0, "t": 158, "d": [15,699], "a": 1 }, + { "px": [224,112], "src": [240,32], "f": 0, "t": 158, "d": [15,700], "a": 1 }, + { "px": [232,112], "src": [240,32], "f": 0, "t": 158, "d": [15,701], "a": 1 }, + { "px": [240,112], "src": [240,32], "f": 0, "t": 158, "d": [15,702], "a": 1 }, + { "px": [248,112], "src": [240,32], "f": 0, "t": 158, "d": [15,703], "a": 1 }, + { "px": [256,112], "src": [240,32], "f": 0, "t": 158, "d": [15,704], "a": 1 }, + { "px": [264,112], "src": [240,32], "f": 0, "t": 158, "d": [15,705], "a": 1 }, + { "px": [272,112], "src": [240,32], "f": 0, "t": 158, "d": [15,706], "a": 1 }, + { "px": [280,112], "src": [240,32], "f": 0, "t": 158, "d": [15,707], "a": 1 }, + { "px": [288,112], "src": [240,32], "f": 0, "t": 158, "d": [15,708], "a": 1 }, + { "px": [296,112], "src": [240,32], "f": 0, "t": 158, "d": [15,709], "a": 1 }, + { "px": [304,112], "src": [240,32], "f": 0, "t": 158, "d": [15,710], "a": 1 }, + { "px": [336,112], "src": [240,32], "f": 0, "t": 158, "d": [15,714], "a": 1 }, + { "px": [344,112], "src": [240,32], "f": 0, "t": 158, "d": [15,715], "a": 1 }, + { "px": [352,112], "src": [240,32], "f": 0, "t": 158, "d": [15,716], "a": 1 }, + { "px": [360,112], "src": [240,32], "f": 0, "t": 158, "d": [15,717], "a": 1 }, + { "px": [104,120], "src": [240,32], "f": 0, "t": 158, "d": [15,733], "a": 1 }, + { "px": [112,120], "src": [240,32], "f": 0, "t": 158, "d": [15,734], "a": 1 }, + { "px": [120,120], "src": [240,32], "f": 0, "t": 158, "d": [15,735], "a": 1 }, + { "px": [128,120], "src": [240,32], "f": 0, "t": 158, "d": [15,736], "a": 1 }, + { "px": [136,120], "src": [240,32], "f": 0, "t": 158, "d": [15,737], "a": 1 }, + { "px": [144,120], "src": [240,32], "f": 0, "t": 158, "d": [15,738], "a": 1 }, + { "px": [152,120], "src": [240,32], "f": 0, "t": 158, "d": [15,739], "a": 1 }, + { "px": [160,120], "src": [240,32], "f": 0, "t": 158, "d": [15,740], "a": 1 }, + { "px": [168,120], "src": [240,32], "f": 0, "t": 158, "d": [15,741], "a": 1 }, + { "px": [176,120], "src": [240,32], "f": 0, "t": 158, "d": [15,742], "a": 1 }, + { "px": [184,120], "src": [240,32], "f": 0, "t": 158, "d": [15,743], "a": 1 }, + { "px": [192,120], "src": [240,32], "f": 0, "t": 158, "d": [15,744], "a": 1 }, + { "px": [200,120], "src": [240,32], "f": 0, "t": 158, "d": [15,745], "a": 1 }, + { "px": [208,120], "src": [240,32], "f": 0, "t": 158, "d": [15,746], "a": 1 }, + { "px": [216,120], "src": [240,32], "f": 0, "t": 158, "d": [15,747], "a": 1 }, + { "px": [224,120], "src": [240,32], "f": 0, "t": 158, "d": [15,748], "a": 1 }, + { "px": [232,120], "src": [240,32], "f": 0, "t": 158, "d": [15,749], "a": 1 }, + { "px": [240,120], "src": [240,32], "f": 0, "t": 158, "d": [15,750], "a": 1 }, + { "px": [248,120], "src": [240,32], "f": 0, "t": 158, "d": [15,751], "a": 1 }, + { "px": [256,120], "src": [240,32], "f": 0, "t": 158, "d": [15,752], "a": 1 }, + { "px": [264,120], "src": [240,32], "f": 0, "t": 158, "d": [15,753], "a": 1 }, + { "px": [272,120], "src": [240,32], "f": 0, "t": 158, "d": [15,754], "a": 1 }, + { "px": [280,120], "src": [240,32], "f": 0, "t": 158, "d": [15,755], "a": 1 }, + { "px": [288,120], "src": [240,32], "f": 0, "t": 158, "d": [15,756], "a": 1 }, + { "px": [296,120], "src": [240,32], "f": 0, "t": 158, "d": [15,757], "a": 1 }, + { "px": [304,120], "src": [240,32], "f": 0, "t": 158, "d": [15,758], "a": 1 }, + { "px": [336,120], "src": [240,32], "f": 0, "t": 158, "d": [15,762], "a": 1 }, + { "px": [344,120], "src": [240,32], "f": 0, "t": 158, "d": [15,763], "a": 1 }, + { "px": [352,120], "src": [240,32], "f": 0, "t": 158, "d": [15,764], "a": 1 }, + { "px": [360,120], "src": [240,32], "f": 0, "t": 158, "d": [15,765], "a": 1 }, + { "px": [104,128], "src": [240,32], "f": 0, "t": 158, "d": [15,781], "a": 1 }, + { "px": [112,128], "src": [240,32], "f": 0, "t": 158, "d": [15,782], "a": 1 }, + { "px": [120,128], "src": [240,32], "f": 0, "t": 158, "d": [15,783], "a": 1 }, + { "px": [128,128], "src": [240,32], "f": 0, "t": 158, "d": [15,784], "a": 1 }, + { "px": [136,128], "src": [240,32], "f": 0, "t": 158, "d": [15,785], "a": 1 }, + { "px": [144,128], "src": [240,32], "f": 0, "t": 158, "d": [15,786], "a": 1 }, + { "px": [152,128], "src": [240,32], "f": 0, "t": 158, "d": [15,787], "a": 1 }, + { "px": [160,128], "src": [240,32], "f": 0, "t": 158, "d": [15,788], "a": 1 }, + { "px": [168,128], "src": [240,32], "f": 0, "t": 158, "d": [15,789], "a": 1 }, + { "px": [176,128], "src": [240,32], "f": 0, "t": 158, "d": [15,790], "a": 1 }, + { "px": [184,128], "src": [240,32], "f": 0, "t": 158, "d": [15,791], "a": 1 }, + { "px": [192,128], "src": [240,32], "f": 0, "t": 158, "d": [15,792], "a": 1 }, + { "px": [200,128], "src": [240,32], "f": 0, "t": 158, "d": [15,793], "a": 1 }, + { "px": [208,128], "src": [240,32], "f": 0, "t": 158, "d": [15,794], "a": 1 }, + { "px": [216,128], "src": [240,32], "f": 0, "t": 158, "d": [15,795], "a": 1 }, + { "px": [224,128], "src": [240,32], "f": 0, "t": 158, "d": [15,796], "a": 1 }, + { "px": [232,128], "src": [240,32], "f": 0, "t": 158, "d": [15,797], "a": 1 }, + { "px": [240,128], "src": [240,32], "f": 0, "t": 158, "d": [15,798], "a": 1 }, + { "px": [248,128], "src": [240,32], "f": 0, "t": 158, "d": [15,799], "a": 1 }, + { "px": [256,128], "src": [240,32], "f": 0, "t": 158, "d": [15,800], "a": 1 }, + { "px": [264,128], "src": [240,32], "f": 0, "t": 158, "d": [15,801], "a": 1 }, + { "px": [272,128], "src": [240,32], "f": 0, "t": 158, "d": [15,802], "a": 1 }, + { "px": [280,128], "src": [240,32], "f": 0, "t": 158, "d": [15,803], "a": 1 }, + { "px": [288,128], "src": [240,32], "f": 0, "t": 158, "d": [15,804], "a": 1 }, + { "px": [296,128], "src": [240,32], "f": 0, "t": 158, "d": [15,805], "a": 1 }, + { "px": [304,128], "src": [240,32], "f": 0, "t": 158, "d": [15,806], "a": 1 }, + { "px": [336,128], "src": [240,32], "f": 0, "t": 158, "d": [15,810], "a": 1 }, + { "px": [344,128], "src": [240,32], "f": 0, "t": 158, "d": [15,811], "a": 1 }, + { "px": [352,128], "src": [240,32], "f": 0, "t": 158, "d": [15,812], "a": 1 }, + { "px": [360,128], "src": [240,32], "f": 0, "t": 158, "d": [15,813], "a": 1 }, + { "px": [104,136], "src": [240,32], "f": 0, "t": 158, "d": [15,829], "a": 1 }, + { "px": [112,136], "src": [240,32], "f": 0, "t": 158, "d": [15,830], "a": 1 }, + { "px": [120,136], "src": [240,32], "f": 0, "t": 158, "d": [15,831], "a": 1 }, + { "px": [128,136], "src": [240,32], "f": 0, "t": 158, "d": [15,832], "a": 1 }, + { "px": [136,136], "src": [240,32], "f": 0, "t": 158, "d": [15,833], "a": 1 }, + { "px": [144,136], "src": [240,32], "f": 0, "t": 158, "d": [15,834], "a": 1 }, + { "px": [152,136], "src": [240,32], "f": 0, "t": 158, "d": [15,835], "a": 1 }, + { "px": [160,136], "src": [240,32], "f": 0, "t": 158, "d": [15,836], "a": 1 }, + { "px": [168,136], "src": [240,32], "f": 0, "t": 158, "d": [15,837], "a": 1 }, + { "px": [176,136], "src": [240,32], "f": 0, "t": 158, "d": [15,838], "a": 1 }, + { "px": [184,136], "src": [240,32], "f": 0, "t": 158, "d": [15,839], "a": 1 }, + { "px": [192,136], "src": [240,32], "f": 0, "t": 158, "d": [15,840], "a": 1 }, + { "px": [200,136], "src": [240,32], "f": 0, "t": 158, "d": [15,841], "a": 1 }, + { "px": [208,136], "src": [240,32], "f": 0, "t": 158, "d": [15,842], "a": 1 }, + { "px": [216,136], "src": [240,32], "f": 0, "t": 158, "d": [15,843], "a": 1 }, + { "px": [224,136], "src": [240,32], "f": 0, "t": 158, "d": [15,844], "a": 1 }, + { "px": [232,136], "src": [240,32], "f": 0, "t": 158, "d": [15,845], "a": 1 }, + { "px": [240,136], "src": [240,32], "f": 0, "t": 158, "d": [15,846], "a": 1 }, + { "px": [248,136], "src": [240,32], "f": 0, "t": 158, "d": [15,847], "a": 1 }, + { "px": [256,136], "src": [240,32], "f": 0, "t": 158, "d": [15,848], "a": 1 }, + { "px": [264,136], "src": [240,32], "f": 0, "t": 158, "d": [15,849], "a": 1 }, + { "px": [272,136], "src": [240,32], "f": 0, "t": 158, "d": [15,850], "a": 1 }, + { "px": [280,136], "src": [240,32], "f": 0, "t": 158, "d": [15,851], "a": 1 }, + { "px": [288,136], "src": [240,32], "f": 0, "t": 158, "d": [15,852], "a": 1 }, + { "px": [296,136], "src": [240,32], "f": 0, "t": 158, "d": [15,853], "a": 1 }, + { "px": [304,136], "src": [240,32], "f": 0, "t": 158, "d": [15,854], "a": 1 }, + { "px": [336,136], "src": [240,32], "f": 0, "t": 158, "d": [15,858], "a": 1 }, + { "px": [344,136], "src": [240,32], "f": 0, "t": 158, "d": [15,859], "a": 1 }, + { "px": [352,136], "src": [240,32], "f": 0, "t": 158, "d": [15,860], "a": 1 }, + { "px": [360,136], "src": [240,32], "f": 0, "t": 158, "d": [15,861], "a": 1 }, + { "px": [336,144], "src": [240,32], "f": 0, "t": 158, "d": [15,906], "a": 1 }, + { "px": [344,144], "src": [240,32], "f": 0, "t": 158, "d": [15,907], "a": 1 }, + { "px": [352,144], "src": [240,32], "f": 0, "t": 158, "d": [15,908], "a": 1 }, + { "px": [360,144], "src": [240,32], "f": 0, "t": 158, "d": [15,909], "a": 1 }, + { "px": [336,152], "src": [240,32], "f": 0, "t": 158, "d": [15,954], "a": 1 }, + { "px": [344,152], "src": [240,32], "f": 0, "t": 158, "d": [15,955], "a": 1 }, + { "px": [352,152], "src": [240,32], "f": 0, "t": 158, "d": [15,956], "a": 1 }, + { "px": [360,152], "src": [240,32], "f": 0, "t": 158, "d": [15,957], "a": 1 }, + { "px": [336,160], "src": [240,32], "f": 0, "t": 158, "d": [15,1002], "a": 1 }, + { "px": [344,160], "src": [240,32], "f": 0, "t": 158, "d": [15,1003], "a": 1 }, + { "px": [352,160], "src": [240,32], "f": 0, "t": 158, "d": [15,1004], "a": 1 }, + { "px": [360,160], "src": [240,32], "f": 0, "t": 158, "d": [15,1005], "a": 1 }, + { "px": [104,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1021], "a": 1 }, + { "px": [112,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1022], "a": 1 }, + { "px": [120,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1023], "a": 1 }, + { "px": [128,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1024], "a": 1 }, + { "px": [136,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1025], "a": 1 }, + { "px": [144,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1026], "a": 1 }, + { "px": [152,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1027], "a": 1 }, + { "px": [160,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1028], "a": 1 }, + { "px": [168,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1029], "a": 1 }, + { "px": [176,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1030], "a": 1 }, + { "px": [184,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1031], "a": 1 }, + { "px": [192,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1032], "a": 1 }, + { "px": [200,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1033], "a": 1 }, + { "px": [208,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1034], "a": 1 }, + { "px": [216,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1035], "a": 1 }, + { "px": [224,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1036], "a": 1 }, + { "px": [232,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1037], "a": 1 }, + { "px": [240,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1038], "a": 1 }, + { "px": [248,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1039], "a": 1 }, + { "px": [256,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1040], "a": 1 }, + { "px": [264,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1041], "a": 1 }, + { "px": [272,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1042], "a": 1 }, + { "px": [280,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1043], "a": 1 }, + { "px": [288,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1044], "a": 1 }, + { "px": [296,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1045], "a": 1 }, + { "px": [304,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1046], "a": 1 }, + { "px": [336,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1050], "a": 1 }, + { "px": [344,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1051], "a": 1 }, + { "px": [352,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1052], "a": 1 }, + { "px": [360,168], "src": [240,32], "f": 0, "t": 158, "d": [15,1053], "a": 1 }, + { "px": [104,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1069], "a": 1 }, + { "px": [112,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1070], "a": 1 }, + { "px": [120,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1071], "a": 1 }, + { "px": [128,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1072], "a": 1 }, + { "px": [136,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1073], "a": 1 }, + { "px": [144,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1074], "a": 1 }, + { "px": [152,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1075], "a": 1 }, + { "px": [160,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1076], "a": 1 }, + { "px": [168,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1077], "a": 1 }, + { "px": [176,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1078], "a": 1 }, + { "px": [184,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1079], "a": 1 }, + { "px": [192,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1080], "a": 1 }, + { "px": [200,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1081], "a": 1 }, + { "px": [208,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1082], "a": 1 }, + { "px": [216,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1083], "a": 1 }, + { "px": [224,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1084], "a": 1 }, + { "px": [232,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1085], "a": 1 }, + { "px": [240,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1086], "a": 1 }, + { "px": [248,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1087], "a": 1 }, + { "px": [256,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1088], "a": 1 }, + { "px": [264,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1089], "a": 1 }, + { "px": [272,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1090], "a": 1 }, + { "px": [280,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1091], "a": 1 }, + { "px": [288,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1092], "a": 1 }, + { "px": [296,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1093], "a": 1 }, + { "px": [304,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1094], "a": 1 }, + { "px": [336,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1098], "a": 1 }, + { "px": [344,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1099], "a": 1 }, + { "px": [352,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1100], "a": 1 }, + { "px": [360,176], "src": [240,32], "f": 0, "t": 158, "d": [15,1101], "a": 1 }, + { "px": [104,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1117], "a": 1 }, + { "px": [112,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1118], "a": 1 }, + { "px": [120,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1119], "a": 1 }, + { "px": [128,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1120], "a": 1 }, + { "px": [136,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1121], "a": 1 }, + { "px": [144,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1122], "a": 1 }, + { "px": [152,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1123], "a": 1 }, + { "px": [160,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1124], "a": 1 }, + { "px": [168,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1125], "a": 1 }, + { "px": [176,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1126], "a": 1 }, + { "px": [184,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1127], "a": 1 }, + { "px": [192,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1128], "a": 1 }, + { "px": [200,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1129], "a": 1 }, + { "px": [208,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1130], "a": 1 }, + { "px": [216,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1131], "a": 1 }, + { "px": [224,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1132], "a": 1 }, + { "px": [232,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1133], "a": 1 }, + { "px": [240,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1134], "a": 1 }, + { "px": [248,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1135], "a": 1 }, + { "px": [256,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1136], "a": 1 }, + { "px": [264,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1137], "a": 1 }, + { "px": [272,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1138], "a": 1 }, + { "px": [280,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1139], "a": 1 }, + { "px": [288,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1140], "a": 1 }, + { "px": [296,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1141], "a": 1 }, + { "px": [304,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1142], "a": 1 }, + { "px": [336,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1146], "a": 1 }, + { "px": [344,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1147], "a": 1 }, + { "px": [352,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1148], "a": 1 }, + { "px": [360,184], "src": [240,32], "f": 0, "t": 158, "d": [15,1149], "a": 1 }, + { "px": [104,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1165], "a": 1 }, + { "px": [112,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1166], "a": 1 }, + { "px": [120,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1167], "a": 1 }, + { "px": [128,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1168], "a": 1 }, + { "px": [136,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1169], "a": 1 }, + { "px": [144,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1170], "a": 1 }, + { "px": [152,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1171], "a": 1 }, + { "px": [160,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1172], "a": 1 }, + { "px": [168,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1173], "a": 1 }, + { "px": [176,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1174], "a": 1 }, + { "px": [184,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1175], "a": 1 }, + { "px": [192,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1176], "a": 1 }, + { "px": [200,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1177], "a": 1 }, + { "px": [208,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1178], "a": 1 }, + { "px": [216,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1179], "a": 1 }, + { "px": [224,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1180], "a": 1 }, + { "px": [232,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1181], "a": 1 }, + { "px": [240,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1182], "a": 1 }, + { "px": [248,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1183], "a": 1 }, + { "px": [256,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1184], "a": 1 }, + { "px": [264,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1185], "a": 1 }, + { "px": [272,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1186], "a": 1 }, + { "px": [280,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1187], "a": 1 }, + { "px": [288,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1188], "a": 1 }, + { "px": [296,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1189], "a": 1 }, + { "px": [304,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1190], "a": 1 }, + { "px": [336,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1194], "a": 1 }, + { "px": [344,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1195], "a": 1 }, + { "px": [352,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1196], "a": 1 }, + { "px": [360,192], "src": [240,32], "f": 0, "t": 158, "d": [15,1197], "a": 1 }, + { "px": [104,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1213], "a": 1 }, + { "px": [112,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1214], "a": 1 }, + { "px": [120,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1215], "a": 1 }, + { "px": [128,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1216], "a": 1 }, + { "px": [136,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1217], "a": 1 }, + { "px": [144,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1218], "a": 1 }, + { "px": [152,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1219], "a": 1 }, + { "px": [160,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1220], "a": 1 }, + { "px": [168,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1221], "a": 1 }, + { "px": [176,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1222], "a": 1 }, + { "px": [184,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1223], "a": 1 }, + { "px": [192,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1224], "a": 1 }, + { "px": [200,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1225], "a": 1 }, + { "px": [208,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1226], "a": 1 }, + { "px": [216,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1227], "a": 1 }, + { "px": [224,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1228], "a": 1 }, + { "px": [232,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1229], "a": 1 }, + { "px": [240,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1230], "a": 1 }, + { "px": [248,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1231], "a": 1 }, + { "px": [256,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1232], "a": 1 }, + { "px": [264,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1233], "a": 1 }, + { "px": [272,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1234], "a": 1 }, + { "px": [280,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1235], "a": 1 }, + { "px": [288,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1236], "a": 1 }, + { "px": [296,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1237], "a": 1 }, + { "px": [304,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1238], "a": 1 }, + { "px": [336,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1242], "a": 1 }, + { "px": [344,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1243], "a": 1 }, + { "px": [352,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1244], "a": 1 }, + { "px": [360,200], "src": [240,32], "f": 0, "t": 158, "d": [15,1245], "a": 1 }, + { "px": [104,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1261], "a": 1 }, + { "px": [112,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1262], "a": 1 }, + { "px": [120,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1263], "a": 1 }, + { "px": [128,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1264], "a": 1 }, + { "px": [136,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1265], "a": 1 }, + { "px": [144,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1266], "a": 1 }, + { "px": [152,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1267], "a": 1 }, + { "px": [160,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1268], "a": 1 }, + { "px": [168,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1269], "a": 1 }, + { "px": [176,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1270], "a": 1 }, + { "px": [184,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1271], "a": 1 }, + { "px": [192,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1272], "a": 1 }, + { "px": [200,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1273], "a": 1 }, + { "px": [208,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1274], "a": 1 }, + { "px": [216,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1275], "a": 1 }, + { "px": [224,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1276], "a": 1 }, + { "px": [232,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1277], "a": 1 }, + { "px": [240,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1278], "a": 1 }, + { "px": [248,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1279], "a": 1 }, + { "px": [256,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1280], "a": 1 }, + { "px": [264,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1281], "a": 1 }, + { "px": [272,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1282], "a": 1 }, + { "px": [280,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1283], "a": 1 }, + { "px": [288,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1284], "a": 1 }, + { "px": [296,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1285], "a": 1 }, + { "px": [304,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1286], "a": 1 }, + { "px": [336,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1290], "a": 1 }, + { "px": [344,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1291], "a": 1 }, + { "px": [352,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1292], "a": 1 }, + { "px": [360,208], "src": [240,32], "f": 0, "t": 158, "d": [15,1293], "a": 1 }, + { "px": [104,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1309], "a": 1 }, + { "px": [112,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1310], "a": 1 }, + { "px": [120,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1311], "a": 1 }, + { "px": [128,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1312], "a": 1 }, + { "px": [136,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1313], "a": 1 }, + { "px": [144,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1314], "a": 1 }, + { "px": [152,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1315], "a": 1 }, + { "px": [160,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1316], "a": 1 }, + { "px": [168,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1317], "a": 1 }, + { "px": [176,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1318], "a": 1 }, + { "px": [184,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1319], "a": 1 }, + { "px": [192,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1320], "a": 1 }, + { "px": [200,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1321], "a": 1 }, + { "px": [208,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1322], "a": 1 }, + { "px": [216,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1323], "a": 1 }, + { "px": [224,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1324], "a": 1 }, + { "px": [232,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1325], "a": 1 }, + { "px": [240,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1326], "a": 1 }, + { "px": [248,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1327], "a": 1 }, + { "px": [256,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1328], "a": 1 }, + { "px": [264,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1329], "a": 1 }, + { "px": [272,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1330], "a": 1 }, + { "px": [280,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1331], "a": 1 }, + { "px": [288,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1332], "a": 1 }, + { "px": [296,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1333], "a": 1 }, + { "px": [304,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1334], "a": 1 }, + { "px": [336,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1338], "a": 1 }, + { "px": [344,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1339], "a": 1 }, + { "px": [352,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1340], "a": 1 }, + { "px": [360,216], "src": [240,32], "f": 0, "t": 158, "d": [15,1341], "a": 1 }, + { "px": [104,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1357], "a": 1 }, + { "px": [112,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1358], "a": 1 }, + { "px": [120,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1359], "a": 1 }, + { "px": [128,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1360], "a": 1 }, + { "px": [136,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1361], "a": 1 }, + { "px": [144,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1362], "a": 1 }, + { "px": [152,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1363], "a": 1 }, + { "px": [160,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1364], "a": 1 }, + { "px": [168,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1365], "a": 1 }, + { "px": [176,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1366], "a": 1 }, + { "px": [184,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1367], "a": 1 }, + { "px": [192,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1368], "a": 1 }, + { "px": [200,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1369], "a": 1 }, + { "px": [208,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1370], "a": 1 }, + { "px": [216,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1371], "a": 1 }, + { "px": [224,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1372], "a": 1 }, + { "px": [232,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1373], "a": 1 }, + { "px": [240,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1374], "a": 1 }, + { "px": [248,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1375], "a": 1 }, + { "px": [256,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1376], "a": 1 }, + { "px": [264,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1377], "a": 1 }, + { "px": [272,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1378], "a": 1 }, + { "px": [280,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1379], "a": 1 }, + { "px": [288,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1380], "a": 1 }, + { "px": [296,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1381], "a": 1 }, + { "px": [304,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1382], "a": 1 }, + { "px": [336,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1386], "a": 1 }, + { "px": [344,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1387], "a": 1 }, + { "px": [352,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1388], "a": 1 }, + { "px": [360,224], "src": [240,32], "f": 0, "t": 158, "d": [15,1389], "a": 1 }, + { "px": [104,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1405], "a": 1 }, + { "px": [112,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1406], "a": 1 }, + { "px": [120,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1407], "a": 1 }, + { "px": [128,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1408], "a": 1 }, + { "px": [136,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1409], "a": 1 }, + { "px": [144,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1410], "a": 1 }, + { "px": [152,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1411], "a": 1 }, + { "px": [160,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1412], "a": 1 }, + { "px": [168,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1413], "a": 1 }, + { "px": [176,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1414], "a": 1 }, + { "px": [184,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1415], "a": 1 }, + { "px": [192,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1416], "a": 1 }, + { "px": [200,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1417], "a": 1 }, + { "px": [208,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1418], "a": 1 }, + { "px": [216,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1419], "a": 1 }, + { "px": [224,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1420], "a": 1 }, + { "px": [232,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1421], "a": 1 }, + { "px": [240,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1422], "a": 1 }, + { "px": [248,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1423], "a": 1 }, + { "px": [256,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1424], "a": 1 }, + { "px": [264,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1425], "a": 1 }, + { "px": [272,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1426], "a": 1 }, + { "px": [280,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1427], "a": 1 }, + { "px": [288,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1428], "a": 1 }, + { "px": [296,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1429], "a": 1 }, + { "px": [304,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1430], "a": 1 }, + { "px": [336,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1434], "a": 1 }, + { "px": [344,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1435], "a": 1 }, + { "px": [352,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1436], "a": 1 }, + { "px": [360,232], "src": [240,32], "f": 0, "t": 158, "d": [15,1437], "a": 1 }, + { "px": [96,32], "src": [232,32], "f": 0, "t": 157, "d": [14,204], "a": 1 }, + { "px": [328,32], "src": [232,32], "f": 0, "t": 157, "d": [14,233], "a": 1 }, + { "px": [96,40], "src": [232,32], "f": 0, "t": 157, "d": [14,252], "a": 1 }, + { "px": [328,40], "src": [232,32], "f": 0, "t": 157, "d": [14,281], "a": 1 }, + { "px": [96,48], "src": [232,32], "f": 0, "t": 157, "d": [14,300], "a": 1 }, + { "px": [328,48], "src": [232,32], "f": 0, "t": 157, "d": [14,329], "a": 1 }, + { "px": [96,56], "src": [232,32], "f": 0, "t": 157, "d": [14,348], "a": 1 }, + { "px": [328,56], "src": [232,32], "f": 0, "t": 157, "d": [14,377], "a": 1 }, + { "px": [96,64], "src": [232,32], "f": 0, "t": 157, "d": [14,396], "a": 1 }, + { "px": [328,64], "src": [232,32], "f": 0, "t": 157, "d": [14,425], "a": 1 }, + { "px": [96,72], "src": [232,32], "f": 0, "t": 157, "d": [14,444], "a": 1 }, + { "px": [328,72], "src": [232,32], "f": 0, "t": 157, "d": [14,473], "a": 1 }, + { "px": [96,80], "src": [232,32], "f": 0, "t": 157, "d": [14,492], "a": 1 }, + { "px": [328,80], "src": [232,32], "f": 0, "t": 157, "d": [14,521], "a": 1 }, + { "px": [96,88], "src": [232,32], "f": 0, "t": 157, "d": [14,540], "a": 1 }, + { "px": [328,88], "src": [232,32], "f": 0, "t": 157, "d": [14,569], "a": 1 }, + { "px": [96,96], "src": [232,32], "f": 0, "t": 157, "d": [14,588], "a": 1 }, + { "px": [328,96], "src": [232,32], "f": 0, "t": 157, "d": [14,617], "a": 1 }, + { "px": [96,104], "src": [232,32], "f": 0, "t": 157, "d": [14,636], "a": 1 }, + { "px": [328,104], "src": [232,32], "f": 0, "t": 157, "d": [14,665], "a": 1 }, + { "px": [96,112], "src": [232,32], "f": 0, "t": 157, "d": [14,684], "a": 1 }, + { "px": [328,112], "src": [232,32], "f": 0, "t": 157, "d": [14,713], "a": 1 }, + { "px": [96,120], "src": [232,32], "f": 0, "t": 157, "d": [14,732], "a": 1 }, + { "px": [328,120], "src": [232,32], "f": 0, "t": 157, "d": [14,761], "a": 1 }, + { "px": [96,128], "src": [232,32], "f": 0, "t": 157, "d": [14,780], "a": 1 }, + { "px": [328,128], "src": [232,32], "f": 0, "t": 157, "d": [14,809], "a": 1 }, + { "px": [96,136], "src": [232,32], "f": 0, "t": 157, "d": [14,828], "a": 1 }, + { "px": [328,136], "src": [232,32], "f": 0, "t": 157, "d": [14,857], "a": 1 }, + { "px": [328,144], "src": [232,32], "f": 0, "t": 157, "d": [14,905], "a": 1 }, + { "px": [328,152], "src": [232,32], "f": 0, "t": 157, "d": [14,953], "a": 1 }, + { "px": [328,160], "src": [232,32], "f": 0, "t": 157, "d": [14,1001], "a": 1 }, + { "px": [96,168], "src": [232,32], "f": 0, "t": 157, "d": [14,1020], "a": 1 }, + { "px": [328,168], "src": [232,32], "f": 0, "t": 157, "d": [14,1049], "a": 1 }, + { "px": [96,176], "src": [232,32], "f": 0, "t": 157, "d": [14,1068], "a": 1 }, + { "px": [328,176], "src": [232,32], "f": 0, "t": 157, "d": [14,1097], "a": 1 }, + { "px": [96,184], "src": [232,32], "f": 0, "t": 157, "d": [14,1116], "a": 1 }, + { "px": [328,184], "src": [232,32], "f": 0, "t": 157, "d": [14,1145], "a": 1 }, + { "px": [96,192], "src": [232,32], "f": 0, "t": 157, "d": [14,1164], "a": 1 }, + { "px": [328,192], "src": [232,32], "f": 0, "t": 157, "d": [14,1193], "a": 1 }, + { "px": [96,200], "src": [232,32], "f": 0, "t": 157, "d": [14,1212], "a": 1 }, + { "px": [328,200], "src": [232,32], "f": 0, "t": 157, "d": [14,1241], "a": 1 }, + { "px": [96,208], "src": [232,32], "f": 0, "t": 157, "d": [14,1260], "a": 1 }, + { "px": [328,208], "src": [232,32], "f": 0, "t": 157, "d": [14,1289], "a": 1 }, + { "px": [96,216], "src": [232,32], "f": 0, "t": 157, "d": [14,1308], "a": 1 }, + { "px": [328,216], "src": [232,32], "f": 0, "t": 157, "d": [14,1337], "a": 1 }, + { "px": [96,224], "src": [232,32], "f": 0, "t": 157, "d": [14,1356], "a": 1 }, + { "px": [328,224], "src": [232,32], "f": 0, "t": 157, "d": [14,1385], "a": 1 }, + { "px": [96,232], "src": [232,32], "f": 0, "t": 157, "d": [14,1404], "a": 1 }, + { "px": [328,232], "src": [232,32], "f": 0, "t": 157, "d": [14,1433], "a": 1 }, + { "px": [104,144], "src": [240,40], "f": 0, "t": 190, "d": [13,877], "a": 1 }, + { "px": [112,144], "src": [240,40], "f": 0, "t": 190, "d": [13,878], "a": 1 }, + { "px": [120,144], "src": [240,40], "f": 0, "t": 190, "d": [13,879], "a": 1 }, + { "px": [128,144], "src": [240,40], "f": 0, "t": 190, "d": [13,880], "a": 1 }, + { "px": [136,144], "src": [240,40], "f": 0, "t": 190, "d": [13,881], "a": 1 }, + { "px": [144,144], "src": [240,40], "f": 0, "t": 190, "d": [13,882], "a": 1 }, + { "px": [152,144], "src": [240,40], "f": 0, "t": 190, "d": [13,883], "a": 1 }, + { "px": [160,144], "src": [240,40], "f": 0, "t": 190, "d": [13,884], "a": 1 }, + { "px": [168,144], "src": [240,40], "f": 0, "t": 190, "d": [13,885], "a": 1 }, + { "px": [176,144], "src": [240,40], "f": 0, "t": 190, "d": [13,886], "a": 1 }, + { "px": [184,144], "src": [240,40], "f": 0, "t": 190, "d": [13,887], "a": 1 }, + { "px": [192,144], "src": [240,40], "f": 0, "t": 190, "d": [13,888], "a": 1 }, + { "px": [200,144], "src": [240,40], "f": 0, "t": 190, "d": [13,889], "a": 1 }, + { "px": [208,144], "src": [240,40], "f": 0, "t": 190, "d": [13,890], "a": 1 }, + { "px": [216,144], "src": [240,40], "f": 0, "t": 190, "d": [13,891], "a": 1 }, + { "px": [224,144], "src": [240,40], "f": 0, "t": 190, "d": [13,892], "a": 1 }, + { "px": [232,144], "src": [240,40], "f": 0, "t": 190, "d": [13,893], "a": 1 }, + { "px": [240,144], "src": [240,40], "f": 0, "t": 190, "d": [13,894], "a": 1 }, + { "px": [248,144], "src": [240,40], "f": 0, "t": 190, "d": [13,895], "a": 1 }, + { "px": [256,144], "src": [240,40], "f": 0, "t": 190, "d": [13,896], "a": 1 }, + { "px": [264,144], "src": [240,40], "f": 0, "t": 190, "d": [13,897], "a": 1 }, + { "px": [272,144], "src": [240,40], "f": 0, "t": 190, "d": [13,898], "a": 1 }, + { "px": [280,144], "src": [240,40], "f": 0, "t": 190, "d": [13,899], "a": 1 }, + { "px": [288,144], "src": [240,40], "f": 0, "t": 190, "d": [13,900], "a": 1 }, + { "px": [296,144], "src": [240,40], "f": 0, "t": 190, "d": [13,901], "a": 1 }, + { "px": [304,144], "src": [240,40], "f": 0, "t": 190, "d": [13,902], "a": 1 }, + { "px": [104,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1453], "a": 1 }, + { "px": [112,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1454], "a": 1 }, + { "px": [120,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1455], "a": 1 }, + { "px": [128,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1456], "a": 1 }, + { "px": [136,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1457], "a": 1 }, + { "px": [144,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1458], "a": 1 }, + { "px": [152,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1459], "a": 1 }, + { "px": [160,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1460], "a": 1 }, + { "px": [168,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1461], "a": 1 }, + { "px": [176,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1462], "a": 1 }, + { "px": [184,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1463], "a": 1 }, + { "px": [192,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1464], "a": 1 }, + { "px": [200,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1465], "a": 1 }, + { "px": [208,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1466], "a": 1 }, + { "px": [216,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1467], "a": 1 }, + { "px": [224,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1468], "a": 1 }, + { "px": [232,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1469], "a": 1 }, + { "px": [240,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1470], "a": 1 }, + { "px": [248,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1471], "a": 1 }, + { "px": [256,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1472], "a": 1 }, + { "px": [264,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1473], "a": 1 }, + { "px": [272,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1474], "a": 1 }, + { "px": [280,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1475], "a": 1 }, + { "px": [288,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1476], "a": 1 }, + { "px": [296,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1477], "a": 1 }, + { "px": [304,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1478], "a": 1 }, + { "px": [336,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1482], "a": 1 }, + { "px": [344,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1483], "a": 1 }, + { "px": [352,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1484], "a": 1 }, + { "px": [360,240], "src": [240,40], "f": 0, "t": 190, "d": [13,1485], "a": 1 }, + { "px": [312,32], "src": [248,32], "f": 0, "t": 159, "d": [12,231], "a": 1 }, + { "px": [368,32], "src": [248,32], "f": 0, "t": 159, "d": [12,238], "a": 1 }, + { "px": [312,40], "src": [248,32], "f": 0, "t": 159, "d": [12,279], "a": 1 }, + { "px": [368,40], "src": [248,32], "f": 0, "t": 159, "d": [12,286], "a": 1 }, + { "px": [312,48], "src": [248,32], "f": 0, "t": 159, "d": [12,327], "a": 1 }, + { "px": [368,48], "src": [248,32], "f": 0, "t": 159, "d": [12,334], "a": 1 }, + { "px": [312,56], "src": [248,32], "f": 0, "t": 159, "d": [12,375], "a": 1 }, + { "px": [368,56], "src": [248,32], "f": 0, "t": 159, "d": [12,382], "a": 1 }, + { "px": [312,64], "src": [248,32], "f": 0, "t": 159, "d": [12,423], "a": 1 }, + { "px": [368,64], "src": [248,32], "f": 0, "t": 159, "d": [12,430], "a": 1 }, + { "px": [312,72], "src": [248,32], "f": 0, "t": 159, "d": [12,471], "a": 1 }, + { "px": [368,72], "src": [248,32], "f": 0, "t": 159, "d": [12,478], "a": 1 }, + { "px": [312,80], "src": [248,32], "f": 0, "t": 159, "d": [12,519], "a": 1 }, + { "px": [368,80], "src": [248,32], "f": 0, "t": 159, "d": [12,526], "a": 1 }, + { "px": [312,88], "src": [248,32], "f": 0, "t": 159, "d": [12,567], "a": 1 }, + { "px": [368,88], "src": [248,32], "f": 0, "t": 159, "d": [12,574], "a": 1 }, + { "px": [312,96], "src": [248,32], "f": 0, "t": 159, "d": [12,615], "a": 1 }, + { "px": [368,96], "src": [248,32], "f": 0, "t": 159, "d": [12,622], "a": 1 }, + { "px": [312,104], "src": [248,32], "f": 0, "t": 159, "d": [12,663], "a": 1 }, + { "px": [368,104], "src": [248,32], "f": 0, "t": 159, "d": [12,670], "a": 1 }, + { "px": [312,112], "src": [248,32], "f": 0, "t": 159, "d": [12,711], "a": 1 }, + { "px": [368,112], "src": [248,32], "f": 0, "t": 159, "d": [12,718], "a": 1 }, + { "px": [312,120], "src": [248,32], "f": 0, "t": 159, "d": [12,759], "a": 1 }, + { "px": [368,120], "src": [248,32], "f": 0, "t": 159, "d": [12,766], "a": 1 }, + { "px": [312,128], "src": [248,32], "f": 0, "t": 159, "d": [12,807], "a": 1 }, + { "px": [368,128], "src": [248,32], "f": 0, "t": 159, "d": [12,814], "a": 1 }, + { "px": [312,136], "src": [248,32], "f": 0, "t": 159, "d": [12,855], "a": 1 }, + { "px": [368,136], "src": [248,32], "f": 0, "t": 159, "d": [12,862], "a": 1 }, + { "px": [368,144], "src": [248,32], "f": 0, "t": 159, "d": [12,910], "a": 1 }, + { "px": [368,152], "src": [248,32], "f": 0, "t": 159, "d": [12,958], "a": 1 }, + { "px": [368,160], "src": [248,32], "f": 0, "t": 159, "d": [12,1006], "a": 1 }, + { "px": [312,168], "src": [248,32], "f": 0, "t": 159, "d": [12,1047], "a": 1 }, + { "px": [368,168], "src": [248,32], "f": 0, "t": 159, "d": [12,1054], "a": 1 }, + { "px": [312,176], "src": [248,32], "f": 0, "t": 159, "d": [12,1095], "a": 1 }, + { "px": [368,176], "src": [248,32], "f": 0, "t": 159, "d": [12,1102], "a": 1 }, + { "px": [312,184], "src": [248,32], "f": 0, "t": 159, "d": [12,1143], "a": 1 }, + { "px": [368,184], "src": [248,32], "f": 0, "t": 159, "d": [12,1150], "a": 1 }, + { "px": [312,192], "src": [248,32], "f": 0, "t": 159, "d": [12,1191], "a": 1 }, + { "px": [368,192], "src": [248,32], "f": 0, "t": 159, "d": [12,1198], "a": 1 }, + { "px": [312,200], "src": [248,32], "f": 0, "t": 159, "d": [12,1239], "a": 1 }, + { "px": [368,200], "src": [248,32], "f": 0, "t": 159, "d": [12,1246], "a": 1 }, + { "px": [312,208], "src": [248,32], "f": 0, "t": 159, "d": [12,1287], "a": 1 }, + { "px": [368,208], "src": [248,32], "f": 0, "t": 159, "d": [12,1294], "a": 1 }, + { "px": [312,216], "src": [248,32], "f": 0, "t": 159, "d": [12,1335], "a": 1 }, + { "px": [368,216], "src": [248,32], "f": 0, "t": 159, "d": [12,1342], "a": 1 }, + { "px": [312,224], "src": [248,32], "f": 0, "t": 159, "d": [12,1383], "a": 1 }, + { "px": [368,224], "src": [248,32], "f": 0, "t": 159, "d": [12,1390], "a": 1 }, + { "px": [312,232], "src": [248,32], "f": 0, "t": 159, "d": [12,1431], "a": 1 }, + { "px": [368,232], "src": [248,32], "f": 0, "t": 159, "d": [12,1438], "a": 1 }, + { "px": [104,24], "src": [240,24], "f": 0, "t": 126, "d": [11,157], "a": 1 }, + { "px": [112,24], "src": [240,24], "f": 0, "t": 126, "d": [11,158], "a": 1 }, + { "px": [120,24], "src": [240,24], "f": 0, "t": 126, "d": [11,159], "a": 1 }, + { "px": [128,24], "src": [240,24], "f": 0, "t": 126, "d": [11,160], "a": 1 }, + { "px": [136,24], "src": [240,24], "f": 0, "t": 126, "d": [11,161], "a": 1 }, + { "px": [144,24], "src": [240,24], "f": 0, "t": 126, "d": [11,162], "a": 1 }, + { "px": [152,24], "src": [240,24], "f": 0, "t": 126, "d": [11,163], "a": 1 }, + { "px": [160,24], "src": [240,24], "f": 0, "t": 126, "d": [11,164], "a": 1 }, + { "px": [168,24], "src": [240,24], "f": 0, "t": 126, "d": [11,165], "a": 1 }, + { "px": [176,24], "src": [240,24], "f": 0, "t": 126, "d": [11,166], "a": 1 }, + { "px": [184,24], "src": [240,24], "f": 0, "t": 126, "d": [11,167], "a": 1 }, + { "px": [192,24], "src": [240,24], "f": 0, "t": 126, "d": [11,168], "a": 1 }, + { "px": [200,24], "src": [240,24], "f": 0, "t": 126, "d": [11,169], "a": 1 }, + { "px": [208,24], "src": [240,24], "f": 0, "t": 126, "d": [11,170], "a": 1 }, + { "px": [216,24], "src": [240,24], "f": 0, "t": 126, "d": [11,171], "a": 1 }, + { "px": [224,24], "src": [240,24], "f": 0, "t": 126, "d": [11,172], "a": 1 }, + { "px": [232,24], "src": [240,24], "f": 0, "t": 126, "d": [11,173], "a": 1 }, + { "px": [240,24], "src": [240,24], "f": 0, "t": 126, "d": [11,174], "a": 1 }, + { "px": [248,24], "src": [240,24], "f": 0, "t": 126, "d": [11,175], "a": 1 }, + { "px": [256,24], "src": [240,24], "f": 0, "t": 126, "d": [11,176], "a": 1 }, + { "px": [264,24], "src": [240,24], "f": 0, "t": 126, "d": [11,177], "a": 1 }, + { "px": [272,24], "src": [240,24], "f": 0, "t": 126, "d": [11,178], "a": 1 }, + { "px": [280,24], "src": [240,24], "f": 0, "t": 126, "d": [11,179], "a": 1 }, + { "px": [288,24], "src": [240,24], "f": 0, "t": 126, "d": [11,180], "a": 1 }, + { "px": [296,24], "src": [240,24], "f": 0, "t": 126, "d": [11,181], "a": 1 }, + { "px": [304,24], "src": [240,24], "f": 0, "t": 126, "d": [11,182], "a": 1 }, + { "px": [336,24], "src": [240,24], "f": 0, "t": 126, "d": [11,186], "a": 1 }, + { "px": [344,24], "src": [240,24], "f": 0, "t": 126, "d": [11,187], "a": 1 }, + { "px": [352,24], "src": [240,24], "f": 0, "t": 126, "d": [11,188], "a": 1 }, + { "px": [360,24], "src": [240,24], "f": 0, "t": 126, "d": [11,189], "a": 1 }, + { "px": [104,160], "src": [240,24], "f": 0, "t": 126, "d": [11,973], "a": 1 }, + { "px": [112,160], "src": [240,24], "f": 0, "t": 126, "d": [11,974], "a": 1 }, + { "px": [120,160], "src": [240,24], "f": 0, "t": 126, "d": [11,975], "a": 1 }, + { "px": [128,160], "src": [240,24], "f": 0, "t": 126, "d": [11,976], "a": 1 }, + { "px": [136,160], "src": [240,24], "f": 0, "t": 126, "d": [11,977], "a": 1 }, + { "px": [144,160], "src": [240,24], "f": 0, "t": 126, "d": [11,978], "a": 1 }, + { "px": [152,160], "src": [240,24], "f": 0, "t": 126, "d": [11,979], "a": 1 }, + { "px": [160,160], "src": [240,24], "f": 0, "t": 126, "d": [11,980], "a": 1 }, + { "px": [168,160], "src": [240,24], "f": 0, "t": 126, "d": [11,981], "a": 1 }, + { "px": [176,160], "src": [240,24], "f": 0, "t": 126, "d": [11,982], "a": 1 }, + { "px": [184,160], "src": [240,24], "f": 0, "t": 126, "d": [11,983], "a": 1 }, + { "px": [192,160], "src": [240,24], "f": 0, "t": 126, "d": [11,984], "a": 1 }, + { "px": [200,160], "src": [240,24], "f": 0, "t": 126, "d": [11,985], "a": 1 }, + { "px": [208,160], "src": [240,24], "f": 0, "t": 126, "d": [11,986], "a": 1 }, + { "px": [216,160], "src": [240,24], "f": 0, "t": 126, "d": [11,987], "a": 1 }, + { "px": [224,160], "src": [240,24], "f": 0, "t": 126, "d": [11,988], "a": 1 }, + { "px": [232,160], "src": [240,24], "f": 0, "t": 126, "d": [11,989], "a": 1 }, + { "px": [240,160], "src": [240,24], "f": 0, "t": 126, "d": [11,990], "a": 1 }, + { "px": [248,160], "src": [240,24], "f": 0, "t": 126, "d": [11,991], "a": 1 }, + { "px": [256,160], "src": [240,24], "f": 0, "t": 126, "d": [11,992], "a": 1 }, + { "px": [264,160], "src": [240,24], "f": 0, "t": 126, "d": [11,993], "a": 1 }, + { "px": [272,160], "src": [240,24], "f": 0, "t": 126, "d": [11,994], "a": 1 }, + { "px": [280,160], "src": [240,24], "f": 0, "t": 126, "d": [11,995], "a": 1 }, + { "px": [288,160], "src": [240,24], "f": 0, "t": 126, "d": [11,996], "a": 1 }, + { "px": [296,160], "src": [240,24], "f": 0, "t": 126, "d": [11,997], "a": 1 }, + { "px": [304,160], "src": [240,24], "f": 0, "t": 126, "d": [11,998], "a": 1 }, + { "px": [96,144], "src": [232,40], "f": 0, "t": 189, "d": [10,876], "a": 1 }, + { "px": [96,240], "src": [232,40], "f": 0, "t": 189, "d": [10,1452], "a": 1 }, + { "px": [328,240], "src": [232,40], "f": 0, "t": 189, "d": [10,1481], "a": 1 }, + { "px": [312,144], "src": [248,40], "f": 0, "t": 191, "d": [9,903], "a": 1 }, + { "px": [312,240], "src": [248,40], "f": 0, "t": 191, "d": [9,1479], "a": 1 }, + { "px": [368,240], "src": [248,40], "f": 0, "t": 191, "d": [9,1486], "a": 1 }, + { "px": [312,24], "src": [248,24], "f": 0, "t": 127, "d": [8,183], "a": 1 }, + { "px": [368,24], "src": [248,24], "f": 0, "t": 127, "d": [8,190], "a": 1 }, + { "px": [312,160], "src": [248,24], "f": 0, "t": 127, "d": [8,999], "a": 1 }, + { "px": [96,24], "src": [232,24], "f": 0, "t": 125, "d": [7,156], "a": 1 }, + { "px": [328,24], "src": [232,24], "f": 0, "t": 125, "d": [7,185], "a": 1 }, + { "px": [96,160], "src": [232,24], "f": 0, "t": 125, "d": [7,972], "a": 1 } + ], + "seed": 8750381, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "Menu", + "__type": "IntGrid", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "02014054-ac70-11f0-b965-0741f9c09fe2", + "levelId": 101, + "layerDefUid": 28, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [ + 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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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, + 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,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,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,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,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,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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + ], + "autoLayerTiles": [], + "seed": 357163, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "WidgetBorder", + "__type": "IntGrid", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "02014055-ac70-11f0-b965-d10a22980b67", + "levelId": 101, + "layerDefUid": 1, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [ + 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,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,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,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,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1, + 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0, + 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1, + 1,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,0, + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1, + 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0, + 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1, + 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1, + 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + ], + "autoLayerTiles": [], + "seed": 1153446, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "02014056-ac70-11f0-b965-29853b8d9802", + "levelId": 101, + "layerDefUid": 27, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 2936839, + "overrideTilesetUid": null, + "gridTiles": [ + { "px": [0,0], "src": [240,8], "f": 0, "t": 62, "d": [0], "a": 1 }, + { "px": [8,0], "src": [240,8], "f": 0, "t": 62, "d": [1], "a": 1 }, + { "px": [16,0], "src": [240,8], "f": 0, "t": 62, "d": [2], "a": 1 }, + { "px": [32,0], "src": [240,8], "f": 0, "t": 62, "d": [4], "a": 1 }, + { "px": [40,0], "src": [240,8], "f": 0, "t": 62, "d": [5], "a": 1 }, + { "px": [48,0], "src": [240,8], "f": 0, "t": 62, "d": [6], "a": 1 }, + { "px": [56,0], "src": [240,8], "f": 0, "t": 62, "d": [7], "a": 1 }, + { "px": [64,0], "src": [240,8], "f": 0, "t": 62, "d": [8], "a": 1 }, + { "px": [72,0], "src": [240,8], "f": 0, "t": 62, "d": [9], "a": 1 }, + { "px": [80,0], "src": [240,8], "f": 0, "t": 62, "d": [10], "a": 1 }, + { "px": [88,0], "src": [240,8], "f": 0, "t": 62, "d": [11], "a": 1 }, + { "px": [96,0], "src": [240,8], "f": 0, "t": 62, "d": [12], "a": 1 }, + { "px": [104,0], "src": [240,8], "f": 0, "t": 62, "d": [13], "a": 1 }, + { "px": [112,0], "src": [240,8], "f": 0, "t": 62, "d": [14], "a": 1 }, + { "px": [120,0], "src": [240,8], "f": 0, "t": 62, "d": [15], "a": 1 }, + { "px": [128,0], "src": [240,8], "f": 0, "t": 62, "d": [16], "a": 1 }, + { "px": [136,0], "src": [240,8], "f": 0, "t": 62, "d": [17], "a": 1 }, + { "px": [144,0], "src": [240,8], "f": 0, "t": 62, "d": [18], "a": 1 }, + { "px": [152,0], "src": [240,8], "f": 0, "t": 62, "d": [19], "a": 1 }, + { "px": [160,0], "src": [240,8], "f": 0, "t": 62, "d": [20], "a": 1 }, + { "px": [168,0], "src": [240,8], "f": 0, "t": 62, "d": [21], "a": 1 }, + { "px": [176,0], "src": [240,8], "f": 0, "t": 62, "d": [22], "a": 1 }, + { "px": [184,0], "src": [240,8], "f": 0, "t": 62, "d": [23], "a": 1 }, + { "px": [192,0], "src": [240,8], "f": 0, "t": 62, "d": [24], "a": 1 }, + { "px": [200,0], "src": [240,8], "f": 0, "t": 62, "d": [25], "a": 1 }, + { "px": [208,0], "src": [240,8], "f": 0, "t": 62, "d": [26], "a": 1 }, + { "px": [216,0], "src": [240,8], "f": 0, "t": 62, "d": [27], "a": 1 }, + { "px": [224,0], "src": [240,8], "f": 0, "t": 62, "d": [28], "a": 1 }, + { "px": [232,0], "src": [240,8], "f": 0, "t": 62, "d": [29], "a": 1 }, + { "px": [240,0], "src": [240,8], "f": 0, "t": 62, "d": [30], "a": 1 }, + { "px": [248,0], "src": [240,8], "f": 0, "t": 62, "d": [31], "a": 1 }, + { "px": [256,0], "src": [240,8], "f": 0, "t": 62, "d": [32], "a": 1 }, + { "px": [264,0], "src": [240,8], "f": 0, "t": 62, "d": [33], "a": 1 }, + { "px": [272,0], "src": [240,8], "f": 0, "t": 62, "d": [34], "a": 1 }, + { "px": [280,0], "src": [240,8], "f": 0, "t": 62, "d": [35], "a": 1 }, + { "px": [288,0], "src": [240,8], "f": 0, "t": 62, "d": [36], "a": 1 }, + { "px": [296,0], "src": [240,8], "f": 0, "t": 62, "d": [37], "a": 1 }, + { "px": [304,0], "src": [240,8], "f": 0, "t": 62, "d": [38], "a": 1 }, + { "px": [312,0], "src": [240,8], "f": 0, "t": 62, "d": [39], "a": 1 }, + { "px": [320,0], "src": [240,8], "f": 0, "t": 62, "d": [40], "a": 1 }, + { "px": [328,0], "src": [240,8], "f": 0, "t": 62, "d": [41], "a": 1 }, + { "px": [336,0], "src": [240,8], "f": 0, "t": 62, "d": [42], "a": 1 }, + { "px": [344,0], "src": [240,8], "f": 0, "t": 62, "d": [43], "a": 1 }, + { "px": [352,0], "src": [240,8], "f": 0, "t": 62, "d": [44], "a": 1 }, + { "px": [360,0], "src": [240,8], "f": 0, "t": 62, "d": [45], "a": 1 }, + { "px": [368,0], "src": [240,8], "f": 0, "t": 62, "d": [46], "a": 1 }, + { "px": [376,0], "src": [240,8], "f": 0, "t": 62, "d": [47], "a": 1 }, + { "px": [0,8], "src": [240,8], "f": 0, "t": 62, "d": [48], "a": 1 }, + { "px": [8,8], "src": [240,8], "f": 0, "t": 62, "d": [49], "a": 1 }, + { "px": [16,8], "src": [240,8], "f": 0, "t": 62, "d": [50], "a": 1 }, + { "px": [32,8], "src": [240,8], "f": 0, "t": 62, "d": [52], "a": 1 }, + { "px": [40,8], "src": [240,8], "f": 0, "t": 62, "d": [53], "a": 1 }, + { "px": [48,8], "src": [240,8], "f": 0, "t": 62, "d": [54], "a": 1 }, + { "px": [56,8], "src": [240,8], "f": 0, "t": 62, "d": [55], "a": 1 }, + { "px": [64,8], "src": [240,8], "f": 0, "t": 62, "d": [56], "a": 1 }, + { "px": [72,8], "src": [240,8], "f": 0, "t": 62, "d": [57], "a": 1 }, + { "px": [80,8], "src": [240,8], "f": 0, "t": 62, "d": [58], "a": 1 }, + { "px": [88,8], "src": [240,8], "f": 0, "t": 62, "d": [59], "a": 1 }, + { "px": [96,8], "src": [240,8], "f": 0, "t": 62, "d": [60], "a": 1 }, + { "px": [104,8], "src": [240,8], "f": 0, "t": 62, "d": [61], "a": 1 }, + { "px": [112,8], "src": [240,8], "f": 0, "t": 62, "d": [62], "a": 1 }, + { "px": [120,8], "src": [240,8], "f": 0, "t": 62, "d": [63], "a": 1 }, + { "px": [128,8], "src": [240,8], "f": 0, "t": 62, "d": [64], "a": 1 }, + { "px": [136,8], "src": [240,8], "f": 0, "t": 62, "d": [65], "a": 1 }, + { "px": [144,8], "src": [240,8], "f": 0, "t": 62, "d": [66], "a": 1 }, + { "px": [152,8], "src": [240,8], "f": 0, "t": 62, "d": [67], "a": 1 }, + { "px": [160,8], "src": [240,8], "f": 0, "t": 62, "d": [68], "a": 1 }, + { "px": [168,8], "src": [240,8], "f": 0, "t": 62, "d": [69], "a": 1 }, + { "px": [176,8], "src": [240,8], "f": 0, "t": 62, "d": [70], "a": 1 }, + { "px": [184,8], "src": [240,8], "f": 0, "t": 62, "d": [71], "a": 1 }, + { "px": [192,8], "src": [240,8], "f": 0, "t": 62, "d": [72], "a": 1 }, + { "px": [200,8], "src": [240,8], "f": 0, "t": 62, "d": [73], "a": 1 }, + { "px": [208,8], "src": [240,8], "f": 0, "t": 62, "d": [74], "a": 1 }, + { "px": [216,8], "src": [240,8], "f": 0, "t": 62, "d": [75], "a": 1 }, + { "px": [224,8], "src": [240,8], "f": 0, "t": 62, "d": [76], "a": 1 }, + { "px": [232,8], "src": [240,8], "f": 0, "t": 62, "d": [77], "a": 1 }, + { "px": [240,8], "src": [240,8], "f": 0, "t": 62, "d": [78], "a": 1 }, + { "px": [248,8], "src": [240,8], "f": 0, "t": 62, "d": [79], "a": 1 }, + { "px": [256,8], "src": [240,8], "f": 0, "t": 62, "d": [80], "a": 1 }, + { "px": [264,8], "src": [240,8], "f": 0, "t": 62, "d": [81], "a": 1 }, + { "px": [272,8], "src": [240,8], "f": 0, "t": 62, "d": [82], "a": 1 }, + { "px": [280,8], "src": [240,8], "f": 0, "t": 62, "d": [83], "a": 1 }, + { "px": [288,8], "src": [240,8], "f": 0, "t": 62, "d": [84], "a": 1 }, + { "px": [296,8], "src": [240,8], "f": 0, "t": 62, "d": [85], "a": 1 }, + { "px": [304,8], "src": [240,8], "f": 0, "t": 62, "d": [86], "a": 1 }, + { "px": [312,8], "src": [240,8], "f": 0, "t": 62, "d": [87], "a": 1 }, + { "px": [320,8], "src": [240,8], "f": 0, "t": 62, "d": [88], "a": 1 }, + { "px": [328,8], "src": [240,8], "f": 0, "t": 62, "d": [89], "a": 1 }, + { "px": [336,8], "src": [240,8], "f": 0, "t": 62, "d": [90], "a": 1 }, + { "px": [344,8], "src": [240,8], "f": 0, "t": 62, "d": [91], "a": 1 }, + { "px": [352,8], "src": [240,8], "f": 0, "t": 62, "d": [92], "a": 1 }, + { "px": [360,8], "src": [240,8], "f": 0, "t": 62, "d": [93], "a": 1 }, + { "px": [368,8], "src": [240,8], "f": 0, "t": 62, "d": [94], "a": 1 }, + { "px": [376,8], "src": [240,8], "f": 0, "t": 62, "d": [95], "a": 1 }, + { "px": [0,16], "src": [240,8], "f": 0, "t": 62, "d": [96], "a": 1 }, + { "px": [8,16], "src": [240,8], "f": 0, "t": 62, "d": [97], "a": 1 }, + { "px": [16,16], "src": [240,8], "f": 0, "t": 62, "d": [98], "a": 1 }, + { "px": [24,16], "src": [240,8], "f": 0, "t": 62, "d": [99], "a": 1 }, + { "px": [32,16], "src": [240,8], "f": 0, "t": 62, "d": [100], "a": 1 }, + { "px": [40,16], "src": [240,8], "f": 0, "t": 62, "d": [101], "a": 1 }, + { "px": [48,16], "src": [240,8], "f": 0, "t": 62, "d": [102], "a": 1 }, + { "px": [56,16], "src": [240,8], "f": 0, "t": 62, "d": [103], "a": 1 }, + { "px": [64,16], "src": [240,8], "f": 0, "t": 62, "d": [104], "a": 1 }, + { "px": [72,16], "src": [240,8], "f": 0, "t": 62, "d": [105], "a": 1 }, + { "px": [80,16], "src": [240,8], "f": 0, "t": 62, "d": [106], "a": 1 }, + { "px": [88,16], "src": [240,8], "f": 0, "t": 62, "d": [107], "a": 1 }, + { "px": [96,16], "src": [240,8], "f": 0, "t": 62, "d": [108], "a": 1 }, + { "px": [104,16], "src": [240,8], "f": 0, "t": 62, "d": [109], "a": 1 }, + { "px": [112,16], "src": [240,8], "f": 0, "t": 62, "d": [110], "a": 1 }, + { "px": [120,16], "src": [240,8], "f": 0, "t": 62, "d": [111], "a": 1 }, + { "px": [128,16], "src": [240,8], "f": 0, "t": 62, "d": [112], "a": 1 }, + { "px": [136,16], "src": [240,8], "f": 0, "t": 62, "d": [113], "a": 1 }, + { "px": [144,16], "src": [240,8], "f": 0, "t": 62, "d": [114], "a": 1 }, + { "px": [152,16], "src": [240,8], "f": 0, "t": 62, "d": [115], "a": 1 }, + { "px": [160,16], "src": [240,8], "f": 0, "t": 62, "d": [116], "a": 1 }, + { "px": [168,16], "src": [240,8], "f": 0, "t": 62, "d": [117], "a": 1 }, + { "px": [176,16], "src": [240,8], "f": 0, "t": 62, "d": [118], "a": 1 }, + { "px": [184,16], "src": [240,8], "f": 0, "t": 62, "d": [119], "a": 1 }, + { "px": [192,16], "src": [240,8], "f": 0, "t": 62, "d": [120], "a": 1 }, + { "px": [200,16], "src": [240,8], "f": 0, "t": 62, "d": [121], "a": 1 }, + { "px": [208,16], "src": [240,8], "f": 0, "t": 62, "d": [122], "a": 1 }, + { "px": [216,16], "src": [240,8], "f": 0, "t": 62, "d": [123], "a": 1 }, + { "px": [224,16], "src": [240,8], "f": 0, "t": 62, "d": [124], "a": 1 }, + { "px": [232,16], "src": [240,8], "f": 0, "t": 62, "d": [125], "a": 1 }, + { "px": [240,16], "src": [240,8], "f": 0, "t": 62, "d": [126], "a": 1 }, + { "px": [248,16], "src": [240,8], "f": 0, "t": 62, "d": [127], "a": 1 }, + { "px": [256,16], "src": [240,8], "f": 0, "t": 62, "d": [128], "a": 1 }, + { "px": [264,16], "src": [240,8], "f": 0, "t": 62, "d": [129], "a": 1 }, + { "px": [272,16], "src": [240,8], "f": 0, "t": 62, "d": [130], "a": 1 }, + { "px": [280,16], "src": [240,8], "f": 0, "t": 62, "d": [131], "a": 1 }, + { "px": [288,16], "src": [240,8], "f": 0, "t": 62, "d": [132], "a": 1 }, + { "px": [296,16], "src": [240,8], "f": 0, "t": 62, "d": [133], "a": 1 }, + { "px": [304,16], "src": [240,8], "f": 0, "t": 62, "d": [134], "a": 1 }, + { "px": [312,16], "src": [240,8], "f": 0, "t": 62, "d": [135], "a": 1 }, + { "px": [320,16], "src": [240,8], "f": 0, "t": 62, "d": [136], "a": 1 }, + { "px": [328,16], "src": [240,8], "f": 0, "t": 62, "d": [137], "a": 1 }, + { "px": [336,16], "src": [240,8], "f": 0, "t": 62, "d": [138], "a": 1 }, + { "px": [344,16], "src": [240,8], "f": 0, "t": 62, "d": [139], "a": 1 }, + { "px": [352,16], "src": [240,8], "f": 0, "t": 62, "d": [140], "a": 1 }, + { "px": [360,16], "src": [240,8], "f": 0, "t": 62, "d": [141], "a": 1 }, + { "px": [368,16], "src": [240,8], "f": 0, "t": 62, "d": [142], "a": 1 }, + { "px": [376,16], "src": [240,8], "f": 0, "t": 62, "d": [143], "a": 1 }, + { "px": [0,24], "src": [240,8], "f": 0, "t": 62, "d": [144], "a": 1 }, + { "px": [8,24], "src": [240,8], "f": 0, "t": 62, "d": [145], "a": 1 }, + { "px": [16,24], "src": [240,8], "f": 0, "t": 62, "d": [146], "a": 1 }, + { "px": [24,24], "src": [240,8], "f": 0, "t": 62, "d": [147], "a": 1 }, + { "px": [32,24], "src": [240,8], "f": 0, "t": 62, "d": [148], "a": 1 }, + { "px": [40,24], "src": [240,8], "f": 0, "t": 62, "d": [149], "a": 1 }, + { "px": [48,24], "src": [240,8], "f": 0, "t": 62, "d": [150], "a": 1 }, + { "px": [56,24], "src": [240,8], "f": 0, "t": 62, "d": [151], "a": 1 }, + { "px": [64,24], "src": [240,8], "f": 0, "t": 62, "d": [152], "a": 1 }, + { "px": [72,24], "src": [240,8], "f": 0, "t": 62, "d": [153], "a": 1 }, + { "px": [80,24], "src": [240,8], "f": 0, "t": 62, "d": [154], "a": 1 }, + { "px": [88,24], "src": [240,8], "f": 0, "t": 62, "d": [155], "a": 1 }, + { "px": [96,24], "src": [240,8], "f": 0, "t": 62, "d": [156], "a": 1 }, + { "px": [104,24], "src": [240,8], "f": 0, "t": 62, "d": [157], "a": 1 }, + { "px": [112,24], "src": [240,8], "f": 0, "t": 62, "d": [158], "a": 1 }, + { "px": [120,24], "src": [240,8], "f": 0, "t": 62, "d": [159], "a": 1 }, + { "px": [128,24], "src": [240,8], "f": 0, "t": 62, "d": [160], "a": 1 }, + { "px": [136,24], "src": [240,8], "f": 0, "t": 62, "d": [161], "a": 1 }, + { "px": [144,24], "src": [240,8], "f": 0, "t": 62, "d": [162], "a": 1 }, + { "px": [152,24], "src": [240,8], "f": 0, "t": 62, "d": [163], "a": 1 }, + { "px": [160,24], "src": [240,8], "f": 0, "t": 62, "d": [164], "a": 1 }, + { "px": [168,24], "src": [240,8], "f": 0, "t": 62, "d": [165], "a": 1 }, + { "px": [176,24], "src": [240,8], "f": 0, "t": 62, "d": [166], "a": 1 }, + { "px": [184,24], "src": [240,8], "f": 0, "t": 62, "d": [167], "a": 1 }, + { "px": [192,24], "src": [240,8], "f": 0, "t": 62, "d": [168], "a": 1 }, + { "px": [200,24], "src": [240,8], "f": 0, "t": 62, "d": [169], "a": 1 }, + { "px": [208,24], "src": [240,8], "f": 0, "t": 62, "d": [170], "a": 1 }, + { "px": [216,24], "src": [240,8], "f": 0, "t": 62, "d": [171], "a": 1 }, + { "px": [224,24], "src": [240,8], "f": 0, "t": 62, "d": [172], "a": 1 }, + { "px": [232,24], "src": [240,8], "f": 0, "t": 62, "d": [173], "a": 1 }, + { "px": [240,24], "src": [240,8], "f": 0, "t": 62, "d": [174], "a": 1 }, + { "px": [248,24], "src": [240,8], "f": 0, "t": 62, "d": [175], "a": 1 }, + { "px": [256,24], "src": [240,8], "f": 0, "t": 62, "d": [176], "a": 1 }, + { "px": [264,24], "src": [240,8], "f": 0, "t": 62, "d": [177], "a": 1 }, + { "px": [272,24], "src": [240,8], "f": 0, "t": 62, "d": [178], "a": 1 }, + { "px": [280,24], "src": [240,8], "f": 0, "t": 62, "d": [179], "a": 1 }, + { "px": [288,24], "src": [240,8], "f": 0, "t": 62, "d": [180], "a": 1 }, + { "px": [296,24], "src": [240,8], "f": 0, "t": 62, "d": [181], "a": 1 }, + { "px": [304,24], "src": [240,8], "f": 0, "t": 62, "d": [182], "a": 1 }, + { "px": [312,24], "src": [240,8], "f": 0, "t": 62, "d": [183], "a": 1 }, + { "px": [320,24], "src": [240,8], "f": 0, "t": 62, "d": [184], "a": 1 }, + { "px": [328,24], "src": [240,8], "f": 0, "t": 62, "d": [185], "a": 1 }, + { "px": [336,24], "src": [240,8], "f": 0, "t": 62, "d": [186], "a": 1 }, + { "px": [344,24], "src": [240,8], "f": 0, "t": 62, "d": [187], "a": 1 }, + { "px": [352,24], "src": [240,8], "f": 0, "t": 62, "d": [188], "a": 1 }, + { "px": [360,24], "src": [240,8], "f": 0, "t": 62, "d": [189], "a": 1 }, + { "px": [368,24], "src": [240,8], "f": 0, "t": 62, "d": [190], "a": 1 }, + { "px": [376,24], "src": [240,8], "f": 0, "t": 62, "d": [191], "a": 1 }, + { "px": [0,32], "src": [240,8], "f": 0, "t": 62, "d": [192], "a": 1 }, + { "px": [8,32], "src": [240,8], "f": 0, "t": 62, "d": [193], "a": 1 }, + { "px": [16,32], "src": [240,8], "f": 0, "t": 62, "d": [194], "a": 1 }, + { "px": [24,32], "src": [240,8], "f": 0, "t": 62, "d": [195], "a": 1 }, + { "px": [32,32], "src": [240,8], "f": 0, "t": 62, "d": [196], "a": 1 }, + { "px": [40,32], "src": [240,8], "f": 0, "t": 62, "d": [197], "a": 1 }, + { "px": [48,32], "src": [240,8], "f": 0, "t": 62, "d": [198], "a": 1 }, + { "px": [56,32], "src": [240,8], "f": 0, "t": 62, "d": [199], "a": 1 }, + { "px": [64,32], "src": [240,8], "f": 0, "t": 62, "d": [200], "a": 1 }, + { "px": [72,32], "src": [240,8], "f": 0, "t": 62, "d": [201], "a": 1 }, + { "px": [80,32], "src": [240,8], "f": 0, "t": 62, "d": [202], "a": 1 }, + { "px": [88,32], "src": [240,8], "f": 0, "t": 62, "d": [203], "a": 1 }, + { "px": [96,32], "src": [240,8], "f": 0, "t": 62, "d": [204], "a": 1 }, + { "px": [104,32], "src": [240,8], "f": 0, "t": 62, "d": [205], "a": 1 }, + { "px": [112,32], "src": [240,8], "f": 0, "t": 62, "d": [206], "a": 1 }, + { "px": [120,32], "src": [240,8], "f": 0, "t": 62, "d": [207], "a": 1 }, + { "px": [128,32], "src": [240,8], "f": 0, "t": 62, "d": [208], "a": 1 }, + { "px": [136,32], "src": [240,8], "f": 0, "t": 62, "d": [209], "a": 1 }, + { "px": [144,32], "src": [240,8], "f": 0, "t": 62, "d": [210], "a": 1 }, + { "px": [152,32], "src": [240,8], "f": 0, "t": 62, "d": [211], "a": 1 }, + { "px": [160,32], "src": [240,8], "f": 0, "t": 62, "d": [212], "a": 1 }, + { "px": [168,32], "src": [240,8], "f": 0, "t": 62, "d": [213], "a": 1 }, + { "px": [176,32], "src": [240,8], "f": 0, "t": 62, "d": [214], "a": 1 }, + { "px": [184,32], "src": [240,8], "f": 0, "t": 62, "d": [215], "a": 1 }, + { "px": [192,32], "src": [240,8], "f": 0, "t": 62, "d": [216], "a": 1 }, + { "px": [200,32], "src": [240,8], "f": 0, "t": 62, "d": [217], "a": 1 }, + { "px": [208,32], "src": [240,8], "f": 0, "t": 62, "d": [218], "a": 1 }, + { "px": [216,32], "src": [240,8], "f": 0, "t": 62, "d": [219], "a": 1 }, + { "px": [224,32], "src": [240,8], "f": 0, "t": 62, "d": [220], "a": 1 }, + { "px": [232,32], "src": [240,8], "f": 0, "t": 62, "d": [221], "a": 1 }, + { "px": [240,32], "src": [240,8], "f": 0, "t": 62, "d": [222], "a": 1 }, + { "px": [248,32], "src": [240,8], "f": 0, "t": 62, "d": [223], "a": 1 }, + { "px": [256,32], "src": [240,8], "f": 0, "t": 62, "d": [224], "a": 1 }, + { "px": [264,32], "src": [240,8], "f": 0, "t": 62, "d": [225], "a": 1 }, + { "px": [272,32], "src": [240,8], "f": 0, "t": 62, "d": [226], "a": 1 }, + { "px": [280,32], "src": [240,8], "f": 0, "t": 62, "d": [227], "a": 1 }, + { "px": [288,32], "src": [240,8], "f": 0, "t": 62, "d": [228], "a": 1 }, + { "px": [296,32], "src": [240,8], "f": 0, "t": 62, "d": [229], "a": 1 }, + { "px": [304,32], "src": [240,8], "f": 0, "t": 62, "d": [230], "a": 1 }, + { "px": [312,32], "src": [240,8], "f": 0, "t": 62, "d": [231], "a": 1 }, + { "px": [320,32], "src": [240,8], "f": 0, "t": 62, "d": [232], "a": 1 }, + { "px": [328,32], "src": [240,8], "f": 0, "t": 62, "d": [233], "a": 1 }, + { "px": [336,32], "src": [240,8], "f": 0, "t": 62, "d": [234], "a": 1 }, + { "px": [344,32], "src": [240,8], "f": 0, "t": 62, "d": [235], "a": 1 }, + { "px": [352,32], "src": [240,8], "f": 0, "t": 62, "d": [236], "a": 1 }, + { "px": [360,32], "src": [240,8], "f": 0, "t": 62, "d": [237], "a": 1 }, + { "px": [368,32], "src": [240,8], "f": 0, "t": 62, "d": [238], "a": 1 }, + { "px": [376,32], "src": [240,8], "f": 0, "t": 62, "d": [239], "a": 1 }, + { "px": [0,40], "src": [240,8], "f": 0, "t": 62, "d": [240], "a": 1 }, + { "px": [8,40], "src": [240,8], "f": 0, "t": 62, "d": [241], "a": 1 }, + { "px": [16,40], "src": [240,8], "f": 0, "t": 62, "d": [242], "a": 1 }, + { "px": [24,40], "src": [240,8], "f": 0, "t": 62, "d": [243], "a": 1 }, + { "px": [32,40], "src": [240,8], "f": 0, "t": 62, "d": [244], "a": 1 }, + { "px": [40,40], "src": [240,8], "f": 0, "t": 62, "d": [245], "a": 1 }, + { "px": [48,40], "src": [240,8], "f": 0, "t": 62, "d": [246], "a": 1 }, + { "px": [56,40], "src": [240,8], "f": 0, "t": 62, "d": [247], "a": 1 }, + { "px": [64,40], "src": [240,8], "f": 0, "t": 62, "d": [248], "a": 1 }, + { "px": [72,40], "src": [240,8], "f": 0, "t": 62, "d": [249], "a": 1 }, + { "px": [80,40], "src": [240,8], "f": 0, "t": 62, "d": [250], "a": 1 }, + { "px": [88,40], "src": [240,8], "f": 0, "t": 62, "d": [251], "a": 1 }, + { "px": [96,40], "src": [240,8], "f": 0, "t": 62, "d": [252], "a": 1 }, + { "px": [104,40], "src": [240,8], "f": 0, "t": 62, "d": [253], "a": 1 }, + { "px": [112,40], "src": [240,8], "f": 0, "t": 62, "d": [254], "a": 1 }, + { "px": [120,40], "src": [240,8], "f": 0, "t": 62, "d": [255], "a": 1 }, + { "px": [128,40], "src": [240,8], "f": 0, "t": 62, "d": [256], "a": 1 }, + { "px": [136,40], "src": [240,8], "f": 0, "t": 62, "d": [257], "a": 1 }, + { "px": [144,40], "src": [240,8], "f": 0, "t": 62, "d": [258], "a": 1 }, + { "px": [152,40], "src": [240,8], "f": 0, "t": 62, "d": [259], "a": 1 }, + { "px": [160,40], "src": [240,8], "f": 0, "t": 62, "d": [260], "a": 1 }, + { "px": [168,40], "src": [240,8], "f": 0, "t": 62, "d": [261], "a": 1 }, + { "px": [176,40], "src": [240,8], "f": 0, "t": 62, "d": [262], "a": 1 }, + { "px": [184,40], "src": [240,8], "f": 0, "t": 62, "d": [263], "a": 1 }, + { "px": [192,40], "src": [240,8], "f": 0, "t": 62, "d": [264], "a": 1 }, + { "px": [200,40], "src": [240,8], "f": 0, "t": 62, "d": [265], "a": 1 }, + { "px": [208,40], "src": [240,8], "f": 0, "t": 62, "d": [266], "a": 1 }, + { "px": [216,40], "src": [240,8], "f": 0, "t": 62, "d": [267], "a": 1 }, + { "px": [224,40], "src": [240,8], "f": 0, "t": 62, "d": [268], "a": 1 }, + { "px": [232,40], "src": [240,8], "f": 0, "t": 62, "d": [269], "a": 1 }, + { "px": [240,40], "src": [240,8], "f": 0, "t": 62, "d": [270], "a": 1 }, + { "px": [248,40], "src": [240,8], "f": 0, "t": 62, "d": [271], "a": 1 }, + { "px": [256,40], "src": [240,8], "f": 0, "t": 62, "d": [272], "a": 1 }, + { "px": [264,40], "src": [240,8], "f": 0, "t": 62, "d": [273], "a": 1 }, + { "px": [272,40], "src": [240,8], "f": 0, "t": 62, "d": [274], "a": 1 }, + { "px": [280,40], "src": [240,8], "f": 0, "t": 62, "d": [275], "a": 1 }, + { "px": [288,40], "src": [240,8], "f": 0, "t": 62, "d": [276], "a": 1 }, + { "px": [296,40], "src": [240,8], "f": 0, "t": 62, "d": [277], "a": 1 }, + { "px": [304,40], "src": [240,8], "f": 0, "t": 62, "d": [278], "a": 1 }, + { "px": [312,40], "src": [240,8], "f": 0, "t": 62, "d": [279], "a": 1 }, + { "px": [320,40], "src": [240,8], "f": 0, "t": 62, "d": [280], "a": 1 }, + { "px": [328,40], "src": [240,8], "f": 0, "t": 62, "d": [281], "a": 1 }, + { "px": [336,40], "src": [240,8], "f": 0, "t": 62, "d": [282], "a": 1 }, + { "px": [344,40], "src": [240,8], "f": 0, "t": 62, "d": [283], "a": 1 }, + { "px": [352,40], "src": [240,8], "f": 0, "t": 62, "d": [284], "a": 1 }, + { "px": [360,40], "src": [240,8], "f": 0, "t": 62, "d": [285], "a": 1 }, + { "px": [368,40], "src": [240,8], "f": 0, "t": 62, "d": [286], "a": 1 }, + { "px": [376,40], "src": [240,8], "f": 0, "t": 62, "d": [287], "a": 1 }, + { "px": [0,48], "src": [240,8], "f": 0, "t": 62, "d": [288], "a": 1 }, + { "px": [8,48], "src": [240,8], "f": 0, "t": 62, "d": [289], "a": 1 }, + { "px": [16,48], "src": [240,8], "f": 0, "t": 62, "d": [290], "a": 1 }, + { "px": [24,48], "src": [240,8], "f": 0, "t": 62, "d": [291], "a": 1 }, + { "px": [32,48], "src": [240,8], "f": 0, "t": 62, "d": [292], "a": 1 }, + { "px": [40,48], "src": [240,8], "f": 0, "t": 62, "d": [293], "a": 1 }, + { "px": [48,48], "src": [240,8], "f": 0, "t": 62, "d": [294], "a": 1 }, + { "px": [56,48], "src": [240,8], "f": 0, "t": 62, "d": [295], "a": 1 }, + { "px": [64,48], "src": [240,8], "f": 0, "t": 62, "d": [296], "a": 1 }, + { "px": [72,48], "src": [240,8], "f": 0, "t": 62, "d": [297], "a": 1 }, + { "px": [80,48], "src": [240,8], "f": 0, "t": 62, "d": [298], "a": 1 }, + { "px": [88,48], "src": [240,8], "f": 0, "t": 62, "d": [299], "a": 1 }, + { "px": [96,48], "src": [240,8], "f": 0, "t": 62, "d": [300], "a": 1 }, + { "px": [104,48], "src": [240,8], "f": 0, "t": 62, "d": [301], "a": 1 }, + { "px": [112,48], "src": [240,8], "f": 0, "t": 62, "d": [302], "a": 1 }, + { "px": [120,48], "src": [240,8], "f": 0, "t": 62, "d": [303], "a": 1 }, + { "px": [128,48], "src": [240,8], "f": 0, "t": 62, "d": [304], "a": 1 }, + { "px": [136,48], "src": [240,8], "f": 0, "t": 62, "d": [305], "a": 1 }, + { "px": [144,48], "src": [240,8], "f": 0, "t": 62, "d": [306], "a": 1 }, + { "px": [152,48], "src": [240,8], "f": 0, "t": 62, "d": [307], "a": 1 }, + { "px": [160,48], "src": [240,8], "f": 0, "t": 62, "d": [308], "a": 1 }, + { "px": [168,48], "src": [240,8], "f": 0, "t": 62, "d": [309], "a": 1 }, + { "px": [176,48], "src": [240,8], "f": 0, "t": 62, "d": [310], "a": 1 }, + { "px": [184,48], "src": [240,8], "f": 0, "t": 62, "d": [311], "a": 1 }, + { "px": [192,48], "src": [240,8], "f": 0, "t": 62, "d": [312], "a": 1 }, + { "px": [200,48], "src": [240,8], "f": 0, "t": 62, "d": [313], "a": 1 }, + { "px": [208,48], "src": [240,8], "f": 0, "t": 62, "d": [314], "a": 1 }, + { "px": [216,48], "src": [240,8], "f": 0, "t": 62, "d": [315], "a": 1 }, + { "px": [224,48], "src": [240,8], "f": 0, "t": 62, "d": [316], "a": 1 }, + { "px": [232,48], "src": [240,8], "f": 0, "t": 62, "d": [317], "a": 1 }, + { "px": [240,48], "src": [240,8], "f": 0, "t": 62, "d": [318], "a": 1 }, + { "px": [248,48], "src": [240,8], "f": 0, "t": 62, "d": [319], "a": 1 }, + { "px": [256,48], "src": [240,8], "f": 0, "t": 62, "d": [320], "a": 1 }, + { "px": [264,48], "src": [240,8], "f": 0, "t": 62, "d": [321], "a": 1 }, + { "px": [272,48], "src": [240,8], "f": 0, "t": 62, "d": [322], "a": 1 }, + { "px": [280,48], "src": [240,8], "f": 0, "t": 62, "d": [323], "a": 1 }, + { "px": [288,48], "src": [240,8], "f": 0, "t": 62, "d": [324], "a": 1 }, + { "px": [296,48], "src": [240,8], "f": 0, "t": 62, "d": [325], "a": 1 }, + { "px": [304,48], "src": [240,8], "f": 0, "t": 62, "d": [326], "a": 1 }, + { "px": [312,48], "src": [240,8], "f": 0, "t": 62, "d": [327], "a": 1 }, + { "px": [320,48], "src": [240,8], "f": 0, "t": 62, "d": [328], "a": 1 }, + { "px": [328,48], "src": [240,8], "f": 0, "t": 62, "d": [329], "a": 1 }, + { "px": [336,48], "src": [240,8], "f": 0, "t": 62, "d": [330], "a": 1 }, + { "px": [344,48], "src": [240,8], "f": 0, "t": 62, "d": [331], "a": 1 }, + { "px": [352,48], "src": [240,8], "f": 0, "t": 62, "d": [332], "a": 1 }, + { "px": [360,48], "src": [240,8], "f": 0, "t": 62, "d": [333], "a": 1 }, + { "px": [368,48], "src": [240,8], "f": 0, "t": 62, "d": [334], "a": 1 }, + { "px": [376,48], "src": [240,8], "f": 0, "t": 62, "d": [335], "a": 1 }, + { "px": [0,56], "src": [240,8], "f": 0, "t": 62, "d": [336], "a": 1 }, + { "px": [8,56], "src": [240,8], "f": 0, "t": 62, "d": [337], "a": 1 }, + { "px": [16,56], "src": [240,8], "f": 0, "t": 62, "d": [338], "a": 1 }, + { "px": [24,56], "src": [240,8], "f": 0, "t": 62, "d": [339], "a": 1 }, + { "px": [32,56], "src": [240,8], "f": 0, "t": 62, "d": [340], "a": 1 }, + { "px": [40,56], "src": [240,8], "f": 0, "t": 62, "d": [341], "a": 1 }, + { "px": [48,56], "src": [240,8], "f": 0, "t": 62, "d": [342], "a": 1 }, + { "px": [56,56], "src": [240,8], "f": 0, "t": 62, "d": [343], "a": 1 }, + { "px": [64,56], "src": [240,8], "f": 0, "t": 62, "d": [344], "a": 1 }, + { "px": [72,56], "src": [240,8], "f": 0, "t": 62, "d": [345], "a": 1 }, + { "px": [80,56], "src": [240,8], "f": 0, "t": 62, "d": [346], "a": 1 }, + { "px": [88,56], "src": [240,8], "f": 0, "t": 62, "d": [347], "a": 1 }, + { "px": [96,56], "src": [240,8], "f": 0, "t": 62, "d": [348], "a": 1 }, + { "px": [104,56], "src": [240,8], "f": 0, "t": 62, "d": [349], "a": 1 }, + { "px": [112,56], "src": [240,8], "f": 0, "t": 62, "d": [350], "a": 1 }, + { "px": [120,56], "src": [240,8], "f": 0, "t": 62, "d": [351], "a": 1 }, + { "px": [128,56], "src": [240,8], "f": 0, "t": 62, "d": [352], "a": 1 }, + { "px": [136,56], "src": [240,8], "f": 0, "t": 62, "d": [353], "a": 1 }, + { "px": [144,56], "src": [240,8], "f": 0, "t": 62, "d": [354], "a": 1 }, + { "px": [152,56], "src": [240,8], "f": 0, "t": 62, "d": [355], "a": 1 }, + { "px": [160,56], "src": [240,8], "f": 0, "t": 62, "d": [356], "a": 1 }, + { "px": [168,56], "src": [240,8], "f": 0, "t": 62, "d": [357], "a": 1 }, + { "px": [176,56], "src": [240,8], "f": 0, "t": 62, "d": [358], "a": 1 }, + { "px": [184,56], "src": [240,8], "f": 0, "t": 62, "d": [359], "a": 1 }, + { "px": [192,56], "src": [240,8], "f": 0, "t": 62, "d": [360], "a": 1 }, + { "px": [200,56], "src": [240,8], "f": 0, "t": 62, "d": [361], "a": 1 }, + { "px": [208,56], "src": [240,8], "f": 0, "t": 62, "d": [362], "a": 1 }, + { "px": [216,56], "src": [240,8], "f": 0, "t": 62, "d": [363], "a": 1 }, + { "px": [224,56], "src": [240,8], "f": 0, "t": 62, "d": [364], "a": 1 }, + { "px": [232,56], "src": [240,8], "f": 0, "t": 62, "d": [365], "a": 1 }, + { "px": [240,56], "src": [240,8], "f": 0, "t": 62, "d": [366], "a": 1 }, + { "px": [248,56], "src": [240,8], "f": 0, "t": 62, "d": [367], "a": 1 }, + { "px": [256,56], "src": [240,8], "f": 0, "t": 62, "d": [368], "a": 1 }, + { "px": [264,56], "src": [240,8], "f": 0, "t": 62, "d": [369], "a": 1 }, + { "px": [272,56], "src": [240,8], "f": 0, "t": 62, "d": [370], "a": 1 }, + { "px": [280,56], "src": [240,8], "f": 0, "t": 62, "d": [371], "a": 1 }, + { "px": [288,56], "src": [240,8], "f": 0, "t": 62, "d": [372], "a": 1 }, + { "px": [296,56], "src": [240,8], "f": 0, "t": 62, "d": [373], "a": 1 }, + { "px": [304,56], "src": [240,8], "f": 0, "t": 62, "d": [374], "a": 1 }, + { "px": [312,56], "src": [240,8], "f": 0, "t": 62, "d": [375], "a": 1 }, + { "px": [320,56], "src": [240,8], "f": 0, "t": 62, "d": [376], "a": 1 }, + { "px": [328,56], "src": [240,8], "f": 0, "t": 62, "d": [377], "a": 1 }, + { "px": [336,56], "src": [240,8], "f": 0, "t": 62, "d": [378], "a": 1 }, + { "px": [344,56], "src": [240,8], "f": 0, "t": 62, "d": [379], "a": 1 }, + { "px": [352,56], "src": [240,8], "f": 0, "t": 62, "d": [380], "a": 1 }, + { "px": [360,56], "src": [240,8], "f": 0, "t": 62, "d": [381], "a": 1 }, + { "px": [368,56], "src": [240,8], "f": 0, "t": 62, "d": [382], "a": 1 }, + { "px": [376,56], "src": [240,8], "f": 0, "t": 62, "d": [383], "a": 1 }, + { "px": [0,64], "src": [240,8], "f": 0, "t": 62, "d": [384], "a": 1 }, + { "px": [8,64], "src": [240,8], "f": 0, "t": 62, "d": [385], "a": 1 }, + { "px": [16,64], "src": [240,8], "f": 0, "t": 62, "d": [386], "a": 1 }, + { "px": [24,64], "src": [240,8], "f": 0, "t": 62, "d": [387], "a": 1 }, + { "px": [32,64], "src": [240,8], "f": 0, "t": 62, "d": [388], "a": 1 }, + { "px": [40,64], "src": [240,8], "f": 0, "t": 62, "d": [389], "a": 1 }, + { "px": [48,64], "src": [240,8], "f": 0, "t": 62, "d": [390], "a": 1 }, + { "px": [56,64], "src": [240,8], "f": 0, "t": 62, "d": [391], "a": 1 }, + { "px": [64,64], "src": [240,8], "f": 0, "t": 62, "d": [392], "a": 1 }, + { "px": [72,64], "src": [240,8], "f": 0, "t": 62, "d": [393], "a": 1 }, + { "px": [80,64], "src": [240,8], "f": 0, "t": 62, "d": [394], "a": 1 }, + { "px": [88,64], "src": [240,8], "f": 0, "t": 62, "d": [395], "a": 1 }, + { "px": [96,64], "src": [240,8], "f": 0, "t": 62, "d": [396], "a": 1 }, + { "px": [104,64], "src": [240,8], "f": 0, "t": 62, "d": [397], "a": 1 }, + { "px": [112,64], "src": [240,8], "f": 0, "t": 62, "d": [398], "a": 1 }, + { "px": [120,64], "src": [240,8], "f": 0, "t": 62, "d": [399], "a": 1 }, + { "px": [128,64], "src": [240,8], "f": 0, "t": 62, "d": [400], "a": 1 }, + { "px": [136,64], "src": [240,8], "f": 0, "t": 62, "d": [401], "a": 1 }, + { "px": [144,64], "src": [240,8], "f": 0, "t": 62, "d": [402], "a": 1 }, + { "px": [152,64], "src": [240,8], "f": 0, "t": 62, "d": [403], "a": 1 }, + { "px": [160,64], "src": [240,8], "f": 0, "t": 62, "d": [404], "a": 1 }, + { "px": [168,64], "src": [240,8], "f": 0, "t": 62, "d": [405], "a": 1 }, + { "px": [176,64], "src": [240,8], "f": 0, "t": 62, "d": [406], "a": 1 }, + { "px": [184,64], "src": [240,8], "f": 0, "t": 62, "d": [407], "a": 1 }, + { "px": [192,64], "src": [240,8], "f": 0, "t": 62, "d": [408], "a": 1 }, + { "px": [200,64], "src": [240,8], "f": 0, "t": 62, "d": [409], "a": 1 }, + { "px": [208,64], "src": [240,8], "f": 0, "t": 62, "d": [410], "a": 1 }, + { "px": [216,64], "src": [240,8], "f": 0, "t": 62, "d": [411], "a": 1 }, + { "px": [224,64], "src": [240,8], "f": 0, "t": 62, "d": [412], "a": 1 }, + { "px": [232,64], "src": [240,8], "f": 0, "t": 62, "d": [413], "a": 1 }, + { "px": [240,64], "src": [240,8], "f": 0, "t": 62, "d": [414], "a": 1 }, + { "px": [248,64], "src": [240,8], "f": 0, "t": 62, "d": [415], "a": 1 }, + { "px": [256,64], "src": [240,8], "f": 0, "t": 62, "d": [416], "a": 1 }, + { "px": [264,64], "src": [240,8], "f": 0, "t": 62, "d": [417], "a": 1 }, + { "px": [272,64], "src": [240,8], "f": 0, "t": 62, "d": [418], "a": 1 }, + { "px": [280,64], "src": [240,8], "f": 0, "t": 62, "d": [419], "a": 1 }, + { "px": [288,64], "src": [240,8], "f": 0, "t": 62, "d": [420], "a": 1 }, + { "px": [296,64], "src": [240,8], "f": 0, "t": 62, "d": [421], "a": 1 }, + { "px": [304,64], "src": [240,8], "f": 0, "t": 62, "d": [422], "a": 1 }, + { "px": [312,64], "src": [240,8], "f": 0, "t": 62, "d": [423], "a": 1 }, + { "px": [320,64], "src": [240,8], "f": 0, "t": 62, "d": [424], "a": 1 }, + { "px": [328,64], "src": [240,8], "f": 0, "t": 62, "d": [425], "a": 1 }, + { "px": [336,64], "src": [240,8], "f": 0, "t": 62, "d": [426], "a": 1 }, + { "px": [344,64], "src": [240,8], "f": 0, "t": 62, "d": [427], "a": 1 }, + { "px": [352,64], "src": [240,8], "f": 0, "t": 62, "d": [428], "a": 1 }, + { "px": [360,64], "src": [240,8], "f": 0, "t": 62, "d": [429], "a": 1 }, + { "px": [368,64], "src": [240,8], "f": 0, "t": 62, "d": [430], "a": 1 }, + { "px": [376,64], "src": [240,8], "f": 0, "t": 62, "d": [431], "a": 1 }, + { "px": [0,72], "src": [240,8], "f": 0, "t": 62, "d": [432], "a": 1 }, + { "px": [8,72], "src": [240,8], "f": 0, "t": 62, "d": [433], "a": 1 }, + { "px": [16,72], "src": [240,8], "f": 0, "t": 62, "d": [434], "a": 1 }, + { "px": [24,72], "src": [240,8], "f": 0, "t": 62, "d": [435], "a": 1 }, + { "px": [32,72], "src": [240,8], "f": 0, "t": 62, "d": [436], "a": 1 }, + { "px": [40,72], "src": [240,8], "f": 0, "t": 62, "d": [437], "a": 1 }, + { "px": [48,72], "src": [240,8], "f": 0, "t": 62, "d": [438], "a": 1 }, + { "px": [56,72], "src": [240,8], "f": 0, "t": 62, "d": [439], "a": 1 }, + { "px": [64,72], "src": [240,8], "f": 0, "t": 62, "d": [440], "a": 1 }, + { "px": [72,72], "src": [240,8], "f": 0, "t": 62, "d": [441], "a": 1 }, + { "px": [80,72], "src": [240,8], "f": 0, "t": 62, "d": [442], "a": 1 }, + { "px": [88,72], "src": [240,8], "f": 0, "t": 62, "d": [443], "a": 1 }, + { "px": [96,72], "src": [240,8], "f": 0, "t": 62, "d": [444], "a": 1 }, + { "px": [104,72], "src": [240,8], "f": 0, "t": 62, "d": [445], "a": 1 }, + { "px": [112,72], "src": [240,8], "f": 0, "t": 62, "d": [446], "a": 1 }, + { "px": [120,72], "src": [240,8], "f": 0, "t": 62, "d": [447], "a": 1 }, + { "px": [128,72], "src": [240,8], "f": 0, "t": 62, "d": [448], "a": 1 }, + { "px": [136,72], "src": [240,8], "f": 0, "t": 62, "d": [449], "a": 1 }, + { "px": [144,72], "src": [240,8], "f": 0, "t": 62, "d": [450], "a": 1 }, + { "px": [152,72], "src": [240,8], "f": 0, "t": 62, "d": [451], "a": 1 }, + { "px": [160,72], "src": [240,8], "f": 0, "t": 62, "d": [452], "a": 1 }, + { "px": [168,72], "src": [240,8], "f": 0, "t": 62, "d": [453], "a": 1 }, + { "px": [176,72], "src": [240,8], "f": 0, "t": 62, "d": [454], "a": 1 }, + { "px": [184,72], "src": [240,8], "f": 0, "t": 62, "d": [455], "a": 1 }, + { "px": [192,72], "src": [240,8], "f": 0, "t": 62, "d": [456], "a": 1 }, + { "px": [200,72], "src": [240,8], "f": 0, "t": 62, "d": [457], "a": 1 }, + { "px": [208,72], "src": [240,8], "f": 0, "t": 62, "d": [458], "a": 1 }, + { "px": [216,72], "src": [240,8], "f": 0, "t": 62, "d": [459], "a": 1 }, + { "px": [224,72], "src": [240,8], "f": 0, "t": 62, "d": [460], "a": 1 }, + { "px": [232,72], "src": [240,8], "f": 0, "t": 62, "d": [461], "a": 1 }, + { "px": [240,72], "src": [240,8], "f": 0, "t": 62, "d": [462], "a": 1 }, + { "px": [248,72], "src": [240,8], "f": 0, "t": 62, "d": [463], "a": 1 }, + { "px": [256,72], "src": [240,8], "f": 0, "t": 62, "d": [464], "a": 1 }, + { "px": [264,72], "src": [240,8], "f": 0, "t": 62, "d": [465], "a": 1 }, + { "px": [272,72], "src": [240,8], "f": 0, "t": 62, "d": [466], "a": 1 }, + { "px": [280,72], "src": [240,8], "f": 0, "t": 62, "d": [467], "a": 1 }, + { "px": [288,72], "src": [240,8], "f": 0, "t": 62, "d": [468], "a": 1 }, + { "px": [296,72], "src": [240,8], "f": 0, "t": 62, "d": [469], "a": 1 }, + { "px": [304,72], "src": [240,8], "f": 0, "t": 62, "d": [470], "a": 1 }, + { "px": [312,72], "src": [240,8], "f": 0, "t": 62, "d": [471], "a": 1 }, + { "px": [320,72], "src": [240,8], "f": 0, "t": 62, "d": [472], "a": 1 }, + { "px": [328,72], "src": [240,8], "f": 0, "t": 62, "d": [473], "a": 1 }, + { "px": [336,72], "src": [240,8], "f": 0, "t": 62, "d": [474], "a": 1 }, + { "px": [344,72], "src": [240,8], "f": 0, "t": 62, "d": [475], "a": 1 }, + { "px": [352,72], "src": [240,8], "f": 0, "t": 62, "d": [476], "a": 1 }, + { "px": [360,72], "src": [240,8], "f": 0, "t": 62, "d": [477], "a": 1 }, + { "px": [368,72], "src": [240,8], "f": 0, "t": 62, "d": [478], "a": 1 }, + { "px": [376,72], "src": [240,8], "f": 0, "t": 62, "d": [479], "a": 1 }, + { "px": [0,80], "src": [240,8], "f": 0, "t": 62, "d": [480], "a": 1 }, + { "px": [8,80], "src": [240,8], "f": 0, "t": 62, "d": [481], "a": 1 }, + { "px": [16,80], "src": [240,8], "f": 0, "t": 62, "d": [482], "a": 1 }, + { "px": [24,80], "src": [240,8], "f": 0, "t": 62, "d": [483], "a": 1 }, + { "px": [32,80], "src": [240,8], "f": 0, "t": 62, "d": [484], "a": 1 }, + { "px": [40,80], "src": [240,8], "f": 0, "t": 62, "d": [485], "a": 1 }, + { "px": [48,80], "src": [240,8], "f": 0, "t": 62, "d": [486], "a": 1 }, + { "px": [56,80], "src": [240,8], "f": 0, "t": 62, "d": [487], "a": 1 }, + { "px": [64,80], "src": [240,8], "f": 0, "t": 62, "d": [488], "a": 1 }, + { "px": [72,80], "src": [240,8], "f": 0, "t": 62, "d": [489], "a": 1 }, + { "px": [80,80], "src": [240,8], "f": 0, "t": 62, "d": [490], "a": 1 }, + { "px": [88,80], "src": [240,8], "f": 0, "t": 62, "d": [491], "a": 1 }, + { "px": [96,80], "src": [240,8], "f": 0, "t": 62, "d": [492], "a": 1 }, + { "px": [104,80], "src": [240,8], "f": 0, "t": 62, "d": [493], "a": 1 }, + { "px": [112,80], "src": [240,8], "f": 0, "t": 62, "d": [494], "a": 1 }, + { "px": [120,80], "src": [240,8], "f": 0, "t": 62, "d": [495], "a": 1 }, + { "px": [128,80], "src": [240,8], "f": 0, "t": 62, "d": [496], "a": 1 }, + { "px": [136,80], "src": [240,8], "f": 0, "t": 62, "d": [497], "a": 1 }, + { "px": [144,80], "src": [240,8], "f": 0, "t": 62, "d": [498], "a": 1 }, + { "px": [152,80], "src": [240,8], "f": 0, "t": 62, "d": [499], "a": 1 }, + { "px": [160,80], "src": [240,8], "f": 0, "t": 62, "d": [500], "a": 1 }, + { "px": [168,80], "src": [240,8], "f": 0, "t": 62, "d": [501], "a": 1 }, + { "px": [176,80], "src": [240,8], "f": 0, "t": 62, "d": [502], "a": 1 }, + { "px": [184,80], "src": [240,8], "f": 0, "t": 62, "d": [503], "a": 1 }, + { "px": [192,80], "src": [240,8], "f": 0, "t": 62, "d": [504], "a": 1 }, + { "px": [200,80], "src": [240,8], "f": 0, "t": 62, "d": [505], "a": 1 }, + { "px": [208,80], "src": [240,8], "f": 0, "t": 62, "d": [506], "a": 1 }, + { "px": [216,80], "src": [240,8], "f": 0, "t": 62, "d": [507], "a": 1 }, + { "px": [224,80], "src": [240,8], "f": 0, "t": 62, "d": [508], "a": 1 }, + { "px": [232,80], "src": [240,8], "f": 0, "t": 62, "d": [509], "a": 1 }, + { "px": [240,80], "src": [240,8], "f": 0, "t": 62, "d": [510], "a": 1 }, + { "px": [248,80], "src": [240,8], "f": 0, "t": 62, "d": [511], "a": 1 }, + { "px": [256,80], "src": [240,8], "f": 0, "t": 62, "d": [512], "a": 1 }, + { "px": [264,80], "src": [240,8], "f": 0, "t": 62, "d": [513], "a": 1 }, + { "px": [272,80], "src": [240,8], "f": 0, "t": 62, "d": [514], "a": 1 }, + { "px": [280,80], "src": [240,8], "f": 0, "t": 62, "d": [515], "a": 1 }, + { "px": [288,80], "src": [240,8], "f": 0, "t": 62, "d": [516], "a": 1 }, + { "px": [296,80], "src": [240,8], "f": 0, "t": 62, "d": [517], "a": 1 }, + { "px": [304,80], "src": [240,8], "f": 0, "t": 62, "d": [518], "a": 1 }, + { "px": [312,80], "src": [240,8], "f": 0, "t": 62, "d": [519], "a": 1 }, + { "px": [320,80], "src": [240,8], "f": 0, "t": 62, "d": [520], "a": 1 }, + { "px": [328,80], "src": [240,8], "f": 0, "t": 62, "d": [521], "a": 1 }, + { "px": [336,80], "src": [240,8], "f": 0, "t": 62, "d": [522], "a": 1 }, + { "px": [344,80], "src": [240,8], "f": 0, "t": 62, "d": [523], "a": 1 }, + { "px": [352,80], "src": [240,8], "f": 0, "t": 62, "d": [524], "a": 1 }, + { "px": [360,80], "src": [240,8], "f": 0, "t": 62, "d": [525], "a": 1 }, + { "px": [368,80], "src": [240,8], "f": 0, "t": 62, "d": [526], "a": 1 }, + { "px": [376,80], "src": [240,8], "f": 0, "t": 62, "d": [527], "a": 1 }, + { "px": [0,88], "src": [240,8], "f": 0, "t": 62, "d": [528], "a": 1 }, + { "px": [8,88], "src": [240,8], "f": 0, "t": 62, "d": [529], "a": 1 }, + { "px": [16,88], "src": [240,8], "f": 0, "t": 62, "d": [530], "a": 1 }, + { "px": [24,88], "src": [240,8], "f": 0, "t": 62, "d": [531], "a": 1 }, + { "px": [32,88], "src": [240,8], "f": 0, "t": 62, "d": [532], "a": 1 }, + { "px": [40,88], "src": [240,8], "f": 0, "t": 62, "d": [533], "a": 1 }, + { "px": [48,88], "src": [240,8], "f": 0, "t": 62, "d": [534], "a": 1 }, + { "px": [56,88], "src": [240,8], "f": 0, "t": 62, "d": [535], "a": 1 }, + { "px": [64,88], "src": [240,8], "f": 0, "t": 62, "d": [536], "a": 1 }, + { "px": [72,88], "src": [240,8], "f": 0, "t": 62, "d": [537], "a": 1 }, + { "px": [80,88], "src": [240,8], "f": 0, "t": 62, "d": [538], "a": 1 }, + { "px": [88,88], "src": [240,8], "f": 0, "t": 62, "d": [539], "a": 1 }, + { "px": [96,88], "src": [240,8], "f": 0, "t": 62, "d": [540], "a": 1 }, + { "px": [104,88], "src": [240,8], "f": 0, "t": 62, "d": [541], "a": 1 }, + { "px": [112,88], "src": [240,8], "f": 0, "t": 62, "d": [542], "a": 1 }, + { "px": [120,88], "src": [240,8], "f": 0, "t": 62, "d": [543], "a": 1 }, + { "px": [128,88], "src": [240,8], "f": 0, "t": 62, "d": [544], "a": 1 }, + { "px": [136,88], "src": [240,8], "f": 0, "t": 62, "d": [545], "a": 1 }, + { "px": [144,88], "src": [240,8], "f": 0, "t": 62, "d": [546], "a": 1 }, + { "px": [152,88], "src": [240,8], "f": 0, "t": 62, "d": [547], "a": 1 }, + { "px": [160,88], "src": [240,8], "f": 0, "t": 62, "d": [548], "a": 1 }, + { "px": [168,88], "src": [240,8], "f": 0, "t": 62, "d": [549], "a": 1 }, + { "px": [176,88], "src": [240,8], "f": 0, "t": 62, "d": [550], "a": 1 }, + { "px": [184,88], "src": [240,8], "f": 0, "t": 62, "d": [551], "a": 1 }, + { "px": [192,88], "src": [240,8], "f": 0, "t": 62, "d": [552], "a": 1 }, + { "px": [200,88], "src": [240,8], "f": 0, "t": 62, "d": [553], "a": 1 }, + { "px": [208,88], "src": [240,8], "f": 0, "t": 62, "d": [554], "a": 1 }, + { "px": [216,88], "src": [240,8], "f": 0, "t": 62, "d": [555], "a": 1 }, + { "px": [224,88], "src": [240,8], "f": 0, "t": 62, "d": [556], "a": 1 }, + { "px": [232,88], "src": [240,8], "f": 0, "t": 62, "d": [557], "a": 1 }, + { "px": [240,88], "src": [240,8], "f": 0, "t": 62, "d": [558], "a": 1 }, + { "px": [248,88], "src": [240,8], "f": 0, "t": 62, "d": [559], "a": 1 }, + { "px": [256,88], "src": [240,8], "f": 0, "t": 62, "d": [560], "a": 1 }, + { "px": [264,88], "src": [240,8], "f": 0, "t": 62, "d": [561], "a": 1 }, + { "px": [272,88], "src": [240,8], "f": 0, "t": 62, "d": [562], "a": 1 }, + { "px": [280,88], "src": [240,8], "f": 0, "t": 62, "d": [563], "a": 1 }, + { "px": [288,88], "src": [240,8], "f": 0, "t": 62, "d": [564], "a": 1 }, + { "px": [296,88], "src": [240,8], "f": 0, "t": 62, "d": [565], "a": 1 }, + { "px": [304,88], "src": [240,8], "f": 0, "t": 62, "d": [566], "a": 1 }, + { "px": [312,88], "src": [240,8], "f": 0, "t": 62, "d": [567], "a": 1 }, + { "px": [320,88], "src": [240,8], "f": 0, "t": 62, "d": [568], "a": 1 }, + { "px": [328,88], "src": [240,8], "f": 0, "t": 62, "d": [569], "a": 1 }, + { "px": [336,88], "src": [240,8], "f": 0, "t": 62, "d": [570], "a": 1 }, + { "px": [344,88], "src": [240,8], "f": 0, "t": 62, "d": [571], "a": 1 }, + { "px": [352,88], "src": [240,8], "f": 0, "t": 62, "d": [572], "a": 1 }, + { "px": [360,88], "src": [240,8], "f": 0, "t": 62, "d": [573], "a": 1 }, + { "px": [368,88], "src": [240,8], "f": 0, "t": 62, "d": [574], "a": 1 }, + { "px": [376,88], "src": [240,8], "f": 0, "t": 62, "d": [575], "a": 1 }, + { "px": [0,96], "src": [240,8], "f": 0, "t": 62, "d": [576], "a": 1 }, + { "px": [8,96], "src": [240,8], "f": 0, "t": 62, "d": [577], "a": 1 }, + { "px": [16,96], "src": [240,8], "f": 0, "t": 62, "d": [578], "a": 1 }, + { "px": [24,96], "src": [240,8], "f": 0, "t": 62, "d": [579], "a": 1 }, + { "px": [32,96], "src": [240,8], "f": 0, "t": 62, "d": [580], "a": 1 }, + { "px": [40,96], "src": [240,8], "f": 0, "t": 62, "d": [581], "a": 1 }, + { "px": [48,96], "src": [240,8], "f": 0, "t": 62, "d": [582], "a": 1 }, + { "px": [56,96], "src": [240,8], "f": 0, "t": 62, "d": [583], "a": 1 }, + { "px": [64,96], "src": [240,8], "f": 0, "t": 62, "d": [584], "a": 1 }, + { "px": [72,96], "src": [240,8], "f": 0, "t": 62, "d": [585], "a": 1 }, + { "px": [80,96], "src": [240,8], "f": 0, "t": 62, "d": [586], "a": 1 }, + { "px": [88,96], "src": [240,8], "f": 0, "t": 62, "d": [587], "a": 1 }, + { "px": [96,96], "src": [240,8], "f": 0, "t": 62, "d": [588], "a": 1 }, + { "px": [104,96], "src": [240,8], "f": 0, "t": 62, "d": [589], "a": 1 }, + { "px": [112,96], "src": [240,8], "f": 0, "t": 62, "d": [590], "a": 1 }, + { "px": [120,96], "src": [240,8], "f": 0, "t": 62, "d": [591], "a": 1 }, + { "px": [128,96], "src": [240,8], "f": 0, "t": 62, "d": [592], "a": 1 }, + { "px": [136,96], "src": [240,8], "f": 0, "t": 62, "d": [593], "a": 1 }, + { "px": [144,96], "src": [240,8], "f": 0, "t": 62, "d": [594], "a": 1 }, + { "px": [152,96], "src": [240,8], "f": 0, "t": 62, "d": [595], "a": 1 }, + { "px": [160,96], "src": [240,8], "f": 0, "t": 62, "d": [596], "a": 1 }, + { "px": [168,96], "src": [240,8], "f": 0, "t": 62, "d": [597], "a": 1 }, + { "px": [176,96], "src": [240,8], "f": 0, "t": 62, "d": [598], "a": 1 }, + { "px": [184,96], "src": [240,8], "f": 0, "t": 62, "d": [599], "a": 1 }, + { "px": [192,96], "src": [240,8], "f": 0, "t": 62, "d": [600], "a": 1 }, + { "px": [200,96], "src": [240,8], "f": 0, "t": 62, "d": [601], "a": 1 }, + { "px": [208,96], "src": [240,8], "f": 0, "t": 62, "d": [602], "a": 1 }, + { "px": [216,96], "src": [240,8], "f": 0, "t": 62, "d": [603], "a": 1 }, + { "px": [224,96], "src": [240,8], "f": 0, "t": 62, "d": [604], "a": 1 }, + { "px": [232,96], "src": [240,8], "f": 0, "t": 62, "d": [605], "a": 1 }, + { "px": [240,96], "src": [240,8], "f": 0, "t": 62, "d": [606], "a": 1 }, + { "px": [248,96], "src": [240,8], "f": 0, "t": 62, "d": [607], "a": 1 }, + { "px": [256,96], "src": [240,8], "f": 0, "t": 62, "d": [608], "a": 1 }, + { "px": [264,96], "src": [240,8], "f": 0, "t": 62, "d": [609], "a": 1 }, + { "px": [272,96], "src": [240,8], "f": 0, "t": 62, "d": [610], "a": 1 }, + { "px": [280,96], "src": [240,8], "f": 0, "t": 62, "d": [611], "a": 1 }, + { "px": [288,96], "src": [240,8], "f": 0, "t": 62, "d": [612], "a": 1 }, + { "px": [296,96], "src": [240,8], "f": 0, "t": 62, "d": [613], "a": 1 }, + { "px": [304,96], "src": [240,8], "f": 0, "t": 62, "d": [614], "a": 1 }, + { "px": [312,96], "src": [240,8], "f": 0, "t": 62, "d": [615], "a": 1 }, + { "px": [320,96], "src": [240,8], "f": 0, "t": 62, "d": [616], "a": 1 }, + { "px": [328,96], "src": [240,8], "f": 0, "t": 62, "d": [617], "a": 1 }, + { "px": [336,96], "src": [240,8], "f": 0, "t": 62, "d": [618], "a": 1 }, + { "px": [344,96], "src": [240,8], "f": 0, "t": 62, "d": [619], "a": 1 }, + { "px": [352,96], "src": [240,8], "f": 0, "t": 62, "d": [620], "a": 1 }, + { "px": [360,96], "src": [240,8], "f": 0, "t": 62, "d": [621], "a": 1 }, + { "px": [368,96], "src": [240,8], "f": 0, "t": 62, "d": [622], "a": 1 }, + { "px": [376,96], "src": [240,8], "f": 0, "t": 62, "d": [623], "a": 1 }, + { "px": [0,104], "src": [240,8], "f": 0, "t": 62, "d": [624], "a": 1 }, + { "px": [8,104], "src": [240,8], "f": 0, "t": 62, "d": [625], "a": 1 }, + { "px": [16,104], "src": [240,8], "f": 0, "t": 62, "d": [626], "a": 1 }, + { "px": [24,104], "src": [240,8], "f": 0, "t": 62, "d": [627], "a": 1 }, + { "px": [32,104], "src": [240,8], "f": 0, "t": 62, "d": [628], "a": 1 }, + { "px": [40,104], "src": [240,8], "f": 0, "t": 62, "d": [629], "a": 1 }, + { "px": [48,104], "src": [240,8], "f": 0, "t": 62, "d": [630], "a": 1 }, + { "px": [56,104], "src": [240,8], "f": 0, "t": 62, "d": [631], "a": 1 }, + { "px": [64,104], "src": [240,8], "f": 0, "t": 62, "d": [632], "a": 1 }, + { "px": [72,104], "src": [240,8], "f": 0, "t": 62, "d": [633], "a": 1 }, + { "px": [80,104], "src": [240,8], "f": 0, "t": 62, "d": [634], "a": 1 }, + { "px": [88,104], "src": [240,8], "f": 0, "t": 62, "d": [635], "a": 1 }, + { "px": [96,104], "src": [240,8], "f": 0, "t": 62, "d": [636], "a": 1 }, + { "px": [104,104], "src": [240,8], "f": 0, "t": 62, "d": [637], "a": 1 }, + { "px": [112,104], "src": [240,8], "f": 0, "t": 62, "d": [638], "a": 1 }, + { "px": [120,104], "src": [240,8], "f": 0, "t": 62, "d": [639], "a": 1 }, + { "px": [128,104], "src": [240,8], "f": 0, "t": 62, "d": [640], "a": 1 }, + { "px": [136,104], "src": [240,8], "f": 0, "t": 62, "d": [641], "a": 1 }, + { "px": [144,104], "src": [240,8], "f": 0, "t": 62, "d": [642], "a": 1 }, + { "px": [152,104], "src": [240,8], "f": 0, "t": 62, "d": [643], "a": 1 }, + { "px": [160,104], "src": [240,8], "f": 0, "t": 62, "d": [644], "a": 1 }, + { "px": [168,104], "src": [240,8], "f": 0, "t": 62, "d": [645], "a": 1 }, + { "px": [176,104], "src": [240,8], "f": 0, "t": 62, "d": [646], "a": 1 }, + { "px": [184,104], "src": [240,8], "f": 0, "t": 62, "d": [647], "a": 1 }, + { "px": [192,104], "src": [240,8], "f": 0, "t": 62, "d": [648], "a": 1 }, + { "px": [200,104], "src": [240,8], "f": 0, "t": 62, "d": [649], "a": 1 }, + { "px": [208,104], "src": [240,8], "f": 0, "t": 62, "d": [650], "a": 1 }, + { "px": [216,104], "src": [240,8], "f": 0, "t": 62, "d": [651], "a": 1 }, + { "px": [224,104], "src": [240,8], "f": 0, "t": 62, "d": [652], "a": 1 }, + { "px": [232,104], "src": [240,8], "f": 0, "t": 62, "d": [653], "a": 1 }, + { "px": [240,104], "src": [240,8], "f": 0, "t": 62, "d": [654], "a": 1 }, + { "px": [248,104], "src": [240,8], "f": 0, "t": 62, "d": [655], "a": 1 }, + { "px": [256,104], "src": [240,8], "f": 0, "t": 62, "d": [656], "a": 1 }, + { "px": [264,104], "src": [240,8], "f": 0, "t": 62, "d": [657], "a": 1 }, + { "px": [272,104], "src": [240,8], "f": 0, "t": 62, "d": [658], "a": 1 }, + { "px": [280,104], "src": [240,8], "f": 0, "t": 62, "d": [659], "a": 1 }, + { "px": [288,104], "src": [240,8], "f": 0, "t": 62, "d": [660], "a": 1 }, + { "px": [296,104], "src": [240,8], "f": 0, "t": 62, "d": [661], "a": 1 }, + { "px": [304,104], "src": [240,8], "f": 0, "t": 62, "d": [662], "a": 1 }, + { "px": [312,104], "src": [240,8], "f": 0, "t": 62, "d": [663], "a": 1 }, + { "px": [320,104], "src": [240,8], "f": 0, "t": 62, "d": [664], "a": 1 }, + { "px": [328,104], "src": [240,8], "f": 0, "t": 62, "d": [665], "a": 1 }, + { "px": [336,104], "src": [240,8], "f": 0, "t": 62, "d": [666], "a": 1 }, + { "px": [344,104], "src": [240,8], "f": 0, "t": 62, "d": [667], "a": 1 }, + { "px": [352,104], "src": [240,8], "f": 0, "t": 62, "d": [668], "a": 1 }, + { "px": [360,104], "src": [240,8], "f": 0, "t": 62, "d": [669], "a": 1 }, + { "px": [368,104], "src": [240,8], "f": 0, "t": 62, "d": [670], "a": 1 }, + { "px": [376,104], "src": [240,8], "f": 0, "t": 62, "d": [671], "a": 1 }, + { "px": [0,112], "src": [240,8], "f": 0, "t": 62, "d": [672], "a": 1 }, + { "px": [8,112], "src": [240,8], "f": 0, "t": 62, "d": [673], "a": 1 }, + { "px": [16,112], "src": [240,8], "f": 0, "t": 62, "d": [674], "a": 1 }, + { "px": [24,112], "src": [240,8], "f": 0, "t": 62, "d": [675], "a": 1 }, + { "px": [32,112], "src": [240,8], "f": 0, "t": 62, "d": [676], "a": 1 }, + { "px": [40,112], "src": [240,8], "f": 0, "t": 62, "d": [677], "a": 1 }, + { "px": [48,112], "src": [240,8], "f": 0, "t": 62, "d": [678], "a": 1 }, + { "px": [56,112], "src": [240,8], "f": 0, "t": 62, "d": [679], "a": 1 }, + { "px": [64,112], "src": [240,8], "f": 0, "t": 62, "d": [680], "a": 1 }, + { "px": [72,112], "src": [240,8], "f": 0, "t": 62, "d": [681], "a": 1 }, + { "px": [80,112], "src": [240,8], "f": 0, "t": 62, "d": [682], "a": 1 }, + { "px": [88,112], "src": [240,8], "f": 0, "t": 62, "d": [683], "a": 1 }, + { "px": [96,112], "src": [240,8], "f": 0, "t": 62, "d": [684], "a": 1 }, + { "px": [104,112], "src": [240,8], "f": 0, "t": 62, "d": [685], "a": 1 }, + { "px": [112,112], "src": [240,8], "f": 0, "t": 62, "d": [686], "a": 1 }, + { "px": [120,112], "src": [240,8], "f": 0, "t": 62, "d": [687], "a": 1 }, + { "px": [128,112], "src": [240,8], "f": 0, "t": 62, "d": [688], "a": 1 }, + { "px": [136,112], "src": [240,8], "f": 0, "t": 62, "d": [689], "a": 1 }, + { "px": [144,112], "src": [240,8], "f": 0, "t": 62, "d": [690], "a": 1 }, + { "px": [152,112], "src": [240,8], "f": 0, "t": 62, "d": [691], "a": 1 }, + { "px": [160,112], "src": [240,8], "f": 0, "t": 62, "d": [692], "a": 1 }, + { "px": [168,112], "src": [240,8], "f": 0, "t": 62, "d": [693], "a": 1 }, + { "px": [176,112], "src": [240,8], "f": 0, "t": 62, "d": [694], "a": 1 }, + { "px": [184,112], "src": [240,8], "f": 0, "t": 62, "d": [695], "a": 1 }, + { "px": [192,112], "src": [240,8], "f": 0, "t": 62, "d": [696], "a": 1 }, + { "px": [200,112], "src": [240,8], "f": 0, "t": 62, "d": [697], "a": 1 }, + { "px": [208,112], "src": [240,8], "f": 0, "t": 62, "d": [698], "a": 1 }, + { "px": [216,112], "src": [240,8], "f": 0, "t": 62, "d": [699], "a": 1 }, + { "px": [224,112], "src": [240,8], "f": 0, "t": 62, "d": [700], "a": 1 }, + { "px": [232,112], "src": [240,8], "f": 0, "t": 62, "d": [701], "a": 1 }, + { "px": [240,112], "src": [240,8], "f": 0, "t": 62, "d": [702], "a": 1 }, + { "px": [248,112], "src": [240,8], "f": 0, "t": 62, "d": [703], "a": 1 }, + { "px": [256,112], "src": [240,8], "f": 0, "t": 62, "d": [704], "a": 1 }, + { "px": [264,112], "src": [240,8], "f": 0, "t": 62, "d": [705], "a": 1 }, + { "px": [272,112], "src": [240,8], "f": 0, "t": 62, "d": [706], "a": 1 }, + { "px": [280,112], "src": [240,8], "f": 0, "t": 62, "d": [707], "a": 1 }, + { "px": [288,112], "src": [240,8], "f": 0, "t": 62, "d": [708], "a": 1 }, + { "px": [296,112], "src": [240,8], "f": 0, "t": 62, "d": [709], "a": 1 }, + { "px": [304,112], "src": [240,8], "f": 0, "t": 62, "d": [710], "a": 1 }, + { "px": [312,112], "src": [240,8], "f": 0, "t": 62, "d": [711], "a": 1 }, + { "px": [320,112], "src": [240,8], "f": 0, "t": 62, "d": [712], "a": 1 }, + { "px": [328,112], "src": [240,8], "f": 0, "t": 62, "d": [713], "a": 1 }, + { "px": [336,112], "src": [240,8], "f": 0, "t": 62, "d": [714], "a": 1 }, + { "px": [344,112], "src": [240,8], "f": 0, "t": 62, "d": [715], "a": 1 }, + { "px": [352,112], "src": [240,8], "f": 0, "t": 62, "d": [716], "a": 1 }, + { "px": [360,112], "src": [240,8], "f": 0, "t": 62, "d": [717], "a": 1 }, + { "px": [368,112], "src": [240,8], "f": 0, "t": 62, "d": [718], "a": 1 }, + { "px": [376,112], "src": [240,8], "f": 0, "t": 62, "d": [719], "a": 1 }, + { "px": [0,120], "src": [240,8], "f": 0, "t": 62, "d": [720], "a": 1 }, + { "px": [8,120], "src": [240,8], "f": 0, "t": 62, "d": [721], "a": 1 }, + { "px": [16,120], "src": [240,8], "f": 0, "t": 62, "d": [722], "a": 1 }, + { "px": [24,120], "src": [240,8], "f": 0, "t": 62, "d": [723], "a": 1 }, + { "px": [32,120], "src": [240,8], "f": 0, "t": 62, "d": [724], "a": 1 }, + { "px": [40,120], "src": [240,8], "f": 0, "t": 62, "d": [725], "a": 1 }, + { "px": [48,120], "src": [240,8], "f": 0, "t": 62, "d": [726], "a": 1 }, + { "px": [56,120], "src": [240,8], "f": 0, "t": 62, "d": [727], "a": 1 }, + { "px": [64,120], "src": [240,8], "f": 0, "t": 62, "d": [728], "a": 1 }, + { "px": [72,120], "src": [240,8], "f": 0, "t": 62, "d": [729], "a": 1 }, + { "px": [80,120], "src": [240,8], "f": 0, "t": 62, "d": [730], "a": 1 }, + { "px": [88,120], "src": [240,8], "f": 0, "t": 62, "d": [731], "a": 1 }, + { "px": [96,120], "src": [240,8], "f": 0, "t": 62, "d": [732], "a": 1 }, + { "px": [104,120], "src": [240,8], "f": 0, "t": 62, "d": [733], "a": 1 }, + { "px": [112,120], "src": [240,8], "f": 0, "t": 62, "d": [734], "a": 1 }, + { "px": [120,120], "src": [240,8], "f": 0, "t": 62, "d": [735], "a": 1 }, + { "px": [128,120], "src": [240,8], "f": 0, "t": 62, "d": [736], "a": 1 }, + { "px": [136,120], "src": [240,8], "f": 0, "t": 62, "d": [737], "a": 1 }, + { "px": [144,120], "src": [240,8], "f": 0, "t": 62, "d": [738], "a": 1 }, + { "px": [152,120], "src": [240,8], "f": 0, "t": 62, "d": [739], "a": 1 }, + { "px": [160,120], "src": [240,8], "f": 0, "t": 62, "d": [740], "a": 1 }, + { "px": [168,120], "src": [240,8], "f": 0, "t": 62, "d": [741], "a": 1 }, + { "px": [176,120], "src": [240,8], "f": 0, "t": 62, "d": [742], "a": 1 }, + { "px": [184,120], "src": [240,8], "f": 0, "t": 62, "d": [743], "a": 1 }, + { "px": [192,120], "src": [240,8], "f": 0, "t": 62, "d": [744], "a": 1 }, + { "px": [200,120], "src": [240,8], "f": 0, "t": 62, "d": [745], "a": 1 }, + { "px": [208,120], "src": [240,8], "f": 0, "t": 62, "d": [746], "a": 1 }, + { "px": [216,120], "src": [240,8], "f": 0, "t": 62, "d": [747], "a": 1 }, + { "px": [224,120], "src": [240,8], "f": 0, "t": 62, "d": [748], "a": 1 }, + { "px": [232,120], "src": [240,8], "f": 0, "t": 62, "d": [749], "a": 1 }, + { "px": [240,120], "src": [240,8], "f": 0, "t": 62, "d": [750], "a": 1 }, + { "px": [248,120], "src": [240,8], "f": 0, "t": 62, "d": [751], "a": 1 }, + { "px": [256,120], "src": [240,8], "f": 0, "t": 62, "d": [752], "a": 1 }, + { "px": [264,120], "src": [240,8], "f": 0, "t": 62, "d": [753], "a": 1 }, + { "px": [272,120], "src": [240,8], "f": 0, "t": 62, "d": [754], "a": 1 }, + { "px": [280,120], "src": [240,8], "f": 0, "t": 62, "d": [755], "a": 1 }, + { "px": [288,120], "src": [240,8], "f": 0, "t": 62, "d": [756], "a": 1 }, + { "px": [296,120], "src": [240,8], "f": 0, "t": 62, "d": [757], "a": 1 }, + { "px": [304,120], "src": [240,8], "f": 0, "t": 62, "d": [758], "a": 1 }, + { "px": [312,120], "src": [240,8], "f": 0, "t": 62, "d": [759], "a": 1 }, + { "px": [320,120], "src": [240,8], "f": 0, "t": 62, "d": [760], "a": 1 }, + { "px": [328,120], "src": [240,8], "f": 0, "t": 62, "d": [761], "a": 1 }, + { "px": [336,120], "src": [240,8], "f": 0, "t": 62, "d": [762], "a": 1 }, + { "px": [344,120], "src": [240,8], "f": 0, "t": 62, "d": [763], "a": 1 }, + { "px": [352,120], "src": [240,8], "f": 0, "t": 62, "d": [764], "a": 1 }, + { "px": [360,120], "src": [240,8], "f": 0, "t": 62, "d": [765], "a": 1 }, + { "px": [368,120], "src": [240,8], "f": 0, "t": 62, "d": [766], "a": 1 }, + { "px": [376,120], "src": [240,8], "f": 0, "t": 62, "d": [767], "a": 1 }, + { "px": [0,128], "src": [240,8], "f": 0, "t": 62, "d": [768], "a": 1 }, + { "px": [8,128], "src": [240,8], "f": 0, "t": 62, "d": [769], "a": 1 }, + { "px": [16,128], "src": [240,8], "f": 0, "t": 62, "d": [770], "a": 1 }, + { "px": [24,128], "src": [240,8], "f": 0, "t": 62, "d": [771], "a": 1 }, + { "px": [32,128], "src": [240,8], "f": 0, "t": 62, "d": [772], "a": 1 }, + { "px": [40,128], "src": [240,8], "f": 0, "t": 62, "d": [773], "a": 1 }, + { "px": [48,128], "src": [240,8], "f": 0, "t": 62, "d": [774], "a": 1 }, + { "px": [56,128], "src": [240,8], "f": 0, "t": 62, "d": [775], "a": 1 }, + { "px": [64,128], "src": [240,8], "f": 0, "t": 62, "d": [776], "a": 1 }, + { "px": [72,128], "src": [240,8], "f": 0, "t": 62, "d": [777], "a": 1 }, + { "px": [80,128], "src": [240,8], "f": 0, "t": 62, "d": [778], "a": 1 }, + { "px": [88,128], "src": [240,8], "f": 0, "t": 62, "d": [779], "a": 1 }, + { "px": [96,128], "src": [240,8], "f": 0, "t": 62, "d": [780], "a": 1 }, + { "px": [104,128], "src": [240,8], "f": 0, "t": 62, "d": [781], "a": 1 }, + { "px": [112,128], "src": [240,8], "f": 0, "t": 62, "d": [782], "a": 1 }, + { "px": [120,128], "src": [240,8], "f": 0, "t": 62, "d": [783], "a": 1 }, + { "px": [128,128], "src": [240,8], "f": 0, "t": 62, "d": [784], "a": 1 }, + { "px": [136,128], "src": [240,8], "f": 0, "t": 62, "d": [785], "a": 1 }, + { "px": [144,128], "src": [240,8], "f": 0, "t": 62, "d": [786], "a": 1 }, + { "px": [152,128], "src": [240,8], "f": 0, "t": 62, "d": [787], "a": 1 }, + { "px": [160,128], "src": [240,8], "f": 0, "t": 62, "d": [788], "a": 1 }, + { "px": [168,128], "src": [240,8], "f": 0, "t": 62, "d": [789], "a": 1 }, + { "px": [176,128], "src": [240,8], "f": 0, "t": 62, "d": [790], "a": 1 }, + { "px": [184,128], "src": [240,8], "f": 0, "t": 62, "d": [791], "a": 1 }, + { "px": [192,128], "src": [240,8], "f": 0, "t": 62, "d": [792], "a": 1 }, + { "px": [200,128], "src": [240,8], "f": 0, "t": 62, "d": [793], "a": 1 }, + { "px": [208,128], "src": [240,8], "f": 0, "t": 62, "d": [794], "a": 1 }, + { "px": [216,128], "src": [240,8], "f": 0, "t": 62, "d": [795], "a": 1 }, + { "px": [224,128], "src": [240,8], "f": 0, "t": 62, "d": [796], "a": 1 }, + { "px": [232,128], "src": [240,8], "f": 0, "t": 62, "d": [797], "a": 1 }, + { "px": [240,128], "src": [240,8], "f": 0, "t": 62, "d": [798], "a": 1 }, + { "px": [248,128], "src": [240,8], "f": 0, "t": 62, "d": [799], "a": 1 }, + { "px": [256,128], "src": [240,8], "f": 0, "t": 62, "d": [800], "a": 1 }, + { "px": [264,128], "src": [240,8], "f": 0, "t": 62, "d": [801], "a": 1 }, + { "px": [272,128], "src": [240,8], "f": 0, "t": 62, "d": [802], "a": 1 }, + { "px": [280,128], "src": [240,8], "f": 0, "t": 62, "d": [803], "a": 1 }, + { "px": [288,128], "src": [240,8], "f": 0, "t": 62, "d": [804], "a": 1 }, + { "px": [296,128], "src": [240,8], "f": 0, "t": 62, "d": [805], "a": 1 }, + { "px": [304,128], "src": [240,8], "f": 0, "t": 62, "d": [806], "a": 1 }, + { "px": [312,128], "src": [240,8], "f": 0, "t": 62, "d": [807], "a": 1 }, + { "px": [320,128], "src": [240,8], "f": 0, "t": 62, "d": [808], "a": 1 }, + { "px": [328,128], "src": [240,8], "f": 0, "t": 62, "d": [809], "a": 1 }, + { "px": [336,128], "src": [240,8], "f": 0, "t": 62, "d": [810], "a": 1 }, + { "px": [344,128], "src": [240,8], "f": 0, "t": 62, "d": [811], "a": 1 }, + { "px": [352,128], "src": [240,8], "f": 0, "t": 62, "d": [812], "a": 1 }, + { "px": [360,128], "src": [240,8], "f": 0, "t": 62, "d": [813], "a": 1 }, + { "px": [368,128], "src": [240,8], "f": 0, "t": 62, "d": [814], "a": 1 }, + { "px": [376,128], "src": [240,8], "f": 0, "t": 62, "d": [815], "a": 1 }, + { "px": [0,136], "src": [240,8], "f": 0, "t": 62, "d": [816], "a": 1 }, + { "px": [8,136], "src": [240,8], "f": 0, "t": 62, "d": [817], "a": 1 }, + { "px": [16,136], "src": [240,8], "f": 0, "t": 62, "d": [818], "a": 1 }, + { "px": [24,136], "src": [240,8], "f": 0, "t": 62, "d": [819], "a": 1 }, + { "px": [32,136], "src": [240,8], "f": 0, "t": 62, "d": [820], "a": 1 }, + { "px": [40,136], "src": [240,8], "f": 0, "t": 62, "d": [821], "a": 1 }, + { "px": [48,136], "src": [240,8], "f": 0, "t": 62, "d": [822], "a": 1 }, + { "px": [56,136], "src": [240,8], "f": 0, "t": 62, "d": [823], "a": 1 }, + { "px": [64,136], "src": [240,8], "f": 0, "t": 62, "d": [824], "a": 1 }, + { "px": [72,136], "src": [240,8], "f": 0, "t": 62, "d": [825], "a": 1 }, + { "px": [80,136], "src": [240,8], "f": 0, "t": 62, "d": [826], "a": 1 }, + { "px": [88,136], "src": [240,8], "f": 0, "t": 62, "d": [827], "a": 1 }, + { "px": [96,136], "src": [240,8], "f": 0, "t": 62, "d": [828], "a": 1 }, + { "px": [104,136], "src": [240,8], "f": 0, "t": 62, "d": [829], "a": 1 }, + { "px": [112,136], "src": [240,8], "f": 0, "t": 62, "d": [830], "a": 1 }, + { "px": [120,136], "src": [240,8], "f": 0, "t": 62, "d": [831], "a": 1 }, + { "px": [128,136], "src": [240,8], "f": 0, "t": 62, "d": [832], "a": 1 }, + { "px": [136,136], "src": [240,8], "f": 0, "t": 62, "d": [833], "a": 1 }, + { "px": [144,136], "src": [240,8], "f": 0, "t": 62, "d": [834], "a": 1 }, + { "px": [152,136], "src": [240,8], "f": 0, "t": 62, "d": [835], "a": 1 }, + { "px": [160,136], "src": [240,8], "f": 0, "t": 62, "d": [836], "a": 1 }, + { "px": [168,136], "src": [240,8], "f": 0, "t": 62, "d": [837], "a": 1 }, + { "px": [176,136], "src": [240,8], "f": 0, "t": 62, "d": [838], "a": 1 }, + { "px": [184,136], "src": [240,8], "f": 0, "t": 62, "d": [839], "a": 1 }, + { "px": [192,136], "src": [240,8], "f": 0, "t": 62, "d": [840], "a": 1 }, + { "px": [200,136], "src": [240,8], "f": 0, "t": 62, "d": [841], "a": 1 }, + { "px": [208,136], "src": [240,8], "f": 0, "t": 62, "d": [842], "a": 1 }, + { "px": [216,136], "src": [240,8], "f": 0, "t": 62, "d": [843], "a": 1 }, + { "px": [224,136], "src": [240,8], "f": 0, "t": 62, "d": [844], "a": 1 }, + { "px": [232,136], "src": [240,8], "f": 0, "t": 62, "d": [845], "a": 1 }, + { "px": [240,136], "src": [240,8], "f": 0, "t": 62, "d": [846], "a": 1 }, + { "px": [248,136], "src": [240,8], "f": 0, "t": 62, "d": [847], "a": 1 }, + { "px": [256,136], "src": [240,8], "f": 0, "t": 62, "d": [848], "a": 1 }, + { "px": [264,136], "src": [240,8], "f": 0, "t": 62, "d": [849], "a": 1 }, + { "px": [272,136], "src": [240,8], "f": 0, "t": 62, "d": [850], "a": 1 }, + { "px": [280,136], "src": [240,8], "f": 0, "t": 62, "d": [851], "a": 1 }, + { "px": [288,136], "src": [240,8], "f": 0, "t": 62, "d": [852], "a": 1 }, + { "px": [296,136], "src": [240,8], "f": 0, "t": 62, "d": [853], "a": 1 }, + { "px": [304,136], "src": [240,8], "f": 0, "t": 62, "d": [854], "a": 1 }, + { "px": [312,136], "src": [240,8], "f": 0, "t": 62, "d": [855], "a": 1 }, + { "px": [320,136], "src": [240,8], "f": 0, "t": 62, "d": [856], "a": 1 }, + { "px": [328,136], "src": [240,8], "f": 0, "t": 62, "d": [857], "a": 1 }, + { "px": [336,136], "src": [240,8], "f": 0, "t": 62, "d": [858], "a": 1 }, + { "px": [344,136], "src": [240,8], "f": 0, "t": 62, "d": [859], "a": 1 }, + { "px": [352,136], "src": [240,8], "f": 0, "t": 62, "d": [860], "a": 1 }, + { "px": [360,136], "src": [240,8], "f": 0, "t": 62, "d": [861], "a": 1 }, + { "px": [368,136], "src": [240,8], "f": 0, "t": 62, "d": [862], "a": 1 }, + { "px": [376,136], "src": [240,8], "f": 0, "t": 62, "d": [863], "a": 1 }, + { "px": [0,144], "src": [240,8], "f": 0, "t": 62, "d": [864], "a": 1 }, + { "px": [8,144], "src": [240,8], "f": 0, "t": 62, "d": [865], "a": 1 }, + { "px": [16,144], "src": [240,8], "f": 0, "t": 62, "d": [866], "a": 1 }, + { "px": [24,144], "src": [240,8], "f": 0, "t": 62, "d": [867], "a": 1 }, + { "px": [32,144], "src": [240,8], "f": 0, "t": 62, "d": [868], "a": 1 }, + { "px": [40,144], "src": [240,8], "f": 0, "t": 62, "d": [869], "a": 1 }, + { "px": [48,144], "src": [240,8], "f": 0, "t": 62, "d": [870], "a": 1 }, + { "px": [56,144], "src": [240,8], "f": 0, "t": 62, "d": [871], "a": 1 }, + { "px": [64,144], "src": [240,8], "f": 0, "t": 62, "d": [872], "a": 1 }, + { "px": [72,144], "src": [240,8], "f": 0, "t": 62, "d": [873], "a": 1 }, + { "px": [80,144], "src": [240,8], "f": 0, "t": 62, "d": [874], "a": 1 }, + { "px": [88,144], "src": [240,8], "f": 0, "t": 62, "d": [875], "a": 1 }, + { "px": [96,144], "src": [240,8], "f": 0, "t": 62, "d": [876], "a": 1 }, + { "px": [104,144], "src": [240,8], "f": 0, "t": 62, "d": [877], "a": 1 }, + { "px": [112,144], "src": [240,8], "f": 0, "t": 62, "d": [878], "a": 1 }, + { "px": [120,144], "src": [240,8], "f": 0, "t": 62, "d": [879], "a": 1 }, + { "px": [128,144], "src": [240,8], "f": 0, "t": 62, "d": [880], "a": 1 }, + { "px": [136,144], "src": [240,8], "f": 0, "t": 62, "d": [881], "a": 1 }, + { "px": [144,144], "src": [240,8], "f": 0, "t": 62, "d": [882], "a": 1 }, + { "px": [152,144], "src": [240,8], "f": 0, "t": 62, "d": [883], "a": 1 }, + { "px": [160,144], "src": [240,8], "f": 0, "t": 62, "d": [884], "a": 1 }, + { "px": [168,144], "src": [240,8], "f": 0, "t": 62, "d": [885], "a": 1 }, + { "px": [176,144], "src": [240,8], "f": 0, "t": 62, "d": [886], "a": 1 }, + { "px": [184,144], "src": [240,8], "f": 0, "t": 62, "d": [887], "a": 1 }, + { "px": [192,144], "src": [240,8], "f": 0, "t": 62, "d": [888], "a": 1 }, + { "px": [200,144], "src": [240,8], "f": 0, "t": 62, "d": [889], "a": 1 }, + { "px": [208,144], "src": [240,8], "f": 0, "t": 62, "d": [890], "a": 1 }, + { "px": [216,144], "src": [240,8], "f": 0, "t": 62, "d": [891], "a": 1 }, + { "px": [224,144], "src": [240,8], "f": 0, "t": 62, "d": [892], "a": 1 }, + { "px": [232,144], "src": [240,8], "f": 0, "t": 62, "d": [893], "a": 1 }, + { "px": [240,144], "src": [240,8], "f": 0, "t": 62, "d": [894], "a": 1 }, + { "px": [248,144], "src": [240,8], "f": 0, "t": 62, "d": [895], "a": 1 }, + { "px": [256,144], "src": [240,8], "f": 0, "t": 62, "d": [896], "a": 1 }, + { "px": [264,144], "src": [240,8], "f": 0, "t": 62, "d": [897], "a": 1 }, + { "px": [272,144], "src": [240,8], "f": 0, "t": 62, "d": [898], "a": 1 }, + { "px": [280,144], "src": [240,8], "f": 0, "t": 62, "d": [899], "a": 1 }, + { "px": [288,144], "src": [240,8], "f": 0, "t": 62, "d": [900], "a": 1 }, + { "px": [296,144], "src": [240,8], "f": 0, "t": 62, "d": [901], "a": 1 }, + { "px": [304,144], "src": [240,8], "f": 0, "t": 62, "d": [902], "a": 1 }, + { "px": [312,144], "src": [240,8], "f": 0, "t": 62, "d": [903], "a": 1 }, + { "px": [320,144], "src": [240,8], "f": 0, "t": 62, "d": [904], "a": 1 }, + { "px": [328,144], "src": [240,8], "f": 0, "t": 62, "d": [905], "a": 1 }, + { "px": [336,144], "src": [240,8], "f": 0, "t": 62, "d": [906], "a": 1 }, + { "px": [344,144], "src": [240,8], "f": 0, "t": 62, "d": [907], "a": 1 }, + { "px": [352,144], "src": [240,8], "f": 0, "t": 62, "d": [908], "a": 1 }, + { "px": [360,144], "src": [240,8], "f": 0, "t": 62, "d": [909], "a": 1 }, + { "px": [368,144], "src": [240,8], "f": 0, "t": 62, "d": [910], "a": 1 }, + { "px": [376,144], "src": [240,8], "f": 0, "t": 62, "d": [911], "a": 1 }, + { "px": [0,152], "src": [240,8], "f": 0, "t": 62, "d": [912], "a": 1 }, + { "px": [8,152], "src": [240,8], "f": 0, "t": 62, "d": [913], "a": 1 }, + { "px": [16,152], "src": [240,8], "f": 0, "t": 62, "d": [914], "a": 1 }, + { "px": [24,152], "src": [240,8], "f": 0, "t": 62, "d": [915], "a": 1 }, + { "px": [32,152], "src": [240,8], "f": 0, "t": 62, "d": [916], "a": 1 }, + { "px": [40,152], "src": [240,8], "f": 0, "t": 62, "d": [917], "a": 1 }, + { "px": [48,152], "src": [240,8], "f": 0, "t": 62, "d": [918], "a": 1 }, + { "px": [56,152], "src": [240,8], "f": 0, "t": 62, "d": [919], "a": 1 }, + { "px": [64,152], "src": [240,8], "f": 0, "t": 62, "d": [920], "a": 1 }, + { "px": [72,152], "src": [240,8], "f": 0, "t": 62, "d": [921], "a": 1 }, + { "px": [80,152], "src": [240,8], "f": 0, "t": 62, "d": [922], "a": 1 }, + { "px": [88,152], "src": [240,8], "f": 0, "t": 62, "d": [923], "a": 1 }, + { "px": [96,152], "src": [240,8], "f": 0, "t": 62, "d": [924], "a": 1 }, + { "px": [104,152], "src": [240,8], "f": 0, "t": 62, "d": [925], "a": 1 }, + { "px": [112,152], "src": [240,8], "f": 0, "t": 62, "d": [926], "a": 1 }, + { "px": [120,152], "src": [240,8], "f": 0, "t": 62, "d": [927], "a": 1 }, + { "px": [128,152], "src": [240,8], "f": 0, "t": 62, "d": [928], "a": 1 }, + { "px": [136,152], "src": [240,8], "f": 0, "t": 62, "d": [929], "a": 1 }, + { "px": [144,152], "src": [240,8], "f": 0, "t": 62, "d": [930], "a": 1 }, + { "px": [152,152], "src": [240,8], "f": 0, "t": 62, "d": [931], "a": 1 }, + { "px": [160,152], "src": [240,8], "f": 0, "t": 62, "d": [932], "a": 1 }, + { "px": [168,152], "src": [240,8], "f": 0, "t": 62, "d": [933], "a": 1 }, + { "px": [176,152], "src": [240,8], "f": 0, "t": 62, "d": [934], "a": 1 }, + { "px": [184,152], "src": [240,8], "f": 0, "t": 62, "d": [935], "a": 1 }, + { "px": [192,152], "src": [240,8], "f": 0, "t": 62, "d": [936], "a": 1 }, + { "px": [200,152], "src": [240,8], "f": 0, "t": 62, "d": [937], "a": 1 }, + { "px": [208,152], "src": [240,8], "f": 0, "t": 62, "d": [938], "a": 1 }, + { "px": [216,152], "src": [240,8], "f": 0, "t": 62, "d": [939], "a": 1 }, + { "px": [224,152], "src": [240,8], "f": 0, "t": 62, "d": [940], "a": 1 }, + { "px": [232,152], "src": [240,8], "f": 0, "t": 62, "d": [941], "a": 1 }, + { "px": [240,152], "src": [240,8], "f": 0, "t": 62, "d": [942], "a": 1 }, + { "px": [248,152], "src": [240,8], "f": 0, "t": 62, "d": [943], "a": 1 }, + { "px": [256,152], "src": [240,8], "f": 0, "t": 62, "d": [944], "a": 1 }, + { "px": [264,152], "src": [240,8], "f": 0, "t": 62, "d": [945], "a": 1 }, + { "px": [272,152], "src": [240,8], "f": 0, "t": 62, "d": [946], "a": 1 }, + { "px": [280,152], "src": [240,8], "f": 0, "t": 62, "d": [947], "a": 1 }, + { "px": [288,152], "src": [240,8], "f": 0, "t": 62, "d": [948], "a": 1 }, + { "px": [296,152], "src": [240,8], "f": 0, "t": 62, "d": [949], "a": 1 }, + { "px": [304,152], "src": [240,8], "f": 0, "t": 62, "d": [950], "a": 1 }, + { "px": [312,152], "src": [240,8], "f": 0, "t": 62, "d": [951], "a": 1 }, + { "px": [320,152], "src": [240,8], "f": 0, "t": 62, "d": [952], "a": 1 }, + { "px": [328,152], "src": [240,8], "f": 0, "t": 62, "d": [953], "a": 1 }, + { "px": [336,152], "src": [240,8], "f": 0, "t": 62, "d": [954], "a": 1 }, + { "px": [344,152], "src": [240,8], "f": 0, "t": 62, "d": [955], "a": 1 }, + { "px": [352,152], "src": [240,8], "f": 0, "t": 62, "d": [956], "a": 1 }, + { "px": [360,152], "src": [240,8], "f": 0, "t": 62, "d": [957], "a": 1 }, + { "px": [368,152], "src": [240,8], "f": 0, "t": 62, "d": [958], "a": 1 }, + { "px": [376,152], "src": [240,8], "f": 0, "t": 62, "d": [959], "a": 1 }, + { "px": [0,160], "src": [240,8], "f": 0, "t": 62, "d": [960], "a": 1 }, + { "px": [8,160], "src": [240,8], "f": 0, "t": 62, "d": [961], "a": 1 }, + { "px": [16,160], "src": [240,8], "f": 0, "t": 62, "d": [962], "a": 1 }, + { "px": [24,160], "src": [240,8], "f": 0, "t": 62, "d": [963], "a": 1 }, + { "px": [32,160], "src": [240,8], "f": 0, "t": 62, "d": [964], "a": 1 }, + { "px": [40,160], "src": [240,8], "f": 0, "t": 62, "d": [965], "a": 1 }, + { "px": [48,160], "src": [240,8], "f": 0, "t": 62, "d": [966], "a": 1 }, + { "px": [56,160], "src": [240,8], "f": 0, "t": 62, "d": [967], "a": 1 }, + { "px": [64,160], "src": [240,8], "f": 0, "t": 62, "d": [968], "a": 1 }, + { "px": [72,160], "src": [240,8], "f": 0, "t": 62, "d": [969], "a": 1 }, + { "px": [80,160], "src": [240,8], "f": 0, "t": 62, "d": [970], "a": 1 }, + { "px": [88,160], "src": [240,8], "f": 0, "t": 62, "d": [971], "a": 1 }, + { "px": [96,160], "src": [32,104], "f": 0, "t": 420, "d": [972], "a": 1 }, + { "px": [104,160], "src": [240,8], "f": 0, "t": 62, "d": [973], "a": 1 }, + { "px": [112,160], "src": [240,8], "f": 0, "t": 62, "d": [974], "a": 1 }, + { "px": [120,160], "src": [240,8], "f": 0, "t": 62, "d": [975], "a": 1 }, + { "px": [128,160], "src": [240,8], "f": 0, "t": 62, "d": [976], "a": 1 }, + { "px": [136,160], "src": [240,8], "f": 0, "t": 62, "d": [977], "a": 1 }, + { "px": [144,160], "src": [240,8], "f": 0, "t": 62, "d": [978], "a": 1 }, + { "px": [152,160], "src": [240,8], "f": 0, "t": 62, "d": [979], "a": 1 }, + { "px": [160,160], "src": [240,8], "f": 0, "t": 62, "d": [980], "a": 1 }, + { "px": [168,160], "src": [240,8], "f": 0, "t": 62, "d": [981], "a": 1 }, + { "px": [176,160], "src": [240,8], "f": 0, "t": 62, "d": [982], "a": 1 }, + { "px": [184,160], "src": [240,8], "f": 0, "t": 62, "d": [983], "a": 1 }, + { "px": [192,160], "src": [240,8], "f": 0, "t": 62, "d": [984], "a": 1 }, + { "px": [200,160], "src": [240,8], "f": 0, "t": 62, "d": [985], "a": 1 }, + { "px": [208,160], "src": [240,8], "f": 0, "t": 62, "d": [986], "a": 1 }, + { "px": [216,160], "src": [240,8], "f": 0, "t": 62, "d": [987], "a": 1 }, + { "px": [224,160], "src": [240,8], "f": 0, "t": 62, "d": [988], "a": 1 }, + { "px": [232,160], "src": [240,8], "f": 0, "t": 62, "d": [989], "a": 1 }, + { "px": [240,160], "src": [240,8], "f": 0, "t": 62, "d": [990], "a": 1 }, + { "px": [248,160], "src": [240,8], "f": 0, "t": 62, "d": [991], "a": 1 }, + { "px": [256,160], "src": [240,8], "f": 0, "t": 62, "d": [992], "a": 1 }, + { "px": [264,160], "src": [240,8], "f": 0, "t": 62, "d": [993], "a": 1 }, + { "px": [272,160], "src": [240,8], "f": 0, "t": 62, "d": [994], "a": 1 }, + { "px": [280,160], "src": [240,8], "f": 0, "t": 62, "d": [995], "a": 1 }, + { "px": [288,160], "src": [240,8], "f": 0, "t": 62, "d": [996], "a": 1 }, + { "px": [296,160], "src": [240,8], "f": 0, "t": 62, "d": [997], "a": 1 }, + { "px": [304,160], "src": [240,8], "f": 0, "t": 62, "d": [998], "a": 1 }, + { "px": [312,160], "src": [240,8], "f": 0, "t": 62, "d": [999], "a": 1 }, + { "px": [320,160], "src": [240,8], "f": 0, "t": 62, "d": [1000], "a": 1 }, + { "px": [328,160], "src": [240,8], "f": 0, "t": 62, "d": [1001], "a": 1 }, + { "px": [336,160], "src": [240,8], "f": 0, "t": 62, "d": [1002], "a": 1 }, + { "px": [344,160], "src": [240,8], "f": 0, "t": 62, "d": [1003], "a": 1 }, + { "px": [352,160], "src": [240,8], "f": 0, "t": 62, "d": [1004], "a": 1 }, + { "px": [360,160], "src": [240,8], "f": 0, "t": 62, "d": [1005], "a": 1 }, + { "px": [368,160], "src": [240,8], "f": 0, "t": 62, "d": [1006], "a": 1 }, + { "px": [376,160], "src": [240,8], "f": 0, "t": 62, "d": [1007], "a": 1 }, + { "px": [0,168], "src": [240,8], "f": 0, "t": 62, "d": [1008], "a": 1 }, + { "px": [8,168], "src": [240,8], "f": 0, "t": 62, "d": [1009], "a": 1 }, + { "px": [16,168], "src": [240,8], "f": 0, "t": 62, "d": [1010], "a": 1 }, + { "px": [24,168], "src": [240,8], "f": 0, "t": 62, "d": [1011], "a": 1 }, + { "px": [32,168], "src": [240,8], "f": 0, "t": 62, "d": [1012], "a": 1 }, + { "px": [40,168], "src": [240,8], "f": 0, "t": 62, "d": [1013], "a": 1 }, + { "px": [48,168], "src": [240,8], "f": 0, "t": 62, "d": [1014], "a": 1 }, + { "px": [56,168], "src": [240,8], "f": 0, "t": 62, "d": [1015], "a": 1 }, + { "px": [64,168], "src": [240,8], "f": 0, "t": 62, "d": [1016], "a": 1 }, + { "px": [72,168], "src": [240,8], "f": 0, "t": 62, "d": [1017], "a": 1 }, + { "px": [80,168], "src": [240,8], "f": 0, "t": 62, "d": [1018], "a": 1 }, + { "px": [88,168], "src": [240,8], "f": 0, "t": 62, "d": [1019], "a": 1 }, + { "px": [96,168], "src": [32,112], "f": 0, "t": 452, "d": [1020], "a": 1 }, + { "px": [104,168], "src": [240,8], "f": 0, "t": 62, "d": [1021], "a": 1 }, + { "px": [112,168], "src": [240,8], "f": 0, "t": 62, "d": [1022], "a": 1 }, + { "px": [120,168], "src": [240,8], "f": 0, "t": 62, "d": [1023], "a": 1 }, + { "px": [128,168], "src": [240,8], "f": 0, "t": 62, "d": [1024], "a": 1 }, + { "px": [136,168], "src": [240,8], "f": 0, "t": 62, "d": [1025], "a": 1 }, + { "px": [144,168], "src": [240,8], "f": 0, "t": 62, "d": [1026], "a": 1 }, + { "px": [152,168], "src": [240,8], "f": 0, "t": 62, "d": [1027], "a": 1 }, + { "px": [160,168], "src": [240,8], "f": 0, "t": 62, "d": [1028], "a": 1 }, + { "px": [168,168], "src": [240,8], "f": 0, "t": 62, "d": [1029], "a": 1 }, + { "px": [176,168], "src": [240,8], "f": 0, "t": 62, "d": [1030], "a": 1 }, + { "px": [184,168], "src": [240,8], "f": 0, "t": 62, "d": [1031], "a": 1 }, + { "px": [192,168], "src": [240,8], "f": 0, "t": 62, "d": [1032], "a": 1 }, + { "px": [200,168], "src": [240,8], "f": 0, "t": 62, "d": [1033], "a": 1 }, + { "px": [208,168], "src": [240,8], "f": 0, "t": 62, "d": [1034], "a": 1 }, + { "px": [216,168], "src": [240,8], "f": 0, "t": 62, "d": [1035], "a": 1 }, + { "px": [224,168], "src": [240,8], "f": 0, "t": 62, "d": [1036], "a": 1 }, + { "px": [232,168], "src": [240,8], "f": 0, "t": 62, "d": [1037], "a": 1 }, + { "px": [240,168], "src": [240,8], "f": 0, "t": 62, "d": [1038], "a": 1 }, + { "px": [248,168], "src": [240,8], "f": 0, "t": 62, "d": [1039], "a": 1 }, + { "px": [256,168], "src": [240,8], "f": 0, "t": 62, "d": [1040], "a": 1 }, + { "px": [264,168], "src": [240,8], "f": 0, "t": 62, "d": [1041], "a": 1 }, + { "px": [272,168], "src": [240,8], "f": 0, "t": 62, "d": [1042], "a": 1 }, + { "px": [280,168], "src": [240,8], "f": 0, "t": 62, "d": [1043], "a": 1 }, + { "px": [288,168], "src": [240,8], "f": 0, "t": 62, "d": [1044], "a": 1 }, + { "px": [296,168], "src": [240,8], "f": 0, "t": 62, "d": [1045], "a": 1 }, + { "px": [304,168], "src": [240,8], "f": 0, "t": 62, "d": [1046], "a": 1 }, + { "px": [312,168], "src": [240,8], "f": 0, "t": 62, "d": [1047], "a": 1 }, + { "px": [320,168], "src": [240,8], "f": 0, "t": 62, "d": [1048], "a": 1 }, + { "px": [328,168], "src": [240,8], "f": 0, "t": 62, "d": [1049], "a": 1 }, + { "px": [336,168], "src": [240,8], "f": 0, "t": 62, "d": [1050], "a": 1 }, + { "px": [344,168], "src": [240,8], "f": 0, "t": 62, "d": [1051], "a": 1 }, + { "px": [352,168], "src": [240,8], "f": 0, "t": 62, "d": [1052], "a": 1 }, + { "px": [360,168], "src": [240,8], "f": 0, "t": 62, "d": [1053], "a": 1 }, + { "px": [368,168], "src": [240,8], "f": 0, "t": 62, "d": [1054], "a": 1 }, + { "px": [376,168], "src": [240,8], "f": 0, "t": 62, "d": [1055], "a": 1 }, + { "px": [0,176], "src": [240,8], "f": 0, "t": 62, "d": [1056], "a": 1 }, + { "px": [8,176], "src": [240,8], "f": 0, "t": 62, "d": [1057], "a": 1 }, + { "px": [16,176], "src": [240,8], "f": 0, "t": 62, "d": [1058], "a": 1 }, + { "px": [24,176], "src": [240,8], "f": 0, "t": 62, "d": [1059], "a": 1 }, + { "px": [32,176], "src": [240,8], "f": 0, "t": 62, "d": [1060], "a": 1 }, + { "px": [40,176], "src": [240,8], "f": 0, "t": 62, "d": [1061], "a": 1 }, + { "px": [48,176], "src": [240,8], "f": 0, "t": 62, "d": [1062], "a": 1 }, + { "px": [56,176], "src": [240,8], "f": 0, "t": 62, "d": [1063], "a": 1 }, + { "px": [64,176], "src": [240,8], "f": 0, "t": 62, "d": [1064], "a": 1 }, + { "px": [72,176], "src": [240,8], "f": 0, "t": 62, "d": [1065], "a": 1 }, + { "px": [80,176], "src": [240,8], "f": 0, "t": 62, "d": [1066], "a": 1 }, + { "px": [88,176], "src": [240,8], "f": 0, "t": 62, "d": [1067], "a": 1 }, + { "px": [96,176], "src": [32,120], "f": 0, "t": 484, "d": [1068], "a": 1 }, + { "px": [104,176], "src": [240,8], "f": 0, "t": 62, "d": [1069], "a": 1 }, + { "px": [112,176], "src": [240,8], "f": 0, "t": 62, "d": [1070], "a": 1 }, + { "px": [120,176], "src": [240,8], "f": 0, "t": 62, "d": [1071], "a": 1 }, + { "px": [128,176], "src": [240,8], "f": 0, "t": 62, "d": [1072], "a": 1 }, + { "px": [136,176], "src": [240,8], "f": 0, "t": 62, "d": [1073], "a": 1 }, + { "px": [144,176], "src": [240,8], "f": 0, "t": 62, "d": [1074], "a": 1 }, + { "px": [152,176], "src": [240,8], "f": 0, "t": 62, "d": [1075], "a": 1 }, + { "px": [160,176], "src": [240,8], "f": 0, "t": 62, "d": [1076], "a": 1 }, + { "px": [168,176], "src": [240,8], "f": 0, "t": 62, "d": [1077], "a": 1 }, + { "px": [176,176], "src": [240,8], "f": 0, "t": 62, "d": [1078], "a": 1 }, + { "px": [184,176], "src": [240,8], "f": 0, "t": 62, "d": [1079], "a": 1 }, + { "px": [192,176], "src": [240,8], "f": 0, "t": 62, "d": [1080], "a": 1 }, + { "px": [200,176], "src": [240,8], "f": 0, "t": 62, "d": [1081], "a": 1 }, + { "px": [208,176], "src": [240,8], "f": 0, "t": 62, "d": [1082], "a": 1 }, + { "px": [216,176], "src": [240,8], "f": 0, "t": 62, "d": [1083], "a": 1 }, + { "px": [224,176], "src": [240,8], "f": 0, "t": 62, "d": [1084], "a": 1 }, + { "px": [232,176], "src": [240,8], "f": 0, "t": 62, "d": [1085], "a": 1 }, + { "px": [240,176], "src": [240,8], "f": 0, "t": 62, "d": [1086], "a": 1 }, + { "px": [248,176], "src": [240,8], "f": 0, "t": 62, "d": [1087], "a": 1 }, + { "px": [256,176], "src": [240,8], "f": 0, "t": 62, "d": [1088], "a": 1 }, + { "px": [264,176], "src": [240,8], "f": 0, "t": 62, "d": [1089], "a": 1 }, + { "px": [272,176], "src": [240,8], "f": 0, "t": 62, "d": [1090], "a": 1 }, + { "px": [280,176], "src": [240,8], "f": 0, "t": 62, "d": [1091], "a": 1 }, + { "px": [288,176], "src": [240,8], "f": 0, "t": 62, "d": [1092], "a": 1 }, + { "px": [296,176], "src": [240,8], "f": 0, "t": 62, "d": [1093], "a": 1 }, + { "px": [304,176], "src": [240,8], "f": 0, "t": 62, "d": [1094], "a": 1 }, + { "px": [312,176], "src": [240,8], "f": 0, "t": 62, "d": [1095], "a": 1 }, + { "px": [320,176], "src": [240,8], "f": 0, "t": 62, "d": [1096], "a": 1 }, + { "px": [328,176], "src": [240,8], "f": 0, "t": 62, "d": [1097], "a": 1 }, + { "px": [336,176], "src": [240,8], "f": 0, "t": 62, "d": [1098], "a": 1 }, + { "px": [344,176], "src": [240,8], "f": 0, "t": 62, "d": [1099], "a": 1 }, + { "px": [352,176], "src": [240,8], "f": 0, "t": 62, "d": [1100], "a": 1 }, + { "px": [360,176], "src": [240,8], "f": 0, "t": 62, "d": [1101], "a": 1 }, + { "px": [368,176], "src": [240,8], "f": 0, "t": 62, "d": [1102], "a": 1 }, + { "px": [376,176], "src": [240,8], "f": 0, "t": 62, "d": [1103], "a": 1 }, + { "px": [0,184], "src": [240,8], "f": 0, "t": 62, "d": [1104], "a": 1 }, + { "px": [8,184], "src": [240,8], "f": 0, "t": 62, "d": [1105], "a": 1 }, + { "px": [16,184], "src": [240,8], "f": 0, "t": 62, "d": [1106], "a": 1 }, + { "px": [24,184], "src": [240,8], "f": 0, "t": 62, "d": [1107], "a": 1 }, + { "px": [32,184], "src": [240,8], "f": 0, "t": 62, "d": [1108], "a": 1 }, + { "px": [40,184], "src": [240,8], "f": 0, "t": 62, "d": [1109], "a": 1 }, + { "px": [48,184], "src": [240,8], "f": 0, "t": 62, "d": [1110], "a": 1 }, + { "px": [56,184], "src": [240,8], "f": 0, "t": 62, "d": [1111], "a": 1 }, + { "px": [64,184], "src": [240,8], "f": 0, "t": 62, "d": [1112], "a": 1 }, + { "px": [72,184], "src": [240,8], "f": 0, "t": 62, "d": [1113], "a": 1 }, + { "px": [80,184], "src": [240,8], "f": 0, "t": 62, "d": [1114], "a": 1 }, + { "px": [88,184], "src": [240,8], "f": 0, "t": 62, "d": [1115], "a": 1 }, + { "px": [96,184], "src": [32,128], "f": 0, "t": 516, "d": [1116], "a": 1 }, + { "px": [104,184], "src": [240,8], "f": 0, "t": 62, "d": [1117], "a": 1 }, + { "px": [112,184], "src": [240,8], "f": 0, "t": 62, "d": [1118], "a": 1 }, + { "px": [120,184], "src": [240,8], "f": 0, "t": 62, "d": [1119], "a": 1 }, + { "px": [128,184], "src": [240,8], "f": 0, "t": 62, "d": [1120], "a": 1 }, + { "px": [136,184], "src": [240,8], "f": 0, "t": 62, "d": [1121], "a": 1 }, + { "px": [144,184], "src": [240,8], "f": 0, "t": 62, "d": [1122], "a": 1 }, + { "px": [152,184], "src": [240,8], "f": 0, "t": 62, "d": [1123], "a": 1 }, + { "px": [160,184], "src": [240,8], "f": 0, "t": 62, "d": [1124], "a": 1 }, + { "px": [168,184], "src": [240,8], "f": 0, "t": 62, "d": [1125], "a": 1 }, + { "px": [176,184], "src": [240,8], "f": 0, "t": 62, "d": [1126], "a": 1 }, + { "px": [184,184], "src": [240,8], "f": 0, "t": 62, "d": [1127], "a": 1 }, + { "px": [192,184], "src": [240,8], "f": 0, "t": 62, "d": [1128], "a": 1 }, + { "px": [200,184], "src": [240,8], "f": 0, "t": 62, "d": [1129], "a": 1 }, + { "px": [208,184], "src": [240,8], "f": 0, "t": 62, "d": [1130], "a": 1 }, + { "px": [216,184], "src": [240,8], "f": 0, "t": 62, "d": [1131], "a": 1 }, + { "px": [224,184], "src": [240,8], "f": 0, "t": 62, "d": [1132], "a": 1 }, + { "px": [232,184], "src": [240,8], "f": 0, "t": 62, "d": [1133], "a": 1 }, + { "px": [240,184], "src": [240,8], "f": 0, "t": 62, "d": [1134], "a": 1 }, + { "px": [248,184], "src": [240,8], "f": 0, "t": 62, "d": [1135], "a": 1 }, + { "px": [256,184], "src": [240,8], "f": 0, "t": 62, "d": [1136], "a": 1 }, + { "px": [264,184], "src": [240,8], "f": 0, "t": 62, "d": [1137], "a": 1 }, + { "px": [272,184], "src": [240,8], "f": 0, "t": 62, "d": [1138], "a": 1 }, + { "px": [280,184], "src": [240,8], "f": 0, "t": 62, "d": [1139], "a": 1 }, + { "px": [288,184], "src": [240,8], "f": 0, "t": 62, "d": [1140], "a": 1 }, + { "px": [296,184], "src": [240,8], "f": 0, "t": 62, "d": [1141], "a": 1 }, + { "px": [304,184], "src": [240,8], "f": 0, "t": 62, "d": [1142], "a": 1 }, + { "px": [312,184], "src": [240,8], "f": 0, "t": 62, "d": [1143], "a": 1 }, + { "px": [320,184], "src": [240,8], "f": 0, "t": 62, "d": [1144], "a": 1 }, + { "px": [328,184], "src": [240,8], "f": 0, "t": 62, "d": [1145], "a": 1 }, + { "px": [336,184], "src": [240,8], "f": 0, "t": 62, "d": [1146], "a": 1 }, + { "px": [344,184], "src": [240,8], "f": 0, "t": 62, "d": [1147], "a": 1 }, + { "px": [352,184], "src": [240,8], "f": 0, "t": 62, "d": [1148], "a": 1 }, + { "px": [360,184], "src": [240,8], "f": 0, "t": 62, "d": [1149], "a": 1 }, + { "px": [368,184], "src": [240,8], "f": 0, "t": 62, "d": [1150], "a": 1 }, + { "px": [376,184], "src": [240,8], "f": 0, "t": 62, "d": [1151], "a": 1 }, + { "px": [0,192], "src": [240,8], "f": 0, "t": 62, "d": [1152], "a": 1 }, + { "px": [8,192], "src": [240,8], "f": 0, "t": 62, "d": [1153], "a": 1 }, + { "px": [16,192], "src": [240,8], "f": 0, "t": 62, "d": [1154], "a": 1 }, + { "px": [24,192], "src": [240,8], "f": 0, "t": 62, "d": [1155], "a": 1 }, + { "px": [32,192], "src": [240,8], "f": 0, "t": 62, "d": [1156], "a": 1 }, + { "px": [40,192], "src": [240,8], "f": 0, "t": 62, "d": [1157], "a": 1 }, + { "px": [48,192], "src": [240,8], "f": 0, "t": 62, "d": [1158], "a": 1 }, + { "px": [56,192], "src": [240,8], "f": 0, "t": 62, "d": [1159], "a": 1 }, + { "px": [64,192], "src": [240,8], "f": 0, "t": 62, "d": [1160], "a": 1 }, + { "px": [72,192], "src": [240,8], "f": 0, "t": 62, "d": [1161], "a": 1 }, + { "px": [80,192], "src": [240,8], "f": 0, "t": 62, "d": [1162], "a": 1 }, + { "px": [88,192], "src": [240,8], "f": 0, "t": 62, "d": [1163], "a": 1 }, + { "px": [96,192], "src": [32,136], "f": 0, "t": 548, "d": [1164], "a": 1 }, + { "px": [104,192], "src": [240,8], "f": 0, "t": 62, "d": [1165], "a": 1 }, + { "px": [112,192], "src": [240,8], "f": 0, "t": 62, "d": [1166], "a": 1 }, + { "px": [120,192], "src": [240,8], "f": 0, "t": 62, "d": [1167], "a": 1 }, + { "px": [128,192], "src": [240,8], "f": 0, "t": 62, "d": [1168], "a": 1 }, + { "px": [136,192], "src": [240,8], "f": 0, "t": 62, "d": [1169], "a": 1 }, + { "px": [144,192], "src": [240,8], "f": 0, "t": 62, "d": [1170], "a": 1 }, + { "px": [152,192], "src": [240,8], "f": 0, "t": 62, "d": [1171], "a": 1 }, + { "px": [160,192], "src": [240,8], "f": 0, "t": 62, "d": [1172], "a": 1 }, + { "px": [168,192], "src": [240,8], "f": 0, "t": 62, "d": [1173], "a": 1 }, + { "px": [176,192], "src": [240,8], "f": 0, "t": 62, "d": [1174], "a": 1 }, + { "px": [184,192], "src": [240,8], "f": 0, "t": 62, "d": [1175], "a": 1 }, + { "px": [192,192], "src": [240,8], "f": 0, "t": 62, "d": [1176], "a": 1 }, + { "px": [200,192], "src": [240,8], "f": 0, "t": 62, "d": [1177], "a": 1 }, + { "px": [208,192], "src": [240,8], "f": 0, "t": 62, "d": [1178], "a": 1 }, + { "px": [216,192], "src": [240,8], "f": 0, "t": 62, "d": [1179], "a": 1 }, + { "px": [224,192], "src": [240,8], "f": 0, "t": 62, "d": [1180], "a": 1 }, + { "px": [232,192], "src": [240,8], "f": 0, "t": 62, "d": [1181], "a": 1 }, + { "px": [240,192], "src": [240,8], "f": 0, "t": 62, "d": [1182], "a": 1 }, + { "px": [248,192], "src": [240,8], "f": 0, "t": 62, "d": [1183], "a": 1 }, + { "px": [256,192], "src": [240,8], "f": 0, "t": 62, "d": [1184], "a": 1 }, + { "px": [264,192], "src": [240,8], "f": 0, "t": 62, "d": [1185], "a": 1 }, + { "px": [272,192], "src": [240,8], "f": 0, "t": 62, "d": [1186], "a": 1 }, + { "px": [280,192], "src": [240,8], "f": 0, "t": 62, "d": [1187], "a": 1 }, + { "px": [288,192], "src": [240,8], "f": 0, "t": 62, "d": [1188], "a": 1 }, + { "px": [296,192], "src": [240,8], "f": 0, "t": 62, "d": [1189], "a": 1 }, + { "px": [304,192], "src": [240,8], "f": 0, "t": 62, "d": [1190], "a": 1 }, + { "px": [312,192], "src": [240,8], "f": 0, "t": 62, "d": [1191], "a": 1 }, + { "px": [320,192], "src": [240,8], "f": 0, "t": 62, "d": [1192], "a": 1 }, + { "px": [328,192], "src": [240,8], "f": 0, "t": 62, "d": [1193], "a": 1 }, + { "px": [336,192], "src": [240,8], "f": 0, "t": 62, "d": [1194], "a": 1 }, + { "px": [344,192], "src": [240,8], "f": 0, "t": 62, "d": [1195], "a": 1 }, + { "px": [352,192], "src": [240,8], "f": 0, "t": 62, "d": [1196], "a": 1 }, + { "px": [360,192], "src": [240,8], "f": 0, "t": 62, "d": [1197], "a": 1 }, + { "px": [368,192], "src": [240,8], "f": 0, "t": 62, "d": [1198], "a": 1 }, + { "px": [376,192], "src": [240,8], "f": 0, "t": 62, "d": [1199], "a": 1 }, + { "px": [0,200], "src": [240,8], "f": 0, "t": 62, "d": [1200], "a": 1 }, + { "px": [8,200], "src": [240,8], "f": 0, "t": 62, "d": [1201], "a": 1 }, + { "px": [16,200], "src": [240,8], "f": 0, "t": 62, "d": [1202], "a": 1 }, + { "px": [24,200], "src": [240,8], "f": 0, "t": 62, "d": [1203], "a": 1 }, + { "px": [32,200], "src": [240,8], "f": 0, "t": 62, "d": [1204], "a": 1 }, + { "px": [40,200], "src": [240,8], "f": 0, "t": 62, "d": [1205], "a": 1 }, + { "px": [48,200], "src": [240,8], "f": 0, "t": 62, "d": [1206], "a": 1 }, + { "px": [56,200], "src": [240,8], "f": 0, "t": 62, "d": [1207], "a": 1 }, + { "px": [64,200], "src": [240,8], "f": 0, "t": 62, "d": [1208], "a": 1 }, + { "px": [72,200], "src": [240,8], "f": 0, "t": 62, "d": [1209], "a": 1 }, + { "px": [80,200], "src": [240,8], "f": 0, "t": 62, "d": [1210], "a": 1 }, + { "px": [88,200], "src": [240,8], "f": 0, "t": 62, "d": [1211], "a": 1 }, + { "px": [96,200], "src": [32,144], "f": 0, "t": 580, "d": [1212], "a": 1 }, + { "px": [104,200], "src": [240,8], "f": 0, "t": 62, "d": [1213], "a": 1 }, + { "px": [112,200], "src": [240,8], "f": 0, "t": 62, "d": [1214], "a": 1 }, + { "px": [120,200], "src": [240,8], "f": 0, "t": 62, "d": [1215], "a": 1 }, + { "px": [128,200], "src": [240,8], "f": 0, "t": 62, "d": [1216], "a": 1 }, + { "px": [136,200], "src": [240,8], "f": 0, "t": 62, "d": [1217], "a": 1 }, + { "px": [144,200], "src": [240,8], "f": 0, "t": 62, "d": [1218], "a": 1 }, + { "px": [152,200], "src": [240,8], "f": 0, "t": 62, "d": [1219], "a": 1 }, + { "px": [160,200], "src": [240,8], "f": 0, "t": 62, "d": [1220], "a": 1 }, + { "px": [168,200], "src": [240,8], "f": 0, "t": 62, "d": [1221], "a": 1 }, + { "px": [176,200], "src": [240,8], "f": 0, "t": 62, "d": [1222], "a": 1 }, + { "px": [184,200], "src": [240,8], "f": 0, "t": 62, "d": [1223], "a": 1 }, + { "px": [192,200], "src": [240,8], "f": 0, "t": 62, "d": [1224], "a": 1 }, + { "px": [200,200], "src": [240,8], "f": 0, "t": 62, "d": [1225], "a": 1 }, + { "px": [208,200], "src": [240,8], "f": 0, "t": 62, "d": [1226], "a": 1 }, + { "px": [216,200], "src": [240,8], "f": 0, "t": 62, "d": [1227], "a": 1 }, + { "px": [224,200], "src": [240,8], "f": 0, "t": 62, "d": [1228], "a": 1 }, + { "px": [232,200], "src": [240,8], "f": 0, "t": 62, "d": [1229], "a": 1 }, + { "px": [240,200], "src": [240,8], "f": 0, "t": 62, "d": [1230], "a": 1 }, + { "px": [248,200], "src": [240,8], "f": 0, "t": 62, "d": [1231], "a": 1 }, + { "px": [256,200], "src": [240,8], "f": 0, "t": 62, "d": [1232], "a": 1 }, + { "px": [264,200], "src": [240,8], "f": 0, "t": 62, "d": [1233], "a": 1 }, + { "px": [272,200], "src": [240,8], "f": 0, "t": 62, "d": [1234], "a": 1 }, + { "px": [280,200], "src": [240,8], "f": 0, "t": 62, "d": [1235], "a": 1 }, + { "px": [288,200], "src": [240,8], "f": 0, "t": 62, "d": [1236], "a": 1 }, + { "px": [296,200], "src": [240,8], "f": 0, "t": 62, "d": [1237], "a": 1 }, + { "px": [304,200], "src": [240,8], "f": 0, "t": 62, "d": [1238], "a": 1 }, + { "px": [312,200], "src": [240,8], "f": 0, "t": 62, "d": [1239], "a": 1 }, + { "px": [320,200], "src": [240,8], "f": 0, "t": 62, "d": [1240], "a": 1 }, + { "px": [328,200], "src": [240,8], "f": 0, "t": 62, "d": [1241], "a": 1 }, + { "px": [336,200], "src": [240,8], "f": 0, "t": 62, "d": [1242], "a": 1 }, + { "px": [344,200], "src": [240,8], "f": 0, "t": 62, "d": [1243], "a": 1 }, + { "px": [352,200], "src": [240,8], "f": 0, "t": 62, "d": [1244], "a": 1 }, + { "px": [360,200], "src": [240,8], "f": 0, "t": 62, "d": [1245], "a": 1 }, + { "px": [368,200], "src": [240,8], "f": 0, "t": 62, "d": [1246], "a": 1 }, + { "px": [376,200], "src": [240,8], "f": 0, "t": 62, "d": [1247], "a": 1 }, + { "px": [0,208], "src": [240,8], "f": 0, "t": 62, "d": [1248], "a": 1 }, + { "px": [8,208], "src": [240,8], "f": 0, "t": 62, "d": [1249], "a": 1 }, + { "px": [16,208], "src": [240,8], "f": 0, "t": 62, "d": [1250], "a": 1 }, + { "px": [24,208], "src": [240,8], "f": 0, "t": 62, "d": [1251], "a": 1 }, + { "px": [32,208], "src": [240,8], "f": 0, "t": 62, "d": [1252], "a": 1 }, + { "px": [40,208], "src": [240,8], "f": 0, "t": 62, "d": [1253], "a": 1 }, + { "px": [48,208], "src": [240,8], "f": 0, "t": 62, "d": [1254], "a": 1 }, + { "px": [56,208], "src": [240,8], "f": 0, "t": 62, "d": [1255], "a": 1 }, + { "px": [64,208], "src": [240,8], "f": 0, "t": 62, "d": [1256], "a": 1 }, + { "px": [72,208], "src": [240,8], "f": 0, "t": 62, "d": [1257], "a": 1 }, + { "px": [80,208], "src": [240,8], "f": 0, "t": 62, "d": [1258], "a": 1 }, + { "px": [88,208], "src": [240,8], "f": 0, "t": 62, "d": [1259], "a": 1 }, + { "px": [96,208], "src": [32,152], "f": 0, "t": 612, "d": [1260], "a": 1 }, + { "px": [104,208], "src": [240,8], "f": 0, "t": 62, "d": [1261], "a": 1 }, + { "px": [112,208], "src": [240,8], "f": 0, "t": 62, "d": [1262], "a": 1 }, + { "px": [120,208], "src": [240,8], "f": 0, "t": 62, "d": [1263], "a": 1 }, + { "px": [128,208], "src": [240,8], "f": 0, "t": 62, "d": [1264], "a": 1 }, + { "px": [136,208], "src": [240,8], "f": 0, "t": 62, "d": [1265], "a": 1 }, + { "px": [144,208], "src": [240,8], "f": 0, "t": 62, "d": [1266], "a": 1 }, + { "px": [152,208], "src": [240,8], "f": 0, "t": 62, "d": [1267], "a": 1 }, + { "px": [160,208], "src": [240,8], "f": 0, "t": 62, "d": [1268], "a": 1 }, + { "px": [168,208], "src": [240,8], "f": 0, "t": 62, "d": [1269], "a": 1 }, + { "px": [176,208], "src": [240,8], "f": 0, "t": 62, "d": [1270], "a": 1 }, + { "px": [184,208], "src": [240,8], "f": 0, "t": 62, "d": [1271], "a": 1 }, + { "px": [192,208], "src": [240,8], "f": 0, "t": 62, "d": [1272], "a": 1 }, + { "px": [200,208], "src": [240,8], "f": 0, "t": 62, "d": [1273], "a": 1 }, + { "px": [208,208], "src": [240,8], "f": 0, "t": 62, "d": [1274], "a": 1 }, + { "px": [216,208], "src": [240,8], "f": 0, "t": 62, "d": [1275], "a": 1 }, + { "px": [224,208], "src": [240,8], "f": 0, "t": 62, "d": [1276], "a": 1 }, + { "px": [232,208], "src": [240,8], "f": 0, "t": 62, "d": [1277], "a": 1 }, + { "px": [240,208], "src": [240,8], "f": 0, "t": 62, "d": [1278], "a": 1 }, + { "px": [248,208], "src": [240,8], "f": 0, "t": 62, "d": [1279], "a": 1 }, + { "px": [256,208], "src": [240,8], "f": 0, "t": 62, "d": [1280], "a": 1 }, + { "px": [264,208], "src": [240,8], "f": 0, "t": 62, "d": [1281], "a": 1 }, + { "px": [272,208], "src": [240,8], "f": 0, "t": 62, "d": [1282], "a": 1 }, + { "px": [280,208], "src": [240,8], "f": 0, "t": 62, "d": [1283], "a": 1 }, + { "px": [288,208], "src": [240,8], "f": 0, "t": 62, "d": [1284], "a": 1 }, + { "px": [296,208], "src": [240,8], "f": 0, "t": 62, "d": [1285], "a": 1 }, + { "px": [304,208], "src": [240,8], "f": 0, "t": 62, "d": [1286], "a": 1 }, + { "px": [312,208], "src": [240,8], "f": 0, "t": 62, "d": [1287], "a": 1 }, + { "px": [320,208], "src": [240,8], "f": 0, "t": 62, "d": [1288], "a": 1 }, + { "px": [328,208], "src": [240,8], "f": 0, "t": 62, "d": [1289], "a": 1 }, + { "px": [336,208], "src": [240,8], "f": 0, "t": 62, "d": [1290], "a": 1 }, + { "px": [344,208], "src": [240,8], "f": 0, "t": 62, "d": [1291], "a": 1 }, + { "px": [352,208], "src": [240,8], "f": 0, "t": 62, "d": [1292], "a": 1 }, + { "px": [360,208], "src": [240,8], "f": 0, "t": 62, "d": [1293], "a": 1 }, + { "px": [368,208], "src": [240,8], "f": 0, "t": 62, "d": [1294], "a": 1 }, + { "px": [376,208], "src": [240,8], "f": 0, "t": 62, "d": [1295], "a": 1 }, + { "px": [0,216], "src": [240,8], "f": 0, "t": 62, "d": [1296], "a": 1 }, + { "px": [8,216], "src": [240,8], "f": 0, "t": 62, "d": [1297], "a": 1 }, + { "px": [16,216], "src": [240,8], "f": 0, "t": 62, "d": [1298], "a": 1 }, + { "px": [24,216], "src": [240,8], "f": 0, "t": 62, "d": [1299], "a": 1 }, + { "px": [32,216], "src": [240,8], "f": 0, "t": 62, "d": [1300], "a": 1 }, + { "px": [40,216], "src": [240,8], "f": 0, "t": 62, "d": [1301], "a": 1 }, + { "px": [48,216], "src": [240,8], "f": 0, "t": 62, "d": [1302], "a": 1 }, + { "px": [56,216], "src": [240,8], "f": 0, "t": 62, "d": [1303], "a": 1 }, + { "px": [64,216], "src": [240,8], "f": 0, "t": 62, "d": [1304], "a": 1 }, + { "px": [72,216], "src": [240,8], "f": 0, "t": 62, "d": [1305], "a": 1 }, + { "px": [80,216], "src": [240,8], "f": 0, "t": 62, "d": [1306], "a": 1 }, + { "px": [88,216], "src": [240,8], "f": 0, "t": 62, "d": [1307], "a": 1 }, + { "px": [96,216], "src": [32,160], "f": 0, "t": 644, "d": [1308], "a": 1 }, + { "px": [104,216], "src": [240,8], "f": 0, "t": 62, "d": [1309], "a": 1 }, + { "px": [112,216], "src": [240,8], "f": 0, "t": 62, "d": [1310], "a": 1 }, + { "px": [120,216], "src": [240,8], "f": 0, "t": 62, "d": [1311], "a": 1 }, + { "px": [128,216], "src": [240,8], "f": 0, "t": 62, "d": [1312], "a": 1 }, + { "px": [136,216], "src": [240,8], "f": 0, "t": 62, "d": [1313], "a": 1 }, + { "px": [144,216], "src": [240,8], "f": 0, "t": 62, "d": [1314], "a": 1 }, + { "px": [152,216], "src": [240,8], "f": 0, "t": 62, "d": [1315], "a": 1 }, + { "px": [160,216], "src": [240,8], "f": 0, "t": 62, "d": [1316], "a": 1 }, + { "px": [168,216], "src": [240,8], "f": 0, "t": 62, "d": [1317], "a": 1 }, + { "px": [176,216], "src": [240,8], "f": 0, "t": 62, "d": [1318], "a": 1 }, + { "px": [184,216], "src": [240,8], "f": 0, "t": 62, "d": [1319], "a": 1 }, + { "px": [192,216], "src": [240,8], "f": 0, "t": 62, "d": [1320], "a": 1 }, + { "px": [200,216], "src": [240,8], "f": 0, "t": 62, "d": [1321], "a": 1 }, + { "px": [208,216], "src": [240,8], "f": 0, "t": 62, "d": [1322], "a": 1 }, + { "px": [216,216], "src": [240,8], "f": 0, "t": 62, "d": [1323], "a": 1 }, + { "px": [224,216], "src": [240,8], "f": 0, "t": 62, "d": [1324], "a": 1 }, + { "px": [232,216], "src": [240,8], "f": 0, "t": 62, "d": [1325], "a": 1 }, + { "px": [240,216], "src": [240,8], "f": 0, "t": 62, "d": [1326], "a": 1 }, + { "px": [248,216], "src": [240,8], "f": 0, "t": 62, "d": [1327], "a": 1 }, + { "px": [256,216], "src": [240,8], "f": 0, "t": 62, "d": [1328], "a": 1 }, + { "px": [264,216], "src": [240,8], "f": 0, "t": 62, "d": [1329], "a": 1 }, + { "px": [272,216], "src": [240,8], "f": 0, "t": 62, "d": [1330], "a": 1 }, + { "px": [280,216], "src": [240,8], "f": 0, "t": 62, "d": [1331], "a": 1 }, + { "px": [288,216], "src": [240,8], "f": 0, "t": 62, "d": [1332], "a": 1 }, + { "px": [296,216], "src": [240,8], "f": 0, "t": 62, "d": [1333], "a": 1 }, + { "px": [304,216], "src": [240,8], "f": 0, "t": 62, "d": [1334], "a": 1 }, + { "px": [312,216], "src": [240,8], "f": 0, "t": 62, "d": [1335], "a": 1 }, + { "px": [320,216], "src": [240,8], "f": 0, "t": 62, "d": [1336], "a": 1 }, + { "px": [328,216], "src": [240,8], "f": 0, "t": 62, "d": [1337], "a": 1 }, + { "px": [336,216], "src": [240,8], "f": 0, "t": 62, "d": [1338], "a": 1 }, + { "px": [344,216], "src": [240,8], "f": 0, "t": 62, "d": [1339], "a": 1 }, + { "px": [352,216], "src": [240,8], "f": 0, "t": 62, "d": [1340], "a": 1 }, + { "px": [360,216], "src": [240,8], "f": 0, "t": 62, "d": [1341], "a": 1 }, + { "px": [368,216], "src": [240,8], "f": 0, "t": 62, "d": [1342], "a": 1 }, + { "px": [376,216], "src": [240,8], "f": 0, "t": 62, "d": [1343], "a": 1 }, + { "px": [0,224], "src": [240,8], "f": 0, "t": 62, "d": [1344], "a": 1 }, + { "px": [8,224], "src": [240,8], "f": 0, "t": 62, "d": [1345], "a": 1 }, + { "px": [16,224], "src": [240,8], "f": 0, "t": 62, "d": [1346], "a": 1 }, + { "px": [24,224], "src": [240,8], "f": 0, "t": 62, "d": [1347], "a": 1 }, + { "px": [32,224], "src": [240,8], "f": 0, "t": 62, "d": [1348], "a": 1 }, + { "px": [40,224], "src": [240,8], "f": 0, "t": 62, "d": [1349], "a": 1 }, + { "px": [48,224], "src": [240,8], "f": 0, "t": 62, "d": [1350], "a": 1 }, + { "px": [56,224], "src": [240,8], "f": 0, "t": 62, "d": [1351], "a": 1 }, + { "px": [64,224], "src": [240,8], "f": 0, "t": 62, "d": [1352], "a": 1 }, + { "px": [72,224], "src": [240,8], "f": 0, "t": 62, "d": [1353], "a": 1 }, + { "px": [80,224], "src": [240,8], "f": 0, "t": 62, "d": [1354], "a": 1 }, + { "px": [88,224], "src": [240,8], "f": 0, "t": 62, "d": [1355], "a": 1 }, + { "px": [96,224], "src": [32,168], "f": 0, "t": 676, "d": [1356], "a": 1 }, + { "px": [104,224], "src": [240,8], "f": 0, "t": 62, "d": [1357], "a": 1 }, + { "px": [112,224], "src": [240,8], "f": 0, "t": 62, "d": [1358], "a": 1 }, + { "px": [120,224], "src": [240,8], "f": 0, "t": 62, "d": [1359], "a": 1 }, + { "px": [128,224], "src": [240,8], "f": 0, "t": 62, "d": [1360], "a": 1 }, + { "px": [136,224], "src": [240,8], "f": 0, "t": 62, "d": [1361], "a": 1 }, + { "px": [144,224], "src": [240,8], "f": 0, "t": 62, "d": [1362], "a": 1 }, + { "px": [152,224], "src": [240,8], "f": 0, "t": 62, "d": [1363], "a": 1 }, + { "px": [160,224], "src": [240,8], "f": 0, "t": 62, "d": [1364], "a": 1 }, + { "px": [168,224], "src": [240,8], "f": 0, "t": 62, "d": [1365], "a": 1 }, + { "px": [176,224], "src": [240,8], "f": 0, "t": 62, "d": [1366], "a": 1 }, + { "px": [184,224], "src": [240,8], "f": 0, "t": 62, "d": [1367], "a": 1 }, + { "px": [192,224], "src": [240,8], "f": 0, "t": 62, "d": [1368], "a": 1 }, + { "px": [200,224], "src": [240,8], "f": 0, "t": 62, "d": [1369], "a": 1 }, + { "px": [208,224], "src": [240,8], "f": 0, "t": 62, "d": [1370], "a": 1 }, + { "px": [216,224], "src": [240,8], "f": 0, "t": 62, "d": [1371], "a": 1 }, + { "px": [224,224], "src": [240,8], "f": 0, "t": 62, "d": [1372], "a": 1 }, + { "px": [232,224], "src": [240,8], "f": 0, "t": 62, "d": [1373], "a": 1 }, + { "px": [240,224], "src": [240,8], "f": 0, "t": 62, "d": [1374], "a": 1 }, + { "px": [248,224], "src": [240,8], "f": 0, "t": 62, "d": [1375], "a": 1 }, + { "px": [256,224], "src": [240,8], "f": 0, "t": 62, "d": [1376], "a": 1 }, + { "px": [264,224], "src": [240,8], "f": 0, "t": 62, "d": [1377], "a": 1 }, + { "px": [272,224], "src": [240,8], "f": 0, "t": 62, "d": [1378], "a": 1 }, + { "px": [280,224], "src": [240,8], "f": 0, "t": 62, "d": [1379], "a": 1 }, + { "px": [288,224], "src": [240,8], "f": 0, "t": 62, "d": [1380], "a": 1 }, + { "px": [296,224], "src": [240,8], "f": 0, "t": 62, "d": [1381], "a": 1 }, + { "px": [304,224], "src": [240,8], "f": 0, "t": 62, "d": [1382], "a": 1 }, + { "px": [312,224], "src": [240,8], "f": 0, "t": 62, "d": [1383], "a": 1 }, + { "px": [320,224], "src": [240,8], "f": 0, "t": 62, "d": [1384], "a": 1 }, + { "px": [328,224], "src": [240,8], "f": 0, "t": 62, "d": [1385], "a": 1 }, + { "px": [336,224], "src": [240,8], "f": 0, "t": 62, "d": [1386], "a": 1 }, + { "px": [344,224], "src": [240,8], "f": 0, "t": 62, "d": [1387], "a": 1 }, + { "px": [352,224], "src": [240,8], "f": 0, "t": 62, "d": [1388], "a": 1 }, + { "px": [360,224], "src": [240,8], "f": 0, "t": 62, "d": [1389], "a": 1 }, + { "px": [368,224], "src": [240,8], "f": 0, "t": 62, "d": [1390], "a": 1 }, + { "px": [376,224], "src": [240,8], "f": 0, "t": 62, "d": [1391], "a": 1 }, + { "px": [0,232], "src": [240,8], "f": 0, "t": 62, "d": [1392], "a": 1 }, + { "px": [8,232], "src": [240,8], "f": 0, "t": 62, "d": [1393], "a": 1 }, + { "px": [16,232], "src": [240,8], "f": 0, "t": 62, "d": [1394], "a": 1 }, + { "px": [24,232], "src": [240,8], "f": 0, "t": 62, "d": [1395], "a": 1 }, + { "px": [32,232], "src": [240,8], "f": 0, "t": 62, "d": [1396], "a": 1 }, + { "px": [40,232], "src": [240,8], "f": 0, "t": 62, "d": [1397], "a": 1 }, + { "px": [48,232], "src": [240,8], "f": 0, "t": 62, "d": [1398], "a": 1 }, + { "px": [56,232], "src": [240,8], "f": 0, "t": 62, "d": [1399], "a": 1 }, + { "px": [64,232], "src": [240,8], "f": 0, "t": 62, "d": [1400], "a": 1 }, + { "px": [72,232], "src": [240,8], "f": 0, "t": 62, "d": [1401], "a": 1 }, + { "px": [80,232], "src": [240,8], "f": 0, "t": 62, "d": [1402], "a": 1 }, + { "px": [88,232], "src": [240,8], "f": 0, "t": 62, "d": [1403], "a": 1 }, + { "px": [96,232], "src": [32,176], "f": 0, "t": 708, "d": [1404], "a": 1 }, + { "px": [104,232], "src": [240,8], "f": 0, "t": 62, "d": [1405], "a": 1 }, + { "px": [112,232], "src": [240,8], "f": 0, "t": 62, "d": [1406], "a": 1 }, + { "px": [120,232], "src": [240,8], "f": 0, "t": 62, "d": [1407], "a": 1 }, + { "px": [128,232], "src": [240,8], "f": 0, "t": 62, "d": [1408], "a": 1 }, + { "px": [136,232], "src": [240,8], "f": 0, "t": 62, "d": [1409], "a": 1 }, + { "px": [144,232], "src": [240,8], "f": 0, "t": 62, "d": [1410], "a": 1 }, + { "px": [152,232], "src": [240,8], "f": 0, "t": 62, "d": [1411], "a": 1 }, + { "px": [160,232], "src": [240,8], "f": 0, "t": 62, "d": [1412], "a": 1 }, + { "px": [168,232], "src": [240,8], "f": 0, "t": 62, "d": [1413], "a": 1 }, + { "px": [176,232], "src": [240,8], "f": 0, "t": 62, "d": [1414], "a": 1 }, + { "px": [184,232], "src": [240,8], "f": 0, "t": 62, "d": [1415], "a": 1 }, + { "px": [192,232], "src": [240,8], "f": 0, "t": 62, "d": [1416], "a": 1 }, + { "px": [200,232], "src": [240,8], "f": 0, "t": 62, "d": [1417], "a": 1 }, + { "px": [208,232], "src": [240,8], "f": 0, "t": 62, "d": [1418], "a": 1 }, + { "px": [216,232], "src": [240,8], "f": 0, "t": 62, "d": [1419], "a": 1 }, + { "px": [224,232], "src": [240,8], "f": 0, "t": 62, "d": [1420], "a": 1 }, + { "px": [232,232], "src": [240,8], "f": 0, "t": 62, "d": [1421], "a": 1 }, + { "px": [240,232], "src": [240,8], "f": 0, "t": 62, "d": [1422], "a": 1 }, + { "px": [248,232], "src": [240,8], "f": 0, "t": 62, "d": [1423], "a": 1 }, + { "px": [256,232], "src": [240,8], "f": 0, "t": 62, "d": [1424], "a": 1 }, + { "px": [264,232], "src": [240,8], "f": 0, "t": 62, "d": [1425], "a": 1 }, + { "px": [272,232], "src": [240,8], "f": 0, "t": 62, "d": [1426], "a": 1 }, + { "px": [280,232], "src": [240,8], "f": 0, "t": 62, "d": [1427], "a": 1 }, + { "px": [288,232], "src": [240,8], "f": 0, "t": 62, "d": [1428], "a": 1 }, + { "px": [296,232], "src": [240,8], "f": 0, "t": 62, "d": [1429], "a": 1 }, + { "px": [304,232], "src": [240,8], "f": 0, "t": 62, "d": [1430], "a": 1 }, + { "px": [312,232], "src": [240,8], "f": 0, "t": 62, "d": [1431], "a": 1 }, + { "px": [320,232], "src": [240,8], "f": 0, "t": 62, "d": [1432], "a": 1 }, + { "px": [328,232], "src": [240,8], "f": 0, "t": 62, "d": [1433], "a": 1 }, + { "px": [336,232], "src": [240,8], "f": 0, "t": 62, "d": [1434], "a": 1 }, + { "px": [344,232], "src": [240,8], "f": 0, "t": 62, "d": [1435], "a": 1 }, + { "px": [352,232], "src": [240,8], "f": 0, "t": 62, "d": [1436], "a": 1 }, + { "px": [360,232], "src": [240,8], "f": 0, "t": 62, "d": [1437], "a": 1 }, + { "px": [368,232], "src": [240,8], "f": 0, "t": 62, "d": [1438], "a": 1 }, + { "px": [376,232], "src": [240,8], "f": 0, "t": 62, "d": [1439], "a": 1 }, + { "px": [0,240], "src": [240,8], "f": 0, "t": 62, "d": [1440], "a": 1 }, + { "px": [8,240], "src": [240,8], "f": 0, "t": 62, "d": [1441], "a": 1 }, + { "px": [16,240], "src": [240,8], "f": 0, "t": 62, "d": [1442], "a": 1 }, + { "px": [24,240], "src": [240,8], "f": 0, "t": 62, "d": [1443], "a": 1 }, + { "px": [32,240], "src": [240,8], "f": 0, "t": 62, "d": [1444], "a": 1 }, + { "px": [40,240], "src": [240,8], "f": 0, "t": 62, "d": [1445], "a": 1 }, + { "px": [48,240], "src": [240,8], "f": 0, "t": 62, "d": [1446], "a": 1 }, + { "px": [56,240], "src": [240,8], "f": 0, "t": 62, "d": [1447], "a": 1 }, + { "px": [64,240], "src": [240,8], "f": 0, "t": 62, "d": [1448], "a": 1 }, + { "px": [72,240], "src": [240,8], "f": 0, "t": 62, "d": [1449], "a": 1 }, + { "px": [80,240], "src": [240,8], "f": 0, "t": 62, "d": [1450], "a": 1 }, + { "px": [88,240], "src": [240,8], "f": 0, "t": 62, "d": [1451], "a": 1 }, + { "px": [96,240], "src": [32,184], "f": 0, "t": 740, "d": [1452], "a": 1 }, + { "px": [104,240], "src": [240,8], "f": 0, "t": 62, "d": [1453], "a": 1 }, + { "px": [112,240], "src": [240,8], "f": 0, "t": 62, "d": [1454], "a": 1 }, + { "px": [120,240], "src": [240,8], "f": 0, "t": 62, "d": [1455], "a": 1 }, + { "px": [128,240], "src": [240,8], "f": 0, "t": 62, "d": [1456], "a": 1 }, + { "px": [136,240], "src": [240,8], "f": 0, "t": 62, "d": [1457], "a": 1 }, + { "px": [144,240], "src": [240,8], "f": 0, "t": 62, "d": [1458], "a": 1 }, + { "px": [152,240], "src": [240,8], "f": 0, "t": 62, "d": [1459], "a": 1 }, + { "px": [160,240], "src": [240,8], "f": 0, "t": 62, "d": [1460], "a": 1 }, + { "px": [168,240], "src": [240,8], "f": 0, "t": 62, "d": [1461], "a": 1 }, + { "px": [176,240], "src": [240,8], "f": 0, "t": 62, "d": [1462], "a": 1 }, + { "px": [184,240], "src": [240,8], "f": 0, "t": 62, "d": [1463], "a": 1 }, + { "px": [192,240], "src": [240,8], "f": 0, "t": 62, "d": [1464], "a": 1 }, + { "px": [200,240], "src": [240,8], "f": 0, "t": 62, "d": [1465], "a": 1 }, + { "px": [208,240], "src": [240,8], "f": 0, "t": 62, "d": [1466], "a": 1 }, + { "px": [216,240], "src": [240,8], "f": 0, "t": 62, "d": [1467], "a": 1 }, + { "px": [224,240], "src": [240,8], "f": 0, "t": 62, "d": [1468], "a": 1 }, + { "px": [232,240], "src": [240,8], "f": 0, "t": 62, "d": [1469], "a": 1 }, + { "px": [240,240], "src": [240,8], "f": 0, "t": 62, "d": [1470], "a": 1 }, + { "px": [248,240], "src": [240,8], "f": 0, "t": 62, "d": [1471], "a": 1 }, + { "px": [256,240], "src": [240,8], "f": 0, "t": 62, "d": [1472], "a": 1 }, + { "px": [264,240], "src": [240,8], "f": 0, "t": 62, "d": [1473], "a": 1 }, + { "px": [272,240], "src": [240,8], "f": 0, "t": 62, "d": [1474], "a": 1 }, + { "px": [280,240], "src": [240,8], "f": 0, "t": 62, "d": [1475], "a": 1 }, + { "px": [288,240], "src": [240,8], "f": 0, "t": 62, "d": [1476], "a": 1 }, + { "px": [296,240], "src": [240,8], "f": 0, "t": 62, "d": [1477], "a": 1 }, + { "px": [304,240], "src": [240,8], "f": 0, "t": 62, "d": [1478], "a": 1 }, + { "px": [312,240], "src": [240,8], "f": 0, "t": 62, "d": [1479], "a": 1 }, + { "px": [320,240], "src": [240,8], "f": 0, "t": 62, "d": [1480], "a": 1 }, + { "px": [328,240], "src": [240,8], "f": 0, "t": 62, "d": [1481], "a": 1 }, + { "px": [336,240], "src": [240,8], "f": 0, "t": 62, "d": [1482], "a": 1 }, + { "px": [344,240], "src": [240,8], "f": 0, "t": 62, "d": [1483], "a": 1 }, + { "px": [352,240], "src": [240,8], "f": 0, "t": 62, "d": [1484], "a": 1 }, + { "px": [360,240], "src": [240,8], "f": 0, "t": 62, "d": [1485], "a": 1 }, + { "px": [368,240], "src": [240,8], "f": 0, "t": 62, "d": [1486], "a": 1 }, + { "px": [376,240], "src": [240,8], "f": 0, "t": 62, "d": [1487], "a": 1 }, + { "px": [0,248], "src": [240,8], "f": 0, "t": 62, "d": [1488], "a": 1 }, + { "px": [8,248], "src": [240,8], "f": 0, "t": 62, "d": [1489], "a": 1 }, + { "px": [16,248], "src": [240,8], "f": 0, "t": 62, "d": [1490], "a": 1 }, + { "px": [24,248], "src": [240,8], "f": 0, "t": 62, "d": [1491], "a": 1 }, + { "px": [32,248], "src": [240,8], "f": 0, "t": 62, "d": [1492], "a": 1 }, + { "px": [40,248], "src": [240,8], "f": 0, "t": 62, "d": [1493], "a": 1 }, + { "px": [48,248], "src": [240,8], "f": 0, "t": 62, "d": [1494], "a": 1 }, + { "px": [56,248], "src": [240,8], "f": 0, "t": 62, "d": [1495], "a": 1 }, + { "px": [64,248], "src": [240,8], "f": 0, "t": 62, "d": [1496], "a": 1 }, + { "px": [72,248], "src": [240,8], "f": 0, "t": 62, "d": [1497], "a": 1 }, + { "px": [80,248], "src": [240,8], "f": 0, "t": 62, "d": [1498], "a": 1 }, + { "px": [88,248], "src": [240,8], "f": 0, "t": 62, "d": [1499], "a": 1 }, + { "px": [96,248], "src": [240,8], "f": 0, "t": 62, "d": [1500], "a": 1 }, + { "px": [104,248], "src": [240,8], "f": 0, "t": 62, "d": [1501], "a": 1 }, + { "px": [112,248], "src": [240,8], "f": 0, "t": 62, "d": [1502], "a": 1 }, + { "px": [120,248], "src": [240,8], "f": 0, "t": 62, "d": [1503], "a": 1 }, + { "px": [128,248], "src": [240,8], "f": 0, "t": 62, "d": [1504], "a": 1 }, + { "px": [136,248], "src": [240,8], "f": 0, "t": 62, "d": [1505], "a": 1 }, + { "px": [144,248], "src": [240,8], "f": 0, "t": 62, "d": [1506], "a": 1 }, + { "px": [152,248], "src": [240,8], "f": 0, "t": 62, "d": [1507], "a": 1 }, + { "px": [160,248], "src": [240,8], "f": 0, "t": 62, "d": [1508], "a": 1 }, + { "px": [168,248], "src": [240,8], "f": 0, "t": 62, "d": [1509], "a": 1 }, + { "px": [176,248], "src": [240,8], "f": 0, "t": 62, "d": [1510], "a": 1 }, + { "px": [184,248], "src": [240,8], "f": 0, "t": 62, "d": [1511], "a": 1 }, + { "px": [192,248], "src": [240,8], "f": 0, "t": 62, "d": [1512], "a": 1 }, + { "px": [200,248], "src": [240,8], "f": 0, "t": 62, "d": [1513], "a": 1 }, + { "px": [208,248], "src": [240,8], "f": 0, "t": 62, "d": [1514], "a": 1 }, + { "px": [216,248], "src": [240,8], "f": 0, "t": 62, "d": [1515], "a": 1 }, + { "px": [224,248], "src": [240,8], "f": 0, "t": 62, "d": [1516], "a": 1 }, + { "px": [232,248], "src": [240,8], "f": 0, "t": 62, "d": [1517], "a": 1 }, + { "px": [240,248], "src": [240,8], "f": 0, "t": 62, "d": [1518], "a": 1 }, + { "px": [248,248], "src": [240,8], "f": 0, "t": 62, "d": [1519], "a": 1 }, + { "px": [256,248], "src": [240,8], "f": 0, "t": 62, "d": [1520], "a": 1 }, + { "px": [264,248], "src": [240,8], "f": 0, "t": 62, "d": [1521], "a": 1 }, + { "px": [272,248], "src": [240,8], "f": 0, "t": 62, "d": [1522], "a": 1 }, + { "px": [280,248], "src": [240,8], "f": 0, "t": 62, "d": [1523], "a": 1 }, + { "px": [288,248], "src": [240,8], "f": 0, "t": 62, "d": [1524], "a": 1 }, + { "px": [296,248], "src": [240,8], "f": 0, "t": 62, "d": [1525], "a": 1 }, + { "px": [304,248], "src": [240,8], "f": 0, "t": 62, "d": [1526], "a": 1 }, + { "px": [312,248], "src": [240,8], "f": 0, "t": 62, "d": [1527], "a": 1 }, + { "px": [320,248], "src": [240,8], "f": 0, "t": 62, "d": [1528], "a": 1 }, + { "px": [328,248], "src": [240,8], "f": 0, "t": 62, "d": [1529], "a": 1 }, + { "px": [336,248], "src": [240,8], "f": 0, "t": 62, "d": [1530], "a": 1 }, + { "px": [344,248], "src": [240,8], "f": 0, "t": 62, "d": [1531], "a": 1 }, + { "px": [352,248], "src": [240,8], "f": 0, "t": 62, "d": [1532], "a": 1 }, + { "px": [360,248], "src": [240,8], "f": 0, "t": 62, "d": [1533], "a": 1 }, + { "px": [368,248], "src": [240,8], "f": 0, "t": 62, "d": [1534], "a": 1 }, + { "px": [376,248], "src": [240,8], "f": 0, "t": 62, "d": [1535], "a": 1 } + ], + "entityInstances": [] + } + ], + "__neighbours": [{ "levelIid": "7b3b0490-8560-11f0-96f7-f3b9d5f9ff28", "dir": "w" }] + }, + { + "identifier": "Modal", + "iid": "df95a4d0-fa90-11f0-a6d7-d7fe08c1cbc4", + "uid": 102, + "worldX": 64, + "worldY": 288, + "worldDepth": 0, + "pxWid": 192, + "pxHei": 72, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 24, + "__cHei": 9, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "df95cbe0-fa90-11f0-a6d7-df94956ae0a3", + "levelId": 102, + "layerDefUid": 50, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 3467085, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Checkbox", + "__grid": [8,3], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 4, "x": 16, "y": 32, "w": 16, "h": 16 }, + "__smartColor": "#D77643", + "iid": "e6db8570-fa90-11f0-a6d7-1fea932b41fc", + "width": 16, + "height": 16, + "defUid": 45, + "px": [64,24], + "fieldInstances": [{ "__identifier": "Label", "__type": "String", "__value": "", "__tile": null, "defUid": 80, "realEditorValues": [] }], + "__worldX": 128, + "__worldY": 312 + }, + { + "__identifier": "Dropdown", + "__grid": [14,5], + "__pivot": [0,0], + "__tags": [], + "__tile": null, + "__smartColor": "#C0CBDC", + "iid": "09927e30-fa90-11f0-b13f-dda84ab1583c", + "width": 64, + "height": 8, + "defUid": 103, + "px": [112,40], + "fieldInstances": [{ "__identifier": "Options", "__type": "Array", "__value": [ "test", "test2", "test3" ], "__tile": null, "defUid": 104, "realEditorValues": [ { + "id": "V_String", + "params": ["test"] + }, { + "id": "V_String", + "params": ["test2"] + }, { + "id": "V_String", + "params": ["test3"] + } ] }], + "__worldX": 176, + "__worldY": 328 + } + ] + }, + { + "__identifier": "MenuLayer", + "__type": "AutoLayer", + "__cWid": 24, + "__cHei": 9, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "df95cbe1-fa90-11f0-a6d7-a362b72e4d03", + "levelId": 102, + "layerDefUid": 29, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 1531109, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "WidgetLayer", + "__type": "AutoLayer", + "__cWid": 24, + "__cHei": 9, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "df95cbe2-fa90-11f0-a6d7-1d00a1858ec8", + "levelId": 102, + "layerDefUid": 5, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 5627127, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "Menu", + "__type": "IntGrid", + "__cWid": 24, + "__cHei": 9, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "df95cbe3-fa90-11f0-a6d7-25ff2a97a69e", + "levelId": 102, + "layerDefUid": 28, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [ + 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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0 + ], + "autoLayerTiles": [], + "seed": 272050, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "WidgetBorder", + "__type": "IntGrid", + "__cWid": 24, + "__cHei": 9, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "df95cbe4-fa90-11f0-a6d7-f52cb3452344", + "levelId": 102, + "layerDefUid": 1, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [ + 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,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,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0 + ], + "autoLayerTiles": [], + "seed": 9078496, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 24, + "__cHei": 9, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 3, + "__tilesetRelPath": "sfx-spritesheet.png", + "iid": "df95cbe5-fa90-11f0-a6d7-f349956c468d", + "levelId": 102, + "layerDefUid": 27, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 7466376, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + } + ], "__neighbours": [] } ], diff --git a/tiny-cli/src/main/resources/sfx/sfx-editor.lua b/tiny-cli/src/main/resources/sfx/sfx-editor.lua index b5411ad1..f484a7ac 100644 --- a/tiny-cli/src/main/resources/sfx/sfx-editor.lua +++ b/tiny-cli/src/main/resources/sfx/sfx-editor.lua @@ -11,7 +11,6 @@ local m = { local green = 13 local red = 5 local white = 18 -local shadow = 2 function roundToHalf(num) local rounded_step = math.floor(num * 2) @@ -31,17 +30,10 @@ local State = { local state = new(State) -function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end +local modal = nil - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +local utils = require("widgets.utils") +inside_widget = utils.inside_widget function filter(widgetType) for w in all(m.widgets) do @@ -178,7 +170,7 @@ Player._update = function(self) end end - self.beat = math.clamp(0, self.beat, 32) + self.beat = math.clamp(0, self.beat, 15.5) self:set_value(self.beat) end @@ -412,11 +404,11 @@ function _init_sfx_editor(entities) -- wire.bind(state, "sfx.volume", volume, "value") local transform = { to_widget = function(to, from, value) - return (value - 60) / 360 + return (value - 60) / 520 end, from_widget = function(to, from, value) - return 60 + value * 360 + return 60 + value * 520 end } wire.bind(state, "sfx.bpm", bpm, "value", transform) @@ -497,6 +489,19 @@ function _init_mini_button(entities) end end +function _init_modal() + local modal_data = { + x = 96, + y = 92, + width = 192, + height = 72, + level_name = "Modal", + fields = {}, + } + modal = widgets:create_modal(modal_data) + table.insert(m.widgets, modal) +end + function _init() m.widgets = {} @@ -514,6 +519,7 @@ function _init() _init_velocity_editor(entities) _init_sfx_editor(entities) _init_mini_button(entities) + _init_modal() _init_player(entities) -- force setting correct values @@ -528,8 +534,20 @@ function _update() end, function() end) - for w in all(m.widgets) do - w:_update() + if ctrl.pressed(keys.m) then + if modal.visible then + modal:close() + else + modal:open() + end + end + + if modal.visible then + modal:_update() + else + for w in all(m.widgets) do + w:_update() + end end end diff --git a/tiny-cli/src/main/resources/sfx/sfx-spritesheet.png b/tiny-cli/src/main/resources/sfx/sfx-spritesheet.png index 4d7d13fb..48a3664b 100644 Binary files a/tiny-cli/src/main/resources/sfx/sfx-spritesheet.png and b/tiny-cli/src/main/resources/sfx/sfx-spritesheet.png differ diff --git a/tiny-cli/src/main/resources/sfx/sfx-templates.lua b/tiny-cli/src/main/resources/sfx/sfx-templates.lua new file mode 100644 index 00000000..f0ec572e --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/sfx-templates.lua @@ -0,0 +1,418 @@ +local M = {} + +-- Note names and musical scales +local note_names = { "C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B" } +local major_scale = { 0, 2, 4, 5, 7, 9, 11 } +local minor_scale = { 0, 2, 3, 5, 7, 8, 10 } +local major_triad = { 0, 4, 7 } + +-- Helper: pick a random element from a table +local function pick(t) + return t[math.random(1, #t)] +end + +-- Helper: random integer in [min, max] +local function rand_int(min, max) + return math.random(min, max) +end + +-- Helper: random float in [min, max] +local function rand_float(min, max) + return min + math.random() * (max - min) +end + +-- Helper: build a note name string from a semitone index (0-based from C0) +local function make_note(semitone) + semitone = math.max(0, math.min(semitone, 95)) + local octave = math.floor(semitone / 12) + local note_index = (semitone % 12) + 1 + return note_names[note_index] .. octave +end + +-- Helper: find an instrument whose wave matches one of the compatible types +local function find_instrument(compatible_waves) + local candidates = {} + for i = 0, 7 do + local inst = sfx.instrument(i) + if inst then + for _, w in ipairs(compatible_waves) do + if inst.wave == w then + table.insert(candidates, i) + break + end + end + end + end + if #candidates > 0 then + return pick(candidates) + end + return 0 +end + +-- Helper: clear all notes from an SFX bar +local function clear_notes(sfx_bar) + local notes = sfx_bar.notes + for i = #notes, 1, -1 do + local n = notes[i] + sfx_bar.remove_note({ beat = n.beat, note = n.note }) + end +end + +-- Template definitions +M.definitions = {} + +-- 1. Shoot: fast descending chromatic, short durations, fading volume +M.definitions["Shoot"] = { + bpm = { 300, 400 }, + waves = { "NOISE", "SQUARE", "SAW_TOOTH" }, + generate = function(sfx_bar, inst) + local count = rand_int(4, 8) + local start_semi = rand_int(48, 72) + local beat = 0 + for i = 1, count do + local semi = start_semi - (i - 1) * rand_int(1, 3) + local vol = 1.0 - (i - 1) / count * 0.7 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- 2. Jump: ascending whole-tone steps +M.definitions["Jump"] = { + bpm = { 240, 360 }, + waves = { "SQUARE", "TRIANGLE", "PULSE" }, + generate = function(sfx_bar, inst) + local count = rand_int(3, 6) + local start_semi = rand_int(36, 48) + local beat = 0 + for i = 1, count do + local semi = start_semi + (i - 1) * 2 + local vol = 0.6 + (i - 1) / count * 0.4 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- 3. Confirm: 2-3 ascending major intervals +M.definitions["Confirm"] = { + bpm = { 180, 240 }, + waves = { "TRIANGLE", "SINE", "SQUARE" }, + generate = function(sfx_bar, inst) + local count = rand_int(2, 3) + local root = rand_int(48, 60) + local intervals = { 0, 4, 7 } + local beat = 0 + for i = 1, count do + local semi = root + intervals[i] + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.8 }) + beat = beat + 0.5 + end + end, +} + +-- 4. Coin: quick ascending major triad arpeggio +M.definitions["Coin"] = { + bpm = { 300, 400 }, + waves = { "TRIANGLE", "SINE" }, + generate = function(sfx_bar, inst) + local count = rand_int(3, 5) + local root = rand_int(60, 72) + local beat = 0 + for i = 1, count do + local triad_index = ((i - 1) % #major_triad) + 1 + local octave_offset = math.floor((i - 1) / #major_triad) * 12 + local semi = root + major_triad[triad_index] + octave_offset + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.9 }) + beat = beat + 0.5 + end + end, +} + +-- 5. Hit: sharp descending notes, high volume +M.definitions["Hit"] = { + bpm = { 240, 360 }, + waves = { "NOISE", "DRUM" }, + generate = function(sfx_bar, inst) + local count = rand_int(1, 3) + local start_semi = rand_int(48, 60) + local beat = 0 + for i = 1, count do + local semi = start_semi - (i - 1) * rand_int(4, 8) + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 1.0 }) + beat = beat + 0.5 + end + end, +} + +-- 6. Door Open: ascending stepped, longer duration +M.definitions["Door Open"] = { + bpm = { 120, 180 }, + waves = { "SQUARE", "PULSE", "TRIANGLE" }, + generate = function(sfx_bar, inst) + local count = rand_int(3, 5) + local root = rand_int(36, 48) + local beat = 0 + for i = 1, count do + local scale_index = ((i - 1) % #major_scale) + 1 + local semi = root + major_scale[scale_index] + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 1.0, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.7 }) + beat = beat + 1.0 + end + end, +} + +-- 7. Door Close: descending stepped, longer duration +M.definitions["Door Close"] = { + bpm = { 120, 180 }, + waves = { "SQUARE", "PULSE", "TRIANGLE" }, + generate = function(sfx_bar, inst) + local count = rand_int(3, 5) + local root = rand_int(48, 60) + local beat = 0 + for i = 1, count do + local scale_index = ((count - i) % #major_scale) + 1 + local semi = root + major_scale[scale_index] + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 1.0, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.7 }) + beat = beat + 1.0 + end + end, +} + +-- 8. Win: ascending major scale fanfare +M.definitions["Win"] = { + bpm = { 160, 220 }, + waves = { "TRIANGLE", "SQUARE", "SAW_TOOTH" }, + generate = function(sfx_bar, inst) + local count = rand_int(5, 8) + local root = rand_int(48, 60) + local beat = 0 + for i = 1, count do + local scale_index = ((i - 1) % #major_scale) + 1 + local octave_offset = math.floor((i - 1) / #major_scale) * 12 + local semi = root + major_scale[scale_index] + octave_offset + local vol = 0.6 + (i - 1) / count * 0.4 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- 9. Lose: descending minor scale, fading +M.definitions["Lose"] = { + bpm = { 100, 160 }, + waves = { "SINE", "TRIANGLE" }, + generate = function(sfx_bar, inst) + local count = rand_int(3, 5) + local root = rand_int(48, 60) + local beat = 0 + for i = 1, count do + local scale_index = ((count - i) % #minor_scale) + 1 + local semi = root + minor_scale[scale_index] + local vol = 1.0 - (i - 1) / count * 0.6 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 1.0, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 1.0 + end + end, +} + +-- 10. Explosion: random noise bursts, varied volume +M.definitions["Explosion"] = { + bpm = { 240, 400 }, + waves = { "NOISE", "DRUM" }, + generate = function(sfx_bar, inst) + local count = rand_int(6, 12) + local beat = 0 + for i = 1, count do + local semi = rand_int(24, 48) + local vol = rand_float(0.5, 1.0) + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- 11. Power Up: ascending chromatic sweep +M.definitions["Power Up"] = { + bpm = { 200, 300 }, + waves = { "SAW_TOOTH", "SQUARE", "PULSE" }, + generate = function(sfx_bar, inst) + local count = rand_int(6, 10) + local start_semi = rand_int(36, 48) + local beat = 0 + for i = 1, count do + local semi = start_semi + (i - 1) + local vol = 0.5 + (i - 1) / count * 0.5 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- 12. Power Down: descending chromatic sweep +M.definitions["Power Down"] = { + bpm = { 200, 300 }, + waves = { "SAW_TOOTH", "SQUARE", "PULSE" }, + generate = function(sfx_bar, inst) + local count = rand_int(6, 10) + local start_semi = rand_int(48, 72) + local beat = 0 + for i = 1, count do + local semi = start_semi - (i - 1) + local vol = 1.0 - (i - 1) / count * 0.5 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- 13. Menu Select: 1-2 short high blips +M.definitions["Menu Select"] = { + bpm = { 200, 300 }, + waves = { "TRIANGLE", "SINE", "SQUARE" }, + generate = function(sfx_bar, inst) + local count = rand_int(1, 2) + local root = rand_int(60, 72) + local beat = 0 + for i = 1, count do + local semi = root + (i - 1) * rand_int(3, 5) + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.7 }) + beat = beat + 0.5 + end + end, +} + +-- 14. Menu Back: 1-2 short descending blips +M.definitions["Menu Back"] = { + bpm = { 200, 300 }, + waves = { "TRIANGLE", "SINE" }, + generate = function(sfx_bar, inst) + local count = rand_int(1, 2) + local root = rand_int(60, 72) + local beat = 0 + for i = 1, count do + local semi = root - (i - 1) * rand_int(3, 5) + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.7 }) + beat = beat + 0.5 + end + end, +} + +-- 15. Footstep: 1-2 very short percussive notes +M.definitions["Footstep"] = { + bpm = { 120, 180 }, + waves = { "NOISE", "DRUM" }, + generate = function(sfx_bar, inst) + local count = rand_int(1, 2) + local beat = 0 + for i = 1, count do + local semi = rand_int(24, 36) + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = 0.6 }) + beat = beat + 0.5 + end + end, +} + +-- 16. Laser: fast descending chromatic +M.definitions["Laser"] = { + bpm = { 300, 480 }, + waves = { "SAW_TOOTH", "PULSE", "SQUARE" }, + generate = function(sfx_bar, inst) + local count = rand_int(6, 12) + local start_semi = rand_int(60, 84) + local beat = 0 + for i = 1, count do + local semi = start_semi - (i - 1) * rand_int(1, 2) + local vol = 0.9 - (i - 1) / count * 0.4 + sfx_bar.set_note({ beat = beat, note = make_note(semi), duration = 0.5, unique = true }) + sfx_bar.set_volume({ beat = beat, volume = vol }) + beat = beat + 0.5 + end + end, +} + +-- Ordered list for dropdown display +M.list = { + "Shoot", "Jump", "Confirm", "Coin", + "Hit", "Door Open", "Door Close", "Win", + "Lose", "Explosion", "Power Up", "Power Down", + "Menu Select", "Menu Back", "Footstep", "Laser", +} + +-- Main generate function +M.generate = function(sfx_bar, template_name) + local def = M.definitions[template_name] + if not def then + return + end + + -- Clear existing notes + clear_notes(sfx_bar) + + -- Set BPM + sfx_bar.bpm = rand_int(def.bpm[1], def.bpm[2]) + + -- Find a compatible instrument + local inst_index = find_instrument(def.waves) + sfx_bar.set_instrument(inst_index) + + -- Generate notes + def.generate(sfx_bar, inst_index) + + -- Clamp notes to span at most 2 octaves + local notes = sfx_bar.notes + if #notes > 0 then + local min_octave = 7 + local max_octave = 0 + for note in all(notes) do + if note.octave < min_octave then min_octave = note.octave end + if note.octave > max_octave then max_octave = note.octave end + end + + if max_octave - min_octave > 1 then + local saved = {} + for note in all(notes) do + table.insert(saved, { + beat = note.beat, + note = note.note, + octave = note.octave, + duration = note.duration, + volume = note.volume, + }) + end + clear_notes(sfx_bar) + for _, note in ipairs(saved) do + local clamped_octave = math.clamp(min_octave, note.octave, min_octave + 1) + local pitch_class = string.sub(note.note, 1, #note.note - 1) + local new_note_name = pitch_class .. clamped_octave + sfx_bar.set_note({ + beat = note.beat, + note = new_note_name, + duration = note.duration, + unique = true, + }) + sfx_bar.set_volume({ beat = note.beat, volume = note.volume }) + end + end + + return min_octave + end +end + +return M diff --git a/tiny-cli/src/main/resources/sfx/sprite-sheet.png b/tiny-cli/src/main/resources/sfx/sprite-sheet.png new file mode 100644 index 00000000..bbb5392d Binary files /dev/null and b/tiny-cli/src/main/resources/sfx/sprite-sheet.png differ diff --git a/tiny-cli/src/main/resources/sfx/test-game.lua b/tiny-cli/src/main/resources/sfx/test-game.lua deleted file mode 100644 index 74def5d2..00000000 --- a/tiny-cli/src/main/resources/sfx/test-game.lua +++ /dev/null @@ -1,579 +0,0 @@ -local widgets = require("widgets") -local mouse = require("mouse") - -local menu = {} -local help = nil - --- name to level index -local mode = { - score = { - id = 1, - widgets = {} - }, - fx = { - id = 2, - widgets = {} - }, - music = { - id = 3, - widgets = {} - } -} - -local button_type = { - Sine = { - spr = 60, - color = 9 - }, - Noise = { - spr = 61, - color = 4 - }, - Triangle = { - spr = 63, - color = 13 - }, - Pulse = { - spr = 62, - color = 10 - }, - Square = { - spr = 62, - color = 11 - }, - Silence = { - spr = 62, - color = 2 - }, - Play = { - spr = 31 - }, - Prev = { - spr = 32 * 5 + 28 - }, - Next = { - spr = 32 * 5 + 29 - }, - PatternId = { - spr = 32 * 5 + 29 + 16 - } -} - --- from a content, set the correct values in the score panel -mode.score.configure = function(self, content) - local content = self.file_selector:current() - - debug.console(content) - for index, note in ipairs(content.tracks[1].patterns[1]) do - if (note.type == "Silence") then - self.sound.notes[index]:set_value(0) - self.sound.volumes[index]:set_value(0) - else - self.sound.selector.selected = note.type - self.sound.notes[index]:set_value(note.note / 107) - self.sound.notes[index].tip_color = button_type[note.type].color - self.sound.volumes[index]:set_value(note.volume / 255) - end - end - - self.sound.bpm.value = content.bpm / 255 - self.sound.volume.value = content.volume / 255 -end - --- from a content, set the correct values in the fx pannel -mode.fx.configure = function(self, content) - local content = self.file_selector:current() - - local mod = content.tracks[1].mod - if mod.type == 1 then - self.fx.sweep.checkbox.value = true - self.fx.sweep.enabled = true - self.fx.sweep.sweep = mod.a / 107 - self.fx.sweep.acceleration = mod.b / 255 - self.fx.sweep.knob_sweep.value = self.fx.sweep.sweep - self.fx.sweep.knob_acceleration.value = self.fx.sweep.acceleration - elseif mod.type == 2 then - self.fx.vibrato.checkbox.value = true - self.fx.vibrato.enabled = true - self.fx.vibrato.vibrato = mod.a / 107 - self.fx.vibrato.depth = mod.b / 255 - self.fx.vibrato.knob_vibrato.value = self.fx.vibrato.vibrato - self.fx.vibrato.knob_depth.value = self.fx.vibrato.depth - end - - local env = content.tracks[1].env - if env ~= nil then - self.fx.envelope.attack_fader:set_value(env.attack / 255) - self.fx.envelope.decay_fader:set_value(env.decay / 255) - self.fx.envelope.sustain_fader:set_value(env.sustain / 255) - self.fx.envelope.release_fader:set_value(env.release / 255) - end -end - -local current_mode = mode.score - -function switch_to(new_mode) - current_mode = new_mode - if current_mode.configure ~= nil then - current_mode:configure() - end -end - -local find_widget = function(widgets, ref) - for w in all(widgets) do - if w.iid == ref.entityIid then - return w - end - end -end - -function _init() - help = nil - menu = {} - - local on_click = { - Wave = function(self) - switch_to(mode.score) - end, - Fx = function(self) - switch_to(mode.fx) - end, - Music = function(self) - switch_to(mode.music) - end - } - - for i in all(map.entities()["MenuItem"]) do - if i.fields.Item == "Help" then - local w = widgets:create_help(i) - table.insert(menu, w) - help = w - end - end - - local on_menu_item_hover = function(self) - help.label = self.help - end - - for i in all(map.entities()["MenuItem"]) do - local w = widgets:create_menu_item(i) - table.insert(menu, w) - w.on_hover = on_menu_item_hover - w:on_update(on_click[i.fields.Item]) - if i.fields.Item == "Wave" then - w.active = 1 - end - end - - local FileSelector = { - current_file = 1, - files = {}, -- item -> {file, content} - screen = nil, - current = function(self) - return self.files[self.current_file].content - end, - currentName = function(self) - return self.files[self.current_file].file - end - } - local file_selector = nil - - for i in all(map.entities()["FilesSelector"]) do - file_selector = new(FileSelector, i) - local files = ws.list("sfx") - table.sort(files) - if #files == 0 then - local new_file = ws.create("sfx", "sfx") - table.insert(file_selector.files, { - file = new_file, - content = sfx.to_table(sfx.empty_score()) - }) - else - for f in all(files) do - debug.console(f) - table.insert(file_selector.files, { - file = f, - content = sfx.to_table(ws.load(f)) - }) - end - end - - file_selector.next = find_widget(menu, file_selector.fields.Next) - file_selector.previous = find_widget(menu, file_selector.fields.Previous) - file_selector.screen = find_widget(menu, file_selector.fields.Screen) - file_selector.save = find_widget(menu, file_selector.fields.Save) - file_selector.new_file = find_widget(menu, file_selector.fields.NewFile) - file_selector.screen.label = true - - file_selector.next:on_update(function(self) - file_selector.current_file = math.min(#file_selector.files, file_selector.current_file + 1) - file_selector.screen:set_value(file_selector.files[file_selector.current_file].file) - switch_to(current_mode) - end) - - file_selector.previous:on_update(function(self) - file_selector.current_file = math.max(1, file_selector.current_file - 1) - file_selector.screen:set_value(file_selector.files[file_selector.current_file].file) - switch_to(current_mode) - end) - - file_selector.new_file:on_update(function(self) - debug.console("creating file") - local new_file = ws.create("sfx", "sfx") - table.insert(file_selector.files, { - file = new_file, - content = sfx.to_table(sfx.empty_score()) - }) - file_selector.current_file = #file_selector.files - file_selector.next:set_value() - end) - - file_selector.save:on_update(function(self) - debug.console("saving file...") - local score = sfx.to_score(file_selector:current()) - debug.console(file_selector:current()) - debug.console(score) - ws.save(file_selector:currentName(), score) - debug.console("file saved!") -- - end) - file_selector.screen:set_value(file_selector.files[file_selector.current_file].file) - end - - -- preload mode - for name, m in pairs(mode) do - m.file_selector = file_selector - - debug.console("preload screen " .. name) - map.level(m.id) - for k in all(map.entities()["Knob"]) do - local knob = widgets:create_knob(k) - knob.on_hover = on_menu_item_hover - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["Button"]) do - local knob = widgets:create_button(k) - knob.on_hover = on_menu_item_hover - knob.overlay = button_type[k.fields.Type].spr - knob.type = k.fields.Type - - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["Fader"]) do - local knob = widgets:create_fader(k) - knob.on_hover = on_menu_item_hover - knob.id = k.fields.Id - knob.type = k.fields.Type - -- knob.on_value_update - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["Checkbox"]) do - local knob = widgets:create_checkbox(k) - knob.on_hover = on_menu_item_hover - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["Envelop"]) do - local knob = widgets:create_envelop(k) - knob.on_hover = on_menu_item_hover - local f = find_widget(m.widgets, knob.fields.Attack) - knob.attack_fader = f - local on_value_update = function(self, value) - knob.attack = value - local content = file_selector:current() - content.tracks[1].env.attack = value * 255 - end - f:on_update(on_value_update) - - f = find_widget(m.widgets, knob.fields.Decay) - knob.decay_fader = f - local on_value_update = function(self, value) - knob.decay = value - local content = file_selector:current() - content.tracks[1].env.decay = value * 255 - end - f:on_update(on_value_update) - - f = find_widget(m.widgets, knob.fields.Sustain) - knob.sustain_fader = f - local on_value_update = function(self, value) - knob.sustain = value - local content = file_selector:current() - content.tracks[1].env.sustain = value * 255 - end - f:on_update(on_value_update) - - f = find_widget(m.widgets, knob.fields.Release) - knob.release_fader = f - local on_value_update = function(self, value) - knob.release = value - local content = file_selector:current() - content.tracks[1].env.release = value * 255 - end - f:on_update(on_value_update) - - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["Vibrato"]) do - local Vibrato = { - enabled = false, - vibrato = 0, - depth = 0, - _update = function(self) - end, - _draw = function(self) - end, - switch = function(self, active) - self.enabled = active - self.checkbox.value = active - if active then - local content = file_selector:current() - content.tracks[1].mod.type = 2 - content.tracks[1].mod.a = self.vibrato * 107 - content.tracks[1].mod.b = self.depth * 255 - end - end - } - local knob = new(Vibrato, k) - local e = find_widget(m.widgets, knob.fields.Enabled) - e.on_changed = function(self, value) - knob.enabled = value - end - knob.checkbox = e - - local v = find_widget(m.widgets, knob.fields.Vibrato) - knob.knob_vibrato = v - v.on_update = function(self, value) - knob.vibrato = value - local content = file_selector:current() - content.tracks[1].mod.a = knob.vibrato * 107 - end - local d = find_widget(m.widgets, knob.fields.Depth) - knob.knob_depth = d - d.on_update = function(self, value) - knob.depth = value - local content = file_selector:current() - content.tracks[1].mod.b = knob.depth * 255 - end - - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["Sweep"]) do - local Sweep = { - enabled = false, - sweep = 0, - acceleration = 0, - _update = function(self) - end, - _draw = function(self) - end, - - switch = function(self, active) - self.enabled = active - self.checkbox.value = active - if active then - local content = file_selector:current() - content.tracks[1].mod.type = 1 - content.tracks[1].mod.a = self.sweep * 107 - content.tracks[1].mod.b = self.acceleration * 255 - end - end - } - local knob = new(Sweep, k) - local e = find_widget(m.widgets, knob.fields.Enabled) - knob.checkbox = e - e.on_changed = function(self, value) - knob.enabled = value - end - - local v = find_widget(m.widgets, knob.fields.Sweep) - knob.knob_sweep = v - v.on_update = function(self, value) - knob.sweep = value - local content = file_selector:current() - content.tracks[1].mod.a = knob.sweep * 107 - end - local d = find_widget(m.widgets, knob.fields.Acceleration) - knob.knob_acceleration = d - d.on_update = function(self, value) - knob.acceleration = value - local content = file_selector:current() - content.tracks[1].mod.b = knob.acceleration * 255 - end - - table.insert(m.widgets, knob) - end - - for k in all(map.entities()["WaveSelector"]) do - local WaveSelector = { - selected = "Sine", - selector = {}, - _update = function(self) - end, - _draw = function(self) - end - } - local knob = new(WaveSelector, k) - local on_update = function(self) - knob.selected = self.type - for b in all(knob.selector) do - b.status = 0 - end - self.status = 2 - end - - local e = find_widget(m.widgets, knob.fields.Sine) - table.insert(knob.selector, e) - e:on_update(on_update) - on_update(e) -- default selection - - e = find_widget(m.widgets, knob.fields.Triangle) - table.insert(knob.selector, e) - e:on_update(on_update) - - e = find_widget(m.widgets, knob.fields.Noise) - table.insert(knob.selector, e) - e:on_update(on_update) - - e = find_widget(m.widgets, knob.fields.Pulse) - table.insert(knob.selector, e) - e:on_update(on_update) - - table.insert(m.widgets, knob) - end - - local play = function(self) - local content = file_selector:current() - local score = sfx.to_score(content) - sfx.sfx(score) - end - - for k in all(map.entities()["Sound"]) do - local Sound = { - volumes = {}, - notes = {}, - _draw = function(self) - end, - _update = function(self) - end - } - local s = new(Sound, k) - local selector = find_widget(m.widgets, k.fields.WaveSelector) - for key, v in ipairs(k.fields.Volumes) do - local f = find_widget(m.widgets, v) - s.volumes[key] = f - local on_update = function(self, value) - local content = file_selector:current() - content.tracks[1].patterns[1][key].volume = value * 255 - end - f:on_update(on_update) - end - - for key, v in ipairs(k.fields.Notes) do - local f = find_widget(m.widgets, v) - s.notes[key] = f - local on_update = function(self, value) - self.tip_color = button_type[selector.selected].color - local content = file_selector:current() - content.tracks[1].patterns[1][key].type = selector.selected - content.tracks[1].patterns[1][key].note = value * 107 -- 107 = number of total notes - - if content.tracks[1].patterns[1][key].volume <= 0 then - s.volumes[key]:set_value(1) - end - end - - f:on_update(on_update) - end - s.bpm = find_widget(m.widgets, k.fields.BPM) - s.bpm.on_update = function(self) - local content = file_selector:current() - -- TODO: update here - content.bpm = self.value * 255 - end - s.volume = find_widget(m.widgets, k.fields.Volume) - s.volume.on_update = function(self) - local content = file_selector:current() - content.volume = self.value * 255 - end - - s.play = find_widget(m.widgets, k.fields.Play) - s.play:on_update(play) - s.selector = selector - m.sound = s - end - - for k in all(map.entities()["Fx"]) do - local Fx = { - envelope = nil, - sweep = nil, - vibrato = nil, - tied_notes = nil - } - - local fx = new(Fx, k) - fx.envelope = find_widget(m.widgets, k.fields.Envelope) - fx.sweep = find_widget(m.widgets, k.fields.Sweep) - fx.vibrato = find_widget(m.widgets, k.fields.Vibrato) - - fx.sweep.checkbox.on_changed = function(self) - fx.sweep:switch(self.value) - fx.vibrato:switch(not self.value) - end - fx.vibrato.checkbox.on_changed = function(self) - fx.sweep:switch(not self.value) - fx.vibrato:switch(self.value) - end - fx.tied_notes = find_widget(m.widgets, k.fields.Envelope) - fx.play = find_widget(m.widgets, k.fields.Play) - fx.play:on_update(play) - m.fx = fx - end - - for k in all(map.entities()["PatternSelector"]) do - end - end - - switch_to(mode.score) -end - -function _update() - mouse._update(function() - end, function() - end, function() - end) - - for w in all(menu) do - w:_update() - end - - help:_update() - - for w in all(current_mode.widgets) do - w:_update() - end -end - -function _draw() - gfx.cls() - - map.level(0) - map.draw() - map.level(current_mode.id) - map.layer(1) - map.draw() - - for w in all(menu) do - w:_draw() - end - help:_draw() - - for w in all(current_mode.widgets) do - w:_draw() - end - mouse._draw(2) -end diff --git a/tiny-cli/src/main/resources/sfx/tiny-instrument-editor.lua b/tiny-cli/src/main/resources/sfx/tiny-instrument-editor.lua new file mode 100644 index 00000000..48b7b841 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/tiny-instrument-editor.lua @@ -0,0 +1,271 @@ +local widgets = require("widgets") +local wire = require("wire") +local EditorBase = require("editor-base") +local LayerManager = require("layers") + +local all_widgets = {} +local modals_by_name = {} +local dropdown_widget = nil +local speaker_widgets = {} +local layer_manager = nil + +local save_state = nil +local save_button_ref = nil + +local state = { + instrument = nil, + next_note_on = nil, + next_note_off = nil, +} + +local set_speakers_playing = function(playing) + for _, s in ipairs(speaker_widgets) do + s.playing = playing + end +end + +local on_press = function() + state.instrument.note_on("C4") + set_speakers_playing(true) +end + +local on_release = function() + state.instrument.note_off("C4") + set_speakers_playing(false) +end + +local on_press_repeat = function() + state.next_note_on = 0 + state.next_note_off = nil + set_speakers_playing(true) +end + +local on_release_repeat = function() + state.next_note_on = nil + set_speakers_playing(false) +end + +local on_repeat_update = function() + if state.next_note_off then + state.next_note_off = state.next_note_off - tiny.dt + if state.next_note_off < 0 then + state.instrument.note_off("C4") + state.next_note_off = nil + + if state.next_note_on then + state.next_note_on = state.instrument.release + tiny.dt + end + end + end + + if state.next_note_on and state.next_note_on >= 0 then + state.next_note_on = state.next_note_on - tiny.dt + if state.next_note_on < 0 then + state.instrument.note_on("C4") + state.next_note_off = state.instrument.attack + state.instrument.decay + end + end +end + +function _init_knobs(entities) + for k in all(entities["Knob"]) do + local knob = widgets:create_knob(k) + table.insert(all_widgets, knob) + end +end + +function _init_faders(entities) + for f in all(entities["Fader"]) do + local fader = widgets:create_fader(f) + table.insert(all_widgets, fader) + end +end + +function _init_envelop(entities) + for k in all(entities["Envelope"]) do + local envelop = widgets:create_envelop(k) + + local widget = wire.find_widget(all_widgets, envelop.fields.Attack) + widget.on_press = on_press_repeat + widget.on_release = on_release_repeat + wire.bind(state, "instrument.attack", widget, "value") + wire.bind(state, "instrument.attack", envelop, "attack") + + widget = wire.find_widget(all_widgets, envelop.fields.Decay) + widget.on_press = on_press_repeat + widget.on_release = on_release_repeat + wire.bind(state, "instrument.decay", widget, "value") + wire.bind(state, "instrument.decay", envelop, "decay") + + widget = wire.find_widget(all_widgets, envelop.fields.Sustain) + widget.on_press = on_press + widget.on_release = on_release + wire.bind(state, "instrument.sustain", widget, "value") + wire.bind(state, "instrument.sustain", envelop, "sustain") + + widget = wire.find_widget(all_widgets, envelop.fields.Release) + widget.on_press = on_press_repeat + widget.on_release = on_release_repeat + wire.bind(state, "instrument.release", widget, "value") + wire.bind(state, "instrument.release", envelop, "release") + + table.insert(all_widgets, envelop) + end +end + +function _init_harmonics(entities) + for mode in all(entities["Harmonics"]) do + for index, harmonic in ipairs(mode.fields.Harmonics) do + local fader = wire.find_widget(all_widgets, harmonic) + fader.on_press = on_press + fader.on_release = on_release + wire.bind(state, "instrument.harmonics." .. index, fader, "value") + end + end +end + +function _init_wave_type(entities) + local buttonToWave = function(wave_type) + return { + from_widget = function(source, target, value) + return wave_type + end, + to_widget = function(source, target, value) + if value == wave_type then + return 2 + else + return 0 + end + end, + } + end + + for b in all(entities["WaveTypeSelector"]) do + local sine = wire.find_widget(all_widgets, b.fields.Sine) + wire.bind(state, "instrument.wave", sine, "status", buttonToWave("SINE")) + local square = wire.find_widget(all_widgets, b.fields.Square) + wire.bind(state, "instrument.wave", square, "status", buttonToWave("SQUARE")) + local pulse = wire.find_widget(all_widgets, b.fields.Pulse) + wire.bind(state, "instrument.wave", pulse, "status", buttonToWave("PULSE")) + local triangle = wire.find_widget(all_widgets, b.fields.Triangle) + wire.bind(state, "instrument.wave", triangle, "status", buttonToWave("TRIANGLE")) + local noise = wire.find_widget(all_widgets, b.fields.Noise) + wire.bind(state, "instrument.wave", noise, "status", buttonToWave("NOISE")) + local sawtooth = wire.find_widget(all_widgets, b.fields.Sawtooth) + wire.bind(state, "instrument.wave", sawtooth, "status", buttonToWave("SAW_TOOTH")) + local drum = wire.find_widget(all_widgets, b.fields.Drum) + wire.bind(state, "instrument.wave", drum, "status", buttonToWave("DRUM")) + end +end + +function _init_keyboard(entities) + local currentNote + local playNote = function(_, value) + if value and currentNote == nil then + state.instrument.note_on(value) + currentNote = value + set_speakers_playing(true) + elseif value and currentNote ~= nil then + state.instrument.note_on(value) + state.instrument.note_off(currentNote) + currentNote = value + elseif not value then + state.instrument.note_off(currentNote) + currentNote = nil + set_speakers_playing(false) + end + end + + for k in all(entities["Keyboard"]) do + local keyboard = widgets:create_keyboard(k) + wire.listen(keyboard, "value", playNote) + table.insert(all_widgets, keyboard) + end +end + +function _init() + all_widgets = {} + modals_by_name = {} + dropdown_widget = nil + speaker_widgets = {} + layer_manager = nil + save_state = nil + save_button_ref = nil + + map.level("InstrumentEditor") + state.instrument = sfx.instrument(0) + + -- Panels first (drawn behind everything) + local panel_entities = map.entities("Panels") + EditorBase.init_panels(panel_entities, all_widgets) + + -- Then all interactive widgets + local widget_entities = map.entities("Widgets") + + local buttons_by_action = EditorBase.init_text_buttons(widget_entities, all_widgets) + save_button_ref = buttons_by_action["Save"] + + EditorBase.init_speakers(widget_entities, all_widgets, speaker_widgets) + _init_knobs(widget_entities) + _init_faders(widget_entities) + + modals_by_name = EditorBase.init_buttons(widget_entities, all_widgets, { + on_open = function() return state.instrument.name end, + on_name_validate = function(value) + if value and state.instrument then + state.instrument.name = value + EditorBase.update_dropdown_name(dropdown_widget, value) + end + end, + }) + + _init_wave_type(widget_entities) + + dropdown_widget = EditorBase.init_entity_dropdown(widget_entities, all_widgets, { + count = 8, + fetch = function(i) return sfx.instrument(i) end, + label = "Instrument", + on_select = function(index) state.instrument = sfx.instrument(index) end, + layer_manager = nil, -- set after layer_manager is created + }) + + _init_keyboard(widget_entities) + _init_envelop(widget_entities) + _init_harmonics(widget_entities) + + layer_manager = LayerManager.create() + layer_manager:register("Widgets", { tiles = nil, widgets = all_widgets, always = true }) + + -- Wire dropdown overlay after layer_manager is created + if dropdown_widget then + local original_update = dropdown_widget._update + dropdown_widget._update = function(self) + local was_open = self.open + original_update(self) + if self.open and not was_open then + layer_manager:set_overlay(self) + elseif not self.open and was_open then + layer_manager:set_overlay(nil) + end + end + end + + save_state = EditorBase.init_save_reminder(all_widgets, save_button_ref, modals_by_name) +end + +function _update() + EditorBase.update(modals_by_name, function() + layer_manager:update_widgets() + end) + + EditorBase.update_save_reminder(save_button_ref, save_state) + + on_repeat_update() +end + +function _draw() + EditorBase.draw(function() + layer_manager:draw_base() + layer_manager:draw_active() + end, modals_by_name) +end diff --git a/tiny-cli/src/main/resources/sfx/tiny-music-editor.ldtk b/tiny-cli/src/main/resources/sfx/tiny-music-editor.ldtk new file mode 100644 index 00000000..43df20d1 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/tiny-music-editor.ldtk @@ -0,0 +1,11524 @@ +{ + "__header__": { + "fileType": "LDtk Project JSON", + "app": "LDtk", + "doc": "https://ldtk.io/json", + "schema": "https://ldtk.io/files/JSON_SCHEMA.json", + "appAuthor": "Sebastien 'deepnight' Benard", + "appVersion": "1.5.3", + "url": "https://ldtk.io" + }, + "iid": "4cf47920-fa90-11f0-ad09-7b5a63ac610d", + "jsonVersion": "1.5.3", + "appBuildId": 473703, + "nextUid": 106, + "identifierStyle": "Capitalize", + "toc": [], + "worldLayout": "Free", + "worldGridWidth": 256, + "worldGridHeight": 256, + "defaultLevelWidth": 384, + "defaultLevelHeight": 256, + "defaultPivotX": 0, + "defaultPivotY": 0, + "defaultGridSize": 16, + "defaultEntityWidth": 16, + "defaultEntityHeight": 16, + "bgColor": "#40465B", + "defaultLevelBgColor": "#696A79", + "minifyJson": false, + "externalLevels": false, + "exportTiled": false, + "simplifiedExport": false, + "imageExportMode": "None", + "exportLevelBg": true, + "pngFilePattern": null, + "backupOnSave": false, + "backupLimit": 10, + "backupRelPath": null, + "levelNamePattern": "Level_%idx", + "tutorialDesc": null, + "customCommands": [], + "flags": [], + "defs": { "layers": [ + { + "__type": "Entities", + "identifier": "Widgets", + "type": "Entities", + "uid": 6, + "doc": null, + "uiColor": null, + "gridSize": 8, + "guideGridWid": 0, + "guideGridHei": 0, + "displayOpacity": 1, + "inactiveOpacity": 0.6, + "hideInList": false, + "hideFieldsWhenInactive": true, + "canSelectWhenInactive": true, + "renderInWorldView": true, + "pxOffsetX": 0, + "pxOffsetY": 0, + "parallaxFactorX": 0, + "parallaxFactorY": 0, + "parallaxScaling": true, + "requiredTags": [], + "excludedTags": [], + "autoTilesKilledByOtherLayerUid": null, + "uiFilterTags": [], + "useAsyncRender": false, + "intGridValues": [], + "intGridValuesGroups": [], + "autoRuleGroups": [], + "autoSourceLayerDefUid": null, + "tilesetDefUid": null, + "tilePivotX": 0, + "tilePivotY": 0, + "biomeFieldUid": null + }, + { + "__type": "Entities", + "identifier": "Panels", + "type": "Entities", + "uid": 75, + "doc": null, + "uiColor": null, + "gridSize": 8, + "guideGridWid": 0, + "guideGridHei": 0, + "displayOpacity": 1, + "inactiveOpacity": 0.6, + "hideInList": false, + "hideFieldsWhenInactive": true, + "canSelectWhenInactive": true, + "renderInWorldView": true, + "pxOffsetX": 0, + "pxOffsetY": 0, + "parallaxFactorX": 0, + "parallaxFactorY": 0, + "parallaxScaling": true, + "requiredTags": ["panel"], + "excludedTags": [], + "autoTilesKilledByOtherLayerUid": null, + "uiFilterTags": [], + "useAsyncRender": false, + "intGridValues": [], + "intGridValuesGroups": [], + "autoRuleGroups": [], + "autoSourceLayerDefUid": null, + "tilesetDefUid": null, + "tilePivotX": 0, + "tilePivotY": 0, + "biomeFieldUid": null + }, + { + "__type": "Tiles", + "identifier": "Background", + "type": "Tiles", + "uid": 10, + "doc": null, + "uiColor": null, + "gridSize": 8, + "guideGridWid": 0, + "guideGridHei": 0, + "displayOpacity": 1, + "inactiveOpacity": 1, + "hideInList": false, + "hideFieldsWhenInactive": false, + "canSelectWhenInactive": true, + "renderInWorldView": true, + "pxOffsetX": 0, + "pxOffsetY": 0, + "parallaxFactorX": 0, + "parallaxFactorY": 0, + "parallaxScaling": true, + "requiredTags": [], + "excludedTags": [], + "autoTilesKilledByOtherLayerUid": null, + "uiFilterTags": [], + "useAsyncRender": false, + "intGridValues": [], + "intGridValuesGroups": [], + "autoRuleGroups": [], + "autoSourceLayerDefUid": null, + "tilesetDefUid": 72, + "tilePivotX": 0, + "tilePivotY": 0, + "biomeFieldUid": null + } + ], "entities": [ + { + "identifier": "Button", + "uid": 1, + "tags": [ "Widget", "Action" ], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 13, + "height": 13, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#BE4A2F", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "FitInside", + "tileRect": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Modal", + "doc": "NameModal", + "__type": "String", + "uid": 13, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "IconName", + "doc": null, + "__type": "LocalEnum.Icon", + "uid": 15, + "type": "F_Enum(14)", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "ValueOnly", + "editorDisplayScale": 1.1, + "editorDisplayPos": "Center", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Action", + "doc": null, + "__type": "LocalEnum.Action", + "uid": 18, + "type": "F_Enum(17)", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Layer", + "doc": null, + "__type": "String", + "uid": 28, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Dropdown", + "uid": 2, + "tags": ["Widget"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 32, + "height": 16, + "resizableX": true, + "resizableY": false, + "minWidth": 32, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#D77643", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "NineSlice", + "tileRect": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "uiTileRect": null, + "nineSliceBorders": [2,10,2,3], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "TextInput", + "uid": 3, + "tags": ["Widget"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 20, + "resizableX": true, + "resizableY": false, + "minWidth": 16, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#EAD4AA", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "Knob", + "uid": 4, + "tags": [ "Widget", "PercentOutput" ], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#E4A672", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "FitInside", + "tileRect": { "tilesetUid": 72, "x": 24, "y": 24, "w": 16, "h": 16 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Color", + "doc": null, + "__type": "LocalEnum.Color", + "uid": 40, + "type": "F_Enum(39)", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "EntityTile", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": { + "id": "V_String", + "params": ["Red"] + }, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Label", + "doc": null, + "__type": "String", + "uid": 41, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Keyboard", + "uid": 16, + "tags": ["Widget"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 112, + "height": 32, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#733E39", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": { "tilesetUid": 5, "x": 0, "y": 192, "w": 112, "h": 32 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "Envelope", + "uid": 23, + "tags": ["Widget"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": true, + "resizableY": true, + "minWidth": 16, + "maxWidth": null, + "minHeight": 16, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#FF0044", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Attack", + "doc": null, + "__type": "EntityRef", + "uid": 24, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": "#0099DB", + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Decay", + "doc": null, + "__type": "EntityRef", + "uid": 25, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": "#63C74D", + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Sustain", + "doc": null, + "__type": "EntityRef", + "uid": 26, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": "#FF0044", + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Release", + "doc": null, + "__type": "EntityRef", + "uid": 27, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": "#B55088", + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "WaveTypeSelector", + "uid": 30, + "tags": ["Orchestrator"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#FEE761", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Sine", + "doc": null, + "__type": "EntityRef", + "uid": 31, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Triangle", + "doc": null, + "__type": "EntityRef", + "uid": 32, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Sawtooth", + "doc": null, + "__type": "EntityRef", + "uid": 33, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Noise", + "doc": null, + "__type": "EntityRef", + "uid": 34, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Pulse", + "doc": null, + "__type": "EntityRef", + "uid": 35, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Square", + "doc": null, + "__type": "EntityRef", + "uid": 36, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Drum", + "doc": null, + "__type": "EntityRef", + "uid": 45, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Harmonics", + "uid": 43, + "tags": ["Orchestrator"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#2CE8F5", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Harmonics", + "doc": null, + "__type": "Array", + "uid": 44, + "type": "F_EntityRef", + "isArray": true, + "canBeNull": false, + "arrayMinLength": 1, + "arrayMaxLength": 7, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 4, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + } + ] + }, + { + "identifier": "Checkbox", + "uid": 46, + "tags": ["Widget"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 8, + "height": 8, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#3E2731", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": { "tilesetUid": 5, "x": 16, "y": 32, "w": 8, "h": 8 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Label", + "doc": null, + "__type": "String", + "uid": 55, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Sweep", + "uid": 47, + "tags": ["Orchestrator"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#FEAE34", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Enabled", + "doc": null, + "__type": "EntityRef", + "uid": 48, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 46, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Acceleration", + "doc": null, + "__type": "EntityRef", + "uid": 49, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Sweep", + "doc": null, + "__type": "EntityRef", + "uid": 50, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Vibrato", + "uid": 51, + "tags": ["Orchestrator"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#63C74D", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Enabled", + "doc": null, + "__type": "EntityRef", + "uid": 52, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 46, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Frequency", + "doc": null, + "__type": "EntityRef", + "uid": 53, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Depth", + "doc": null, + "__type": "EntityRef", + "uid": 54, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 4, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "ModeSwitch", + "uid": 57, + "tags": [], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 32, + "height": 8, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#3E8948", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": { "tilesetUid": 5, "x": 80, "y": 48, "w": 32, "h": 8 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 1, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Active", + "doc": null, + "__type": "String", + "uid": 58, + "type": "F_String", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "SfxEditor", + "uid": 59, + "tags": ["Orchestrator"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": true, + "resizableY": true, + "minWidth": 16, + "maxWidth": null, + "minHeight": 16, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#124E89", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "BPM", + "doc": null, + "__type": "EntityRef", + "uid": 60, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 4, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "Instrument", + "doc": null, + "__type": "EntityRef", + "uid": 61, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Octave", + "doc": null, + "__type": "EntityRef", + "uid": 87, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 86, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Volume", + "doc": null, + "__type": "EntityRef", + "uid": 88, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": null, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + } + ] + }, + { + "identifier": "VelocityEditor", + "uid": 62, + "tags": [], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": true, + "resizableY": true, + "minWidth": 16, + "maxWidth": null, + "minHeight": 16, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#63C74D", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "Player", + "uid": 63, + "tags": ["Orchestrator"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#0099DB", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "SfxEditor", + "doc": null, + "__type": "EntityRef", + "uid": 64, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 59, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "VelocityEditor", + "doc": null, + "__type": "EntityRef", + "uid": 65, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 62, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "BPM", + "doc": null, + "__type": "EntityRef", + "uid": 66, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 4, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "SaveButton", + "doc": null, + "__type": "EntityRef", + "uid": 67, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 1, + "allowedRefTags": ["Action"], + "tilesetUid": null + }, + { + "identifier": "PlayButton", + "doc": null, + "__type": "EntityRef", + "uid": 68, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 1, + "allowedRefTags": ["Action"], + "tilesetUid": null + }, + { + "identifier": "ExportButton", + "doc": null, + "__type": "EntityRef", + "uid": 69, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 1, + "allowedRefTags": ["Action"], + "tilesetUid": null + }, + { + "identifier": "SfxSelector", + "doc": null, + "__type": "EntityRef", + "uid": 85, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Panel", + "uid": 71, + "tags": ["panel"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": true, + "resizableY": true, + "minWidth": 8, + "maxWidth": null, + "minHeight": 8, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#FFFFFF", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "NineSlice", + "tileRect": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "uiTileRect": null, + "nineSliceBorders": [5,5,5,5], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Variant", + "doc": null, + "__type": "LocalEnum.Variant", + "uid": 74, + "type": "F_Enum(73)", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": { + "id": "V_String", + "params": ["LigthBlue"] + }, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Label", + "doc": null, + "__type": "String", + "uid": 81, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "ValueOnly", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Speaker", + "uid": 76, + "tags": [], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 98, + "height": 96, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#C0CBDC", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "Repeat", + "tileRect": { "tilesetUid": 72, "x": 208, "y": 0, "w": 48, "h": 96 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "Fader", + "uid": 77, + "tags": [ "Widget", "PercentOutput" ], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 8, + "height": 24, + "resizableX": false, + "resizableY": true, + "minWidth": null, + "maxWidth": null, + "minHeight": 24, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#8B9BB4", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "Repeat", + "tileRect": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "TextButton", + "uid": 78, + "tags": [ "Widget", "Action" ], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": true, + "resizableY": false, + "minWidth": 16, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 0.08, + "lineOpacity": 0, + "hollow": false, + "color": "#5A6988", + "renderMode": "Tile", + "showName": true, + "tilesetId": 72, + "tileRenderMode": "NineSlice", + "tileRect": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "uiTileRect": null, + "nineSliceBorders": [3,3,3,3], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "Label", + "doc": null, + "__type": "String", + "uid": 79, + "type": "F_String", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "ValueOnly", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": true, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "IsActive", + "doc": null, + "__type": "Bool", + "uid": 80, + "type": "F_Bool", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Variant", + "doc": null, + "__type": "LocalEnum.Variant", + "uid": 82, + "type": "F_Enum(73)", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": { + "id": "V_String", + "params": ["HardBlue"] + }, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "TinyExit", + "doc": null, + "__type": "String", + "uid": 83, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "ValueOnly", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": true, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Action", + "doc": null, + "__type": "LocalEnum.Action", + "uid": 84, + "type": "F_Enum(17)", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] + }, + { + "identifier": "Counter", + "uid": 86, + "tags": ["Widget"], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 40, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#2CE8F5", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [] + }, + { + "identifier": "MusicGenerator", + "uid": 90, + "tags": [], + "exportToToc": false, + "allowOutOfBounds": false, + "doc": null, + "width": 16, + "height": 16, + "resizableX": false, + "resizableY": false, + "minWidth": null, + "maxWidth": null, + "minHeight": null, + "maxHeight": null, + "keepAspectRatio": false, + "tileOpacity": 1, + "fillOpacity": 1, + "lineOpacity": 1, + "hollow": false, + "color": "#3A4466", + "renderMode": "Rectangle", + "showName": true, + "tilesetId": null, + "tileRenderMode": "FitInside", + "tileRect": null, + "uiTileRect": null, + "nineSliceBorders": [], + "maxCount": 0, + "limitScope": "PerLevel", + "limitBehavior": "MoveLastOne", + "pivotX": 0, + "pivotY": 0, + "fieldDefs": [ + { + "identifier": "DrumPattern", + "doc": null, + "__type": "EntityRef", + "uid": 91, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "DrumVolume", + "doc": null, + "__type": "EntityRef", + "uid": 92, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": null, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "MusicTheme", + "doc": null, + "__type": "EntityRef", + "uid": 94, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "MusicScale", + "doc": null, + "__type": "EntityRef", + "uid": 93, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "LeadInstrument", + "doc": null, + "__type": "EntityRef", + "uid": 95, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "LeadVolume", + "doc": null, + "__type": "EntityRef", + "uid": 96, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": null, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "BassInstrument", + "doc": null, + "__type": "EntityRef", + "uid": 97, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "BassVolume", + "doc": null, + "__type": "EntityRef", + "uid": 98, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": null, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "RythmVolume", + "doc": null, + "__type": "EntityRef", + "uid": 99, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 90, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "RythmChordProgression", + "doc": null, + "__type": "EntityRef", + "uid": 100, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "RythmInstrument", + "doc": null, + "__type": "EntityRef", + "uid": 101, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Play", + "doc": null, + "__type": "EntityRef", + "uid": 102, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 1, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Volume", + "doc": null, + "__type": "EntityRef", + "uid": 103, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlyTags", + "allowedRefsEntityUid": 90, + "allowedRefTags": ["PercentOutput"], + "tilesetUid": null + }, + { + "identifier": "Selector", + "doc": null, + "__type": "EntityRef", + "uid": 104, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 2, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "Export", + "doc": null, + "__type": "EntityRef", + "uid": 105, + "type": "F_EntityRef", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "RefLinkBetweenCenters", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "CurvedArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": null, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySpecificEntity", + "allowedRefsEntityUid": 78, + "allowedRefTags": [], + "tilesetUid": null + } + ] + } + ], "tilesets": [ + { + "__cWid": 32, + "__cHei": 32, + "identifier": "Sprite_sheet", + "uid": 72, + "relPath": "sprite-sheet.png", + "embedAtlas": null, + "pxWid": 256, + "pxHei": 256, + "tileGridSize": 8, + "spacing": 0, + "padding": 0, + "tags": [], + "tagsSourceEnumUid": null, + "enumTags": [], + "customData": [], + "savedSelections": [], + "cachedPixelData": { + "opaqueTiles": "0000000000000000000000000000000001001001001001001001001000001111000000000000000000000000000011111000000000000000000000000001111110000000000000000000000000011111100000000000000000000000000111110000000000000000000000000001111100000000000000000000000000011111000000000000000000001000000111110000000000000000000010000000111100000000000000000000100000000001000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000001000000000000000000000001000000111000000000000000000000010000001110000000000000000000000100000011100000000000000000000001000000010000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000010000000000001111111111110000000100000000000011111111111100000001000000000000000000000000000000010000000000000000000000000000000100000000000011111111111100000001000000000000111111111111000000010000000000000000000000000000000100000000000", + "averageColors": "bccddcdebbbdbddddeeebcddbdc7ded8bcc8b9abd9acb89bbc88dd78bc78b9cad9dab8babca9deb9bca9b99cda9cb98b000000000000245644577456c678f89adcdefdefdbcddeeeffffddddded8ffe8ddc8d9acf8acd89bdd78ff66dd67d9daf8ead8cadeb9ffb8dda8da9cfa9cd98b000000000000d568f668fbbcfabcf99abbbcdbcdbabcbccdddddbbccbcb8ddc8bbb8b89bd89bb78abc78dd67bb67b8bad8cab7b9bca8dda8bb98b98bd98bb87b000000000000d567fccdf88af789f789f6793fd80000b868b868b689b689b888b888b888b888b679b67900000000000000000000000000000000000000000000000000003456faacf99af889f889f9abf679778a0000b868b868b689b689b888b888b888b888b679b67900000000000000000000000000000000000000000000000000008456fbbdf99af99afccdfffff679bbbc0000e8ba1456eb8914563ccc8bbb245600000000000000000000000000000000000000000000000000000000000000009456fbcdf9abfaabf889fddd000000000000145600001456000079aa1dee145600000000000000000000000000000000000000000000000000000000000000008456fbcdfabcfabcf899f446edde9ccc0000000000001def00001def00001def1def00001def00001def000000000000000000000000000000000000000000004456fabcfbcdfbbdfbcdf9aa9ccc5aab00001def00001def000000000000000000000000000000001def00001def000000000000f6a7000000000000000000000000f668fccefccdfccdfccdedc79cb800001f660f663fb81fb838ea08ea26b816b828ac08ac3fad1fad4a991b97745624560000fda7000000000000000000000000f568f778fccdfcdefcce9cb85aa800000f660f660000000018ea08ea16b80000000008ac1fad0fad36890456245614560000fd9c000000000000000000000000455764578457e889f9ab0000000000003456155738ea00004a9c000000000000000000000000000000000000000000000000fd66000000000000000000000000000000000000000024560000000000002457056818ea00001a9c000000000000000000000000000000000000000000000000fdc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f7c9000000000000000000000000e87af98be87a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f98b000000000000000000000000f98bfa9cf98b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fddd000000000000000000000000f87af98bf87a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbcd000000000000000000000000f89bf8acf89b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f89b000000000000000000000000e78af78ae78a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f4460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fda70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd9c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd6600000000000000000000000000000000000000000000bddcd888d888d888d888deeddddcd888d888d888d888d888d888bddc000000000000000000000000fdc700000000000000000000000000000000000000000000deedf888f888f888f888fffdfeedf888f888f888f888f888f888deed000000000000000000000000f7c900000000000000000000000000000000000000000000deedfffdfeedfffdfeedfffdfeedfffdfeedfeedfffdfffdfeeddeed000000000000000000000000f98b00000000000000000000000000000000000000000000bddcdeeddddcdeeddddcdeeddddcdeeddddcdddcdeeddeeddddcbddc000000000000000000000000fddd00000000000000000000000000000000000000000000b456d679d78bdcdedeefdfffda9cd9dbdaeadf96df78dfb9ddb8b6b8000000000000000000000000fbcd00000000000000000000000000000000000000000000d456f679f78bfcdefeeffffffa9cf9dbfaeaff96ff78ffb9fdb8d6b8000000000000000000000000f89b00000000000000000000000000000000000000000000d456f446f8acf8acfeeffffffa9cfa9cffe8ffe8ffadffadf6b8d6b8000000000000000000000000f67900000000000000000000000000000000000000000000b456d456d8acd8acdeefdfffda9cda9cdfe8dfe8dfaddfadd6b8b6b8000000000000000000000000f44600000000000000000000000000000000000000000000" + } + } + ], "enums": [ + { "identifier": "Icon", "uid": 14, "values": [ + { "id": "Envelope", "tileRect": null, "color": 12470831 }, + { "id": "Gear", "tileRect": null, "color": 14120515 }, + { "id": "Random", "tileRect": null, "color": 15389866 }, + { "id": "Modulations", "tileRect": null, "color": 14984818 }, + { "id": "Harmonics", "tileRect": null, "color": 7552569 }, + { "id": "Sine", "tileRect": null, "color": 4073265 }, + { "id": "Triangle", "tileRect": null, "color": 16690740 }, + { "id": "Pulse", "tileRect": null, "color": 16705377 }, + { "id": "Noise", "tileRect": null, "color": 6539085 }, + { "id": "Square", "tileRect": null, "color": 4098376 }, + { "id": "Sawtooth", "tileRect": null, "color": 1199753 }, + { "id": "Drum", "tileRect": null, "color": 39387 }, + { "id": "Play", "tileRect": null, "color": 2943221 }, + { "id": "Save", "tileRect": null, "color": 16777215 }, + { "id": "Export", "tileRect": null, "color": 12635100 } + ], "iconTilesetUid": null, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }, + { "identifier": "Action", "uid": 17, "values": [ + { "id": "Validate", "tileRect": null, "color": 12470831 }, + { "id": "Cancel", "tileRect": null, "color": 14120515 }, + { "id": "Save", "tileRect": null, "color": 15389866 } + ], "iconTilesetUid": null, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }, + { "identifier": "Color", "uid": 39, "values": [ + { "id": "Blue", "tileRect": { "tilesetUid": 72, "x": 88, "y": 24, "w": 16, "h": 16 }, "color": 39387 }, + { "id": "Green", "tileRect": { "tilesetUid": 72, "x": 40, "y": 24, "w": 16, "h": 16 }, "color": 6539085 }, + { "id": "Orange", "tileRect": { "tilesetUid": 72, "x": 72, "y": 24, "w": 16, "h": 16 }, "color": 11882632 }, + { "id": "Red", "tileRect": { "tilesetUid": 72, "x": 24, "y": 24, "w": 16, "h": 16 }, "color": 16711748 }, + { "id": "Yellow", "tileRect": { "tilesetUid": 72, "x": 56, "y": 24, "w": 16, "h": 16 }, "color": 12470831 } + ], "iconTilesetUid": 72, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }, + { "identifier": "Variant", "uid": 73, "values": [ + { "id": "White", "tileRect": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, "color": 14984818 }, + { "id": "LigthBlue", "tileRect": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, "color": 2943221 }, + { "id": "HardBlue", "tileRect": { "tilesetUid": 72, "x": 72, "y": 0, "w": 24, "h": 24 }, "color": 14120515 }, + { "id": "Yellow", "tileRect": { "tilesetUid": 72, "x": 48, "y": 0, "w": 24, "h": 24 }, "color": 15389866 }, + { "id": "Red", "tileRect": { "tilesetUid": 72, "x": 96, "y": 0, "w": 24, "h": 24 }, "color": 7552569 }, + { "id": "Green", "tileRect": { "tilesetUid": 72, "x": 120, "y": 0, "w": 24, "h": 24 }, "color": 4073265 }, + { "id": "Orange", "tileRect": { "tilesetUid": 72, "x": 144, "y": 0, "w": 24, "h": 24 }, "color": 16690740 }, + { "id": "Purple", "tileRect": { "tilesetUid": 72, "x": 168, "y": 0, "w": 24, "h": 24 }, "color": 16705377 } + ], "iconTilesetUid": 72, "externalRelPath": null, "externalFileChecksum": null, "tags": [] } + ], "externalEnums": [], "levelFields": [] }, + "levels": [ + { + "identifier": "InstrumentEditor", + "iid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "uid": 0, + "worldX": 0, + "worldY": 0, + "worldDepth": 0, + "pxWid": 384, + "pxHei": 256, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelId": 0, + "layerDefUid": 6, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 791332, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Keyboard", + "__grid": [17,26], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": null, + "__smartColor": "#733E39", + "iid": "89021c00-fa90-11f0-8d20-3f0909672c1b", + "width": 112, + "height": 32, + "defUid": 16, + "px": [140,208], + "fieldInstances": [], + "__worldX": 140, + "__worldY": 208 + }, + { + "__identifier": "Speaker", + "__grid": [36,21], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 72, "x": 208, "y": 0, "w": 48, "h": 96 }, + "__smartColor": "#C0CBDC", + "iid": "b29caab0-fa90-11f0-ade1-3320f808a7cd", + "width": 98, + "height": 96, + "defUid": 76, + "px": [288,168], + "fieldInstances": [], + "__worldX": 288, + "__worldY": 168 + }, + { + "__identifier": "Speaker", + "__grid": [0,21], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 72, "x": 208, "y": 0, "w": 48, "h": 96 }, + "__smartColor": "#C0CBDC", + "iid": "b4446740-fa90-11f0-ade1-afb98a823567", + "width": 98, + "height": 96, + "defUid": 76, + "px": [0,168], + "fieldInstances": [], + "__worldX": 0, + "__worldY": 168 + }, + { + "__identifier": "Knob", + "__grid": [16,22], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 56, "y": 24, "w": 16, "h": 16 }, + "__smartColor": "#E4A672", + "iid": "94174ae0-fa90-11f0-ade1-9d1424886248", + "width": 16, + "height": 16, + "defUid": 4, + "px": [128,176], + "fieldInstances": [ + { "__identifier": "Color", "__type": "LocalEnum.Color", "__value": "Yellow", "__tile": { "tilesetUid": 72, "x": 56, "y": 24, "w": 16, "h": 16 }, "defUid": 40, "realEditorValues": [{ + "id": "V_String", + "params": ["Yellow"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 41, "realEditorValues": [] } + ], + "__worldX": 128, + "__worldY": 176 + }, + { + "__identifier": "Knob", + "__grid": [21,22], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 40, "y": 24, "w": 16, "h": 16 }, + "__smartColor": "#E4A672", + "iid": "94bf4ab0-fa90-11f0-ade1-ab1a9c595258", + "width": 16, + "height": 16, + "defUid": 4, + "px": [168,176], + "fieldInstances": [ + { "__identifier": "Color", "__type": "LocalEnum.Color", "__value": "Green", "__tile": { "tilesetUid": 72, "x": 40, "y": 24, "w": 16, "h": 16 }, "defUid": 40, "realEditorValues": [{ + "id": "V_String", + "params": ["Green"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 41, "realEditorValues": [] } + ], + "__worldX": 168, + "__worldY": 176 + }, + { + "__identifier": "Knob", + "__grid": [26,22], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 72, "y": 24, "w": 16, "h": 16 }, + "__smartColor": "#E4A672", + "iid": "9542d290-fa90-11f0-ade1-ab899ebe0964", + "width": 16, + "height": 16, + "defUid": 4, + "px": [208,176], + "fieldInstances": [ + { "__identifier": "Color", "__type": "LocalEnum.Color", "__value": "Orange", "__tile": { "tilesetUid": 72, "x": 72, "y": 24, "w": 16, "h": 16 }, "defUid": 40, "realEditorValues": [{ + "id": "V_String", + "params": ["Orange"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 41, "realEditorValues": [] } + ], + "__worldX": 208, + "__worldY": 176 + }, + { + "__identifier": "Knob", + "__grid": [31,22], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 24, "w": 16, "h": 16 }, + "__smartColor": "#E4A672", + "iid": "96280270-fa90-11f0-ade1-637b463d516b", + "width": 16, + "height": 16, + "defUid": 4, + "px": [248,176], + "fieldInstances": [ + { "__identifier": "Color", "__type": "LocalEnum.Color", "__value": "Red", "__tile": { "tilesetUid": 72, "x": 24, "y": 24, "w": 16, "h": 16 }, "defUid": 40, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 41, "realEditorValues": [] } + ], + "__worldX": 248, + "__worldY": 176 + }, + { + "__identifier": "Envelope", + "__grid": [14,10], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": null, + "__smartColor": "#FF0044", + "iid": "8c51f250-fa90-11f0-ade1-d313c7943aff", + "width": 166, + "height": 80, + "defUid": 23, + "px": [114,80], + "fieldInstances": [ + { "__identifier": "Attack", "__type": "EntityRef", "__value": { + "entityIid": "94174ae0-fa90-11f0-ade1-9d1424886248", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 24, "realEditorValues": [{ + "id": "V_String", + "params": ["94174ae0-fa90-11f0-ade1-9d1424886248"] + }] }, + { "__identifier": "Decay", "__type": "EntityRef", "__value": { + "entityIid": "94bf4ab0-fa90-11f0-ade1-ab1a9c595258", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 25, "realEditorValues": [{ + "id": "V_String", + "params": ["94bf4ab0-fa90-11f0-ade1-ab1a9c595258"] + }] }, + { "__identifier": "Sustain", "__type": "EntityRef", "__value": { + "entityIid": "9542d290-fa90-11f0-ade1-ab899ebe0964", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 26, "realEditorValues": [{ + "id": "V_String", + "params": ["9542d290-fa90-11f0-ade1-ab899ebe0964"] + }] }, + { "__identifier": "Release", "__type": "EntityRef", "__value": { + "entityIid": "96280270-fa90-11f0-ade1-637b463d516b", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 27, "realEditorValues": [{ + "id": "V_String", + "params": ["96280270-fa90-11f0-ade1-637b463d516b"] + }] } + ], + "__worldX": 114, + "__worldY": 80 + }, + { + "__identifier": "Fader", + "__grid": [1,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "2fdb7c70-fa90-11f0-ade1-3b9f367fc7e9", + "width": 8, + "height": 48, + "defUid": 77, + "px": [14,112], + "fieldInstances": [], + "__worldX": 14, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [3,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "346411d0-fa90-11f0-ade1-1bf6bba48c53", + "width": 8, + "height": 48, + "defUid": 77, + "px": [26,112], + "fieldInstances": [], + "__worldX": 26, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [4,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "3626a960-fa90-11f0-ade1-0587a7ef8128", + "width": 8, + "height": 48, + "defUid": 77, + "px": [38,112], + "fieldInstances": [], + "__worldX": 38, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [6,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "37cf2940-fa90-11f0-ade1-5b018a3ac01f", + "width": 8, + "height": 48, + "defUid": 77, + "px": [50,112], + "fieldInstances": [], + "__worldX": 50, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [7,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "3a279830-fa90-11f0-ade1-492df4507858", + "width": 8, + "height": 48, + "defUid": 77, + "px": [62,112], + "fieldInstances": [], + "__worldX": 62, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [9,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "935b22a0-fa90-11f0-ade1-5b2f270bcca8", + "width": 8, + "height": 48, + "defUid": 77, + "px": [74,112], + "fieldInstances": [], + "__worldX": 74, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [10,14], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "93e190b0-fa90-11f0-ade1-8534814b62ee", + "width": 8, + "height": 48, + "defUid": 77, + "px": [86,112], + "fieldInstances": [], + "__worldX": 86, + "__worldY": 112 + }, + { + "__identifier": "Harmonics", + "__grid": [13,26], + "__pivot": [0,0], + "__tags": ["Orchestrator"], + "__tile": null, + "__smartColor": "#2CE8F5", + "iid": "d1d9f9c0-fa90-11f0-ade1-d5f796a2158f", + "width": 16, + "height": 16, + "defUid": 43, + "px": [109,209], + "fieldInstances": [{ "__identifier": "Harmonics", "__type": "Array", "__value": [ + { + "entityIid": "2fdb7c70-fa90-11f0-ade1-3b9f367fc7e9", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, + { + "entityIid": "346411d0-fa90-11f0-ade1-1bf6bba48c53", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, + { + "entityIid": "3626a960-fa90-11f0-ade1-0587a7ef8128", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, + { + "entityIid": "37cf2940-fa90-11f0-ade1-5b018a3ac01f", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, + { + "entityIid": "3a279830-fa90-11f0-ade1-492df4507858", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, + { + "entityIid": "935b22a0-fa90-11f0-ade1-5b2f270bcca8", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, + { + "entityIid": "93e190b0-fa90-11f0-ade1-8534814b62ee", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + } + ], "__tile": null, "defUid": 44, "realEditorValues": [ + { + "id": "V_String", + "params": ["2fdb7c70-fa90-11f0-ade1-3b9f367fc7e9"] + }, + { + "id": "V_String", + "params": ["346411d0-fa90-11f0-ade1-1bf6bba48c53"] + }, + { + "id": "V_String", + "params": ["3626a960-fa90-11f0-ade1-0587a7ef8128"] + }, + { + "id": "V_String", + "params": ["37cf2940-fa90-11f0-ade1-5b018a3ac01f"] + }, + { + "id": "V_String", + "params": ["3a279830-fa90-11f0-ade1-492df4507858"] + }, + { + "id": "V_String", + "params": ["935b22a0-fa90-11f0-ade1-5b2f270bcca8"] + }, + { + "id": "V_String", + "params": ["93e190b0-fa90-11f0-ade1-8534814b62ee"] + } + ] }], + "__worldX": 109, + "__worldY": 209 + }, + { + "__identifier": "Dropdown", + "__grid": [13,5], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "49a4b0f0-fa90-11f0-8348-0f19ae757f51", + "width": 163, + "height": 16, + "defUid": 2, + "px": [107,43], + "fieldInstances": [], + "__worldX": 107, + "__worldY": 43 + }, + { + "__identifier": "Button", + "__grid": [33,5], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "838547d0-fa90-11f0-8348-419ba72c9dd0", + "width": 13, + "height": 13, + "defUid": 1, + "px": [270,44], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": "NameModal", "__tile": null, "defUid": 13, "realEditorValues": [{ + "id": "V_String", + "params": ["NameModal"] + }] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Gear", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Gear"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 270, + "__worldY": 44 + }, + { + "__identifier": "TextButton", + "__grid": [0,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "0ed92860-fa90-11f0-8348-8157c09dd647", + "width": 88, + "height": 16, + "defUid": 78, + "px": [4,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "←→ Instrument", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["←→ Instrument"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": true, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ true ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Green", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Green"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [null] } + ], + "__worldX": 4, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [12,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "47fe1e20-fa90-11f0-8348-e9541ac48bcc", + "width": 96, + "height": 16, + "defUid": 78, + "px": [96,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↑↓ sound effect", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↑↓ sound effect"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ false ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Yellow", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Yellow"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": "tiny-sfx-editor.lua", "__tile": null, "defUid": 83, "realEditorValues": [{ + "id": "V_String", + "params": ["tiny-sfx-editor.lua"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 96, + "__worldY": 4 + }, + { + "__identifier": "Button", + "__grid": [0,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "cbbbe5c0-fa90-11f0-8348-2d04cba85dc2", + "width": 13, + "height": 13, + "defUid": 1, + "px": [6,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Sine", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Sine"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 6, + "__worldY": 78 + }, + { + "__identifier": "Button", + "__grid": [2,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "cc665690-fa90-11f0-8348-1d6ab18c35d2", + "width": 13, + "height": 13, + "defUid": 1, + "px": [20,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Triangle", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Triangle"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 20, + "__worldY": 78 + }, + { + "__identifier": "Button", + "__grid": [4,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "cd0cf6d0-fa90-11f0-8348-a7946f5e4c7f", + "width": 13, + "height": 13, + "defUid": 1, + "px": [34,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Pulse", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Pulse"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 34, + "__worldY": 78 + }, + { + "__identifier": "Button", + "__grid": [6,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "cdcceb70-fa90-11f0-8348-df9dafe386da", + "width": 13, + "height": 13, + "defUid": 1, + "px": [48,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Noise", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Noise"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 48, + "__worldY": 78 + }, + { + "__identifier": "Button", + "__grid": [7,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "ce79f450-fa90-11f0-8348-fdf3dd47b3a8", + "width": 13, + "height": 13, + "defUid": 1, + "px": [62,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Square", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Square"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 62, + "__worldY": 78 + }, + { + "__identifier": "Button", + "__grid": [9,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "cf1a2bf0-fa90-11f0-8348-c74e561b7b5c", + "width": 13, + "height": 13, + "defUid": 1, + "px": [76,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Sawtooth", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Sawtooth"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 76, + "__worldY": 78 + }, + { + "__identifier": "WaveTypeSelector", + "__grid": [1,4], + "__pivot": [0,0], + "__tags": ["Orchestrator"], + "__tile": null, + "__smartColor": "#FEE761", + "iid": "b87c5c40-fa90-11f0-8348-63926a421713", + "width": 16, + "height": 16, + "defUid": 30, + "px": [8,32], + "fieldInstances": [ + { "__identifier": "Sine", "__type": "EntityRef", "__value": { + "entityIid": "cbbbe5c0-fa90-11f0-8348-2d04cba85dc2", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 31, "realEditorValues": [{ + "id": "V_String", + "params": ["cbbbe5c0-fa90-11f0-8348-2d04cba85dc2"] + }] }, + { "__identifier": "Triangle", "__type": "EntityRef", "__value": { + "entityIid": "cc665690-fa90-11f0-8348-1d6ab18c35d2", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 32, "realEditorValues": [{ + "id": "V_String", + "params": ["cc665690-fa90-11f0-8348-1d6ab18c35d2"] + }] }, + { "__identifier": "Sawtooth", "__type": "EntityRef", "__value": { + "entityIid": "cf1a2bf0-fa90-11f0-8348-c74e561b7b5c", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 33, "realEditorValues": [{ + "id": "V_String", + "params": ["cf1a2bf0-fa90-11f0-8348-c74e561b7b5c"] + }] }, + { "__identifier": "Noise", "__type": "EntityRef", "__value": { + "entityIid": "cdcceb70-fa90-11f0-8348-df9dafe386da", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 34, "realEditorValues": [{ + "id": "V_String", + "params": ["cdcceb70-fa90-11f0-8348-df9dafe386da"] + }] }, + { "__identifier": "Pulse", "__type": "EntityRef", "__value": { + "entityIid": "cd0cf6d0-fa90-11f0-8348-a7946f5e4c7f", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 35, "realEditorValues": [{ + "id": "V_String", + "params": ["cd0cf6d0-fa90-11f0-8348-a7946f5e4c7f"] + }] }, + { "__identifier": "Square", "__type": "EntityRef", "__value": { + "entityIid": "ce79f450-fa90-11f0-8348-fdf3dd47b3a8", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 36, "realEditorValues": [{ + "id": "V_String", + "params": ["ce79f450-fa90-11f0-8348-fdf3dd47b3a8"] + }] }, + { "__identifier": "Drum", "__type": "EntityRef", "__value": { + "entityIid": "18f51620-fa90-11f0-8348-6d6cbe757794", + "layerIid": "32e251f0-fa90-11f0-ad09-7fbd56ca9a79", + "levelIid": "4cf47922-fa90-11f0-ad09-17fea0f83d6f", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 45, "realEditorValues": [{ + "id": "V_String", + "params": ["18f51620-fa90-11f0-8348-6d6cbe757794"] + }] } + ], + "__worldX": 8, + "__worldY": 32 + }, + { + "__identifier": "Button", + "__grid": [11,9], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "18f51620-fa90-11f0-8348-6d6cbe757794", + "width": 13, + "height": 13, + "defUid": 1, + "px": [90,78], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Drum", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Drum"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 90, + "__worldY": 78 + }, + { + "__identifier": "TextButton", + "__grid": [45,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "123a5720-fa90-11f0-8348-b147a0fed2be", + "width": 18, + "height": 16, + "defUid": 78, + "px": [360,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↧↨", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↧↨"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Red", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Red"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": "Save", "__tile": null, "defUid": 84, "realEditorValues": [{ + "id": "V_String", + "params": ["Save"] + }] } + ], + "__worldX": 360, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [24,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "65d67a20-fa90-11f0-9ead-57d08aed11b2", + "width": 60, + "height": 16, + "defUid": 78, + "px": [196,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↔↕Music", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↔↕Music"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ false ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Orange", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Orange"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": "tiny-music-editor.lua", "__tile": null, "defUid": 83, "realEditorValues": [{ + "id": "V_String", + "params": ["tiny-music-editor.lua"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [null] } + ], + "__worldX": 196, + "__worldY": 4 + } + ] + }, + { + "__identifier": "Panels", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "812baec0-fa90-11f0-ade1-ab78803cff43", + "levelId": 0, + "layerDefUid": 75, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 5591170, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Panel", + "__grid": [13,4], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "e7d851f0-fa90-11f0-ade1-d99f1c33cd23", + "width": 184, + "height": 40, + "defUid": 71, + "px": [104,32], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 104, + "__worldY": 32 + }, + { + "__identifier": "Panel", + "__grid": [13,9], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "f0ea6710-fa90-11f0-ade1-7bcfb0e9d645", + "width": 180, + "height": 96, + "defUid": 71, + "px": [106,72], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 106, + "__worldY": 72 + }, + { + "__identifier": "Panel", + "__grid": [0,9], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "f61f1af0-fa90-11f0-ade1-5d3f516b034b", + "width": 105, + "height": 24, + "defUid": 71, + "px": [2,72], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 2, + "__worldY": 72 + }, + { + "__identifier": "Panel", + "__grid": [35,9], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "f968eec0-fa90-11f0-ade1-b7cb48c489ab", + "width": 96, + "height": 96, + "defUid": 71, + "px": [286,72], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 286, + "__worldY": 72 + }, + { + "__identifier": "Panel", + "__grid": [0,12], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "009776d0-fa90-11f0-ade1-a7ed0f374733", + "width": 105, + "height": 72, + "defUid": 71, + "px": [2,96], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Harmonics", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Harmonics"] + }] } + ], + "__worldX": 2, + "__worldY": 96 + }, + { + "__identifier": "Panel", + "__grid": [13,21], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "0520f690-fa90-11f0-ade1-05dcb8638b50", + "width": 184, + "height": 32, + "defUid": 71, + "px": [104,168], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 104, + "__worldY": 168 + }, + { + "__identifier": "Panel", + "__grid": [16,25], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "0aaa0c00-fa90-11f0-ade1-4b1551e99e07", + "width": 136, + "height": 48, + "defUid": 71, + "px": [128,200], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 128, + "__worldY": 200 + } + ] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 72, + "__tilesetRelPath": "sprite-sheet.png", + "iid": "0869c0a0-fa90-11f0-ad09-ffc9e5bf0004", + "levelId": 0, + "layerDefUid": 10, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 2187549, + "overrideTilesetUid": 72, + "gridTiles": [ + { "px": [0,0], "src": [216,104], "f": 0, "t": 443, "d": [0], "a": 1 }, + { "px": [8,0], "src": [224,104], "f": 0, "t": 444, "d": [1], "a": 1 }, + { "px": [16,0], "src": [224,104], "f": 0, "t": 444, "d": [2], "a": 1 }, + { "px": [24,0], "src": [224,104], "f": 0, "t": 444, "d": [3], "a": 1 }, + { "px": [32,0], "src": [224,104], "f": 0, "t": 444, "d": [4], "a": 1 }, + { "px": [40,0], "src": [224,104], "f": 0, "t": 444, "d": [5], "a": 1 }, + { "px": [48,0], "src": [224,104], "f": 0, "t": 444, "d": [6], "a": 1 }, + { "px": [56,0], "src": [224,104], "f": 0, "t": 444, "d": [7], "a": 1 }, + { "px": [64,0], "src": [224,104], "f": 0, "t": 444, "d": [8], "a": 1 }, + { "px": [72,0], "src": [224,104], "f": 0, "t": 444, "d": [9], "a": 1 }, + { "px": [80,0], "src": [224,104], "f": 0, "t": 444, "d": [10], "a": 1 }, + { "px": [88,0], "src": [224,104], "f": 0, "t": 444, "d": [11], "a": 1 }, + { "px": [96,0], "src": [224,104], "f": 0, "t": 444, "d": [12], "a": 1 }, + { "px": [104,0], "src": [224,104], "f": 0, "t": 444, "d": [13], "a": 1 }, + { "px": [112,0], "src": [224,104], "f": 0, "t": 444, "d": [14], "a": 1 }, + { "px": [120,0], "src": [224,104], "f": 0, "t": 444, "d": [15], "a": 1 }, + { "px": [128,0], "src": [224,104], "f": 0, "t": 444, "d": [16], "a": 1 }, + { "px": [136,0], "src": [224,104], "f": 0, "t": 444, "d": [17], "a": 1 }, + { "px": [144,0], "src": [224,104], "f": 0, "t": 444, "d": [18], "a": 1 }, + { "px": [152,0], "src": [224,104], "f": 0, "t": 444, "d": [19], "a": 1 }, + { "px": [160,0], "src": [224,104], "f": 0, "t": 444, "d": [20], "a": 1 }, + { "px": [168,0], "src": [224,104], "f": 0, "t": 444, "d": [21], "a": 1 }, + { "px": [176,0], "src": [224,104], "f": 0, "t": 444, "d": [22], "a": 1 }, + { "px": [184,0], "src": [224,104], "f": 0, "t": 444, "d": [23], "a": 1 }, + { "px": [192,0], "src": [224,104], "f": 0, "t": 444, "d": [24], "a": 1 }, + { "px": [200,0], "src": [224,104], "f": 0, "t": 444, "d": [25], "a": 1 }, + { "px": [208,0], "src": [224,104], "f": 0, "t": 444, "d": [26], "a": 1 }, + { "px": [216,0], "src": [224,104], "f": 0, "t": 444, "d": [27], "a": 1 }, + { "px": [224,0], "src": [224,104], "f": 0, "t": 444, "d": [28], "a": 1 }, + { "px": [232,0], "src": [224,104], "f": 0, "t": 444, "d": [29], "a": 1 }, + { "px": [240,0], "src": [224,104], "f": 0, "t": 444, "d": [30], "a": 1 }, + { "px": [248,0], "src": [224,104], "f": 0, "t": 444, "d": [31], "a": 1 }, + { "px": [256,0], "src": [224,104], "f": 0, "t": 444, "d": [32], "a": 1 }, + { "px": [264,0], "src": [224,104], "f": 0, "t": 444, "d": [33], "a": 1 }, + { "px": [272,0], "src": [224,104], "f": 0, "t": 444, "d": [34], "a": 1 }, + { "px": [280,0], "src": [224,104], "f": 0, "t": 444, "d": [35], "a": 1 }, + { "px": [288,0], "src": [224,104], "f": 0, "t": 444, "d": [36], "a": 1 }, + { "px": [296,0], "src": [224,104], "f": 0, "t": 444, "d": [37], "a": 1 }, + { "px": [304,0], "src": [224,104], "f": 0, "t": 444, "d": [38], "a": 1 }, + { "px": [312,0], "src": [224,104], "f": 0, "t": 444, "d": [39], "a": 1 }, + { "px": [320,0], "src": [224,104], "f": 0, "t": 444, "d": [40], "a": 1 }, + { "px": [328,0], "src": [224,104], "f": 0, "t": 444, "d": [41], "a": 1 }, + { "px": [336,0], "src": [224,104], "f": 0, "t": 444, "d": [42], "a": 1 }, + { "px": [344,0], "src": [224,104], "f": 0, "t": 444, "d": [43], "a": 1 }, + { "px": [352,0], "src": [224,104], "f": 0, "t": 444, "d": [44], "a": 1 }, + { "px": [360,0], "src": [224,104], "f": 0, "t": 444, "d": [45], "a": 1 }, + { "px": [368,0], "src": [224,104], "f": 0, "t": 444, "d": [46], "a": 1 }, + { "px": [376,0], "src": [232,104], "f": 0, "t": 445, "d": [47], "a": 1 }, + { "px": [0,8], "src": [216,112], "f": 0, "t": 475, "d": [48], "a": 1 }, + { "px": [8,8], "src": [224,112], "f": 0, "t": 476, "d": [49], "a": 1 }, + { "px": [16,8], "src": [224,112], "f": 0, "t": 476, "d": [50], "a": 1 }, + { "px": [24,8], "src": [224,112], "f": 0, "t": 476, "d": [51], "a": 1 }, + { "px": [32,8], "src": [224,112], "f": 0, "t": 476, "d": [52], "a": 1 }, + { "px": [40,8], "src": [224,112], "f": 0, "t": 476, "d": [53], "a": 1 }, + { "px": [48,8], "src": [224,112], "f": 0, "t": 476, "d": [54], "a": 1 }, + { "px": [56,8], "src": [224,112], "f": 0, "t": 476, "d": [55], "a": 1 }, + { "px": [64,8], "src": [224,112], "f": 0, "t": 476, "d": [56], "a": 1 }, + { "px": [72,8], "src": [224,112], "f": 0, "t": 476, "d": [57], "a": 1 }, + { "px": [80,8], "src": [224,112], "f": 0, "t": 476, "d": [58], "a": 1 }, + { "px": [88,8], "src": [224,112], "f": 0, "t": 476, "d": [59], "a": 1 }, + { "px": [96,8], "src": [224,112], "f": 0, "t": 476, "d": [60], "a": 1 }, + { "px": [104,8], "src": [224,112], "f": 0, "t": 476, "d": [61], "a": 1 }, + { "px": [112,8], "src": [224,112], "f": 0, "t": 476, "d": [62], "a": 1 }, + { "px": [120,8], "src": [224,112], "f": 0, "t": 476, "d": [63], "a": 1 }, + { "px": [128,8], "src": [224,112], "f": 0, "t": 476, "d": [64], "a": 1 }, + { "px": [136,8], "src": [224,112], "f": 0, "t": 476, "d": [65], "a": 1 }, + { "px": [144,8], "src": [224,112], "f": 0, "t": 476, "d": [66], "a": 1 }, + { "px": [152,8], "src": [224,112], "f": 0, "t": 476, "d": [67], "a": 1 }, + { "px": [160,8], "src": [224,112], "f": 0, "t": 476, "d": [68], "a": 1 }, + { "px": [168,8], "src": [224,112], "f": 0, "t": 476, "d": [69], "a": 1 }, + { "px": [176,8], "src": [224,112], "f": 0, "t": 476, "d": [70], "a": 1 }, + { "px": [184,8], "src": [224,112], "f": 0, "t": 476, "d": [71], "a": 1 }, + { "px": [192,8], "src": [224,112], "f": 0, "t": 476, "d": [72], "a": 1 }, + { "px": [200,8], "src": [224,112], "f": 0, "t": 476, "d": [73], "a": 1 }, + { "px": [208,8], "src": [224,112], "f": 0, "t": 476, "d": [74], "a": 1 }, + { "px": [216,8], "src": [224,112], "f": 0, "t": 476, "d": [75], "a": 1 }, + { "px": [224,8], "src": [224,112], "f": 0, "t": 476, "d": [76], "a": 1 }, + { "px": [232,8], "src": [224,112], "f": 0, "t": 476, "d": [77], "a": 1 }, + { "px": [240,8], "src": [224,112], "f": 0, "t": 476, "d": [78], "a": 1 }, + { "px": [248,8], "src": [224,112], "f": 0, "t": 476, "d": [79], "a": 1 }, + { "px": [256,8], "src": [224,112], "f": 0, "t": 476, "d": [80], "a": 1 }, + { "px": [264,8], "src": [224,112], "f": 0, "t": 476, "d": [81], "a": 1 }, + { "px": [272,8], "src": [224,112], "f": 0, "t": 476, "d": [82], "a": 1 }, + { "px": [280,8], "src": [224,112], "f": 0, "t": 476, "d": [83], "a": 1 }, + { "px": [288,8], "src": [224,112], "f": 0, "t": 476, "d": [84], "a": 1 }, + { "px": [296,8], "src": [224,112], "f": 0, "t": 476, "d": [85], "a": 1 }, + { "px": [304,8], "src": [224,112], "f": 0, "t": 476, "d": [86], "a": 1 }, + { "px": [312,8], "src": [224,112], "f": 0, "t": 476, "d": [87], "a": 1 }, + { "px": [320,8], "src": [224,112], "f": 0, "t": 476, "d": [88], "a": 1 }, + { "px": [328,8], "src": [224,112], "f": 0, "t": 476, "d": [89], "a": 1 }, + { "px": [336,8], "src": [224,112], "f": 0, "t": 476, "d": [90], "a": 1 }, + { "px": [344,8], "src": [224,112], "f": 0, "t": 476, "d": [91], "a": 1 }, + { "px": [352,8], "src": [224,112], "f": 0, "t": 476, "d": [92], "a": 1 }, + { "px": [360,8], "src": [224,112], "f": 0, "t": 476, "d": [93], "a": 1 }, + { "px": [368,8], "src": [224,112], "f": 0, "t": 476, "d": [94], "a": 1 }, + { "px": [376,8], "src": [232,112], "f": 0, "t": 477, "d": [95], "a": 1 }, + { "px": [0,16], "src": [216,120], "f": 0, "t": 507, "d": [96], "a": 1 }, + { "px": [8,16], "src": [224,120], "f": 0, "t": 508, "d": [97], "a": 1 }, + { "px": [16,16], "src": [224,120], "f": 0, "t": 508, "d": [98], "a": 1 }, + { "px": [24,16], "src": [224,120], "f": 0, "t": 508, "d": [99], "a": 1 }, + { "px": [32,16], "src": [224,120], "f": 0, "t": 508, "d": [100], "a": 1 }, + { "px": [40,16], "src": [224,120], "f": 0, "t": 508, "d": [101], "a": 1 }, + { "px": [48,16], "src": [224,120], "f": 0, "t": 508, "d": [102], "a": 1 }, + { "px": [56,16], "src": [224,120], "f": 0, "t": 508, "d": [103], "a": 1 }, + { "px": [64,16], "src": [224,120], "f": 0, "t": 508, "d": [104], "a": 1 }, + { "px": [72,16], "src": [224,120], "f": 0, "t": 508, "d": [105], "a": 1 }, + { "px": [80,16], "src": [224,120], "f": 0, "t": 508, "d": [106], "a": 1 }, + { "px": [88,16], "src": [224,120], "f": 0, "t": 508, "d": [107], "a": 1 }, + { "px": [96,16], "src": [224,120], "f": 0, "t": 508, "d": [108], "a": 1 }, + { "px": [104,16], "src": [224,120], "f": 0, "t": 508, "d": [109], "a": 1 }, + { "px": [112,16], "src": [224,120], "f": 0, "t": 508, "d": [110], "a": 1 }, + { "px": [120,16], "src": [224,120], "f": 0, "t": 508, "d": [111], "a": 1 }, + { "px": [128,16], "src": [224,120], "f": 0, "t": 508, "d": [112], "a": 1 }, + { "px": [136,16], "src": [224,120], "f": 0, "t": 508, "d": [113], "a": 1 }, + { "px": [144,16], "src": [224,120], "f": 0, "t": 508, "d": [114], "a": 1 }, + { "px": [152,16], "src": [224,120], "f": 0, "t": 508, "d": [115], "a": 1 }, + { "px": [160,16], "src": [224,120], "f": 0, "t": 508, "d": [116], "a": 1 }, + { "px": [168,16], "src": [224,120], "f": 0, "t": 508, "d": [117], "a": 1 }, + { "px": [176,16], "src": [224,120], "f": 0, "t": 508, "d": [118], "a": 1 }, + { "px": [184,16], "src": [224,120], "f": 0, "t": 508, "d": [119], "a": 1 }, + { "px": [192,16], "src": [224,120], "f": 0, "t": 508, "d": [120], "a": 1 }, + { "px": [200,16], "src": [224,120], "f": 0, "t": 508, "d": [121], "a": 1 }, + { "px": [208,16], "src": [224,120], "f": 0, "t": 508, "d": [122], "a": 1 }, + { "px": [216,16], "src": [224,120], "f": 0, "t": 508, "d": [123], "a": 1 }, + { "px": [224,16], "src": [224,120], "f": 0, "t": 508, "d": [124], "a": 1 }, + { "px": [232,16], "src": [224,120], "f": 0, "t": 508, "d": [125], "a": 1 }, + { "px": [240,16], "src": [224,120], "f": 0, "t": 508, "d": [126], "a": 1 }, + { "px": [248,16], "src": [224,120], "f": 0, "t": 508, "d": [127], "a": 1 }, + { "px": [256,16], "src": [224,120], "f": 0, "t": 508, "d": [128], "a": 1 }, + { "px": [264,16], "src": [224,120], "f": 0, "t": 508, "d": [129], "a": 1 }, + { "px": [272,16], "src": [224,120], "f": 0, "t": 508, "d": [130], "a": 1 }, + { "px": [280,16], "src": [224,120], "f": 0, "t": 508, "d": [131], "a": 1 }, + { "px": [288,16], "src": [224,120], "f": 0, "t": 508, "d": [132], "a": 1 }, + { "px": [296,16], "src": [224,120], "f": 0, "t": 508, "d": [133], "a": 1 }, + { "px": [304,16], "src": [224,120], "f": 0, "t": 508, "d": [134], "a": 1 }, + { "px": [312,16], "src": [224,120], "f": 0, "t": 508, "d": [135], "a": 1 }, + { "px": [320,16], "src": [224,120], "f": 0, "t": 508, "d": [136], "a": 1 }, + { "px": [328,16], "src": [224,120], "f": 0, "t": 508, "d": [137], "a": 1 }, + { "px": [336,16], "src": [224,120], "f": 0, "t": 508, "d": [138], "a": 1 }, + { "px": [344,16], "src": [224,120], "f": 0, "t": 508, "d": [139], "a": 1 }, + { "px": [352,16], "src": [224,120], "f": 0, "t": 508, "d": [140], "a": 1 }, + { "px": [360,16], "src": [224,120], "f": 0, "t": 508, "d": [141], "a": 1 }, + { "px": [368,16], "src": [224,120], "f": 0, "t": 508, "d": [142], "a": 1 }, + { "px": [376,16], "src": [232,120], "f": 0, "t": 509, "d": [143], "a": 1 }, + { "px": [0,24], "src": [216,128], "f": 0, "t": 539, "d": [144], "a": 1 }, + { "px": [8,24], "src": [224,128], "f": 0, "t": 540, "d": [145], "a": 1 }, + { "px": [16,24], "src": [224,128], "f": 0, "t": 540, "d": [146], "a": 1 }, + { "px": [24,24], "src": [224,128], "f": 0, "t": 540, "d": [147], "a": 1 }, + { "px": [32,24], "src": [224,128], "f": 0, "t": 540, "d": [148], "a": 1 }, + { "px": [40,24], "src": [224,128], "f": 0, "t": 540, "d": [149], "a": 1 }, + { "px": [48,24], "src": [224,128], "f": 0, "t": 540, "d": [150], "a": 1 }, + { "px": [56,24], "src": [224,128], "f": 0, "t": 540, "d": [151], "a": 1 }, + { "px": [64,24], "src": [224,128], "f": 0, "t": 540, "d": [152], "a": 1 }, + { "px": [72,24], "src": [224,128], "f": 0, "t": 540, "d": [153], "a": 1 }, + { "px": [80,24], "src": [224,128], "f": 0, "t": 540, "d": [154], "a": 1 }, + { "px": [88,24], "src": [224,128], "f": 0, "t": 540, "d": [155], "a": 1 }, + { "px": [96,24], "src": [224,128], "f": 0, "t": 540, "d": [156], "a": 1 }, + { "px": [104,24], "src": [224,128], "f": 0, "t": 540, "d": [157], "a": 1 }, + { "px": [112,24], "src": [224,128], "f": 0, "t": 540, "d": [158], "a": 1 }, + { "px": [120,24], "src": [224,128], "f": 0, "t": 540, "d": [159], "a": 1 }, + { "px": [128,24], "src": [224,128], "f": 0, "t": 540, "d": [160], "a": 1 }, + { "px": [136,24], "src": [224,128], "f": 0, "t": 540, "d": [161], "a": 1 }, + { "px": [144,24], "src": [224,128], "f": 0, "t": 540, "d": [162], "a": 1 }, + { "px": [152,24], "src": [224,128], "f": 0, "t": 540, "d": [163], "a": 1 }, + { "px": [160,24], "src": [224,128], "f": 0, "t": 540, "d": [164], "a": 1 }, + { "px": [168,24], "src": [224,128], "f": 0, "t": 540, "d": [165], "a": 1 }, + { "px": [176,24], "src": [224,128], "f": 0, "t": 540, "d": [166], "a": 1 }, + { "px": [184,24], "src": [224,128], "f": 0, "t": 540, "d": [167], "a": 1 }, + { "px": [192,24], "src": [224,128], "f": 0, "t": 540, "d": [168], "a": 1 }, + { "px": [200,24], "src": [224,128], "f": 0, "t": 540, "d": [169], "a": 1 }, + { "px": [208,24], "src": [224,128], "f": 0, "t": 540, "d": [170], "a": 1 }, + { "px": [216,24], "src": [224,128], "f": 0, "t": 540, "d": [171], "a": 1 }, + { "px": [224,24], "src": [224,128], "f": 0, "t": 540, "d": [172], "a": 1 }, + { "px": [232,24], "src": [224,128], "f": 0, "t": 540, "d": [173], "a": 1 }, + { "px": [240,24], "src": [224,128], "f": 0, "t": 540, "d": [174], "a": 1 }, + { "px": [248,24], "src": [224,128], "f": 0, "t": 540, "d": [175], "a": 1 }, + { "px": [256,24], "src": [224,128], "f": 0, "t": 540, "d": [176], "a": 1 }, + { "px": [264,24], "src": [224,128], "f": 0, "t": 540, "d": [177], "a": 1 }, + { "px": [272,24], "src": [224,128], "f": 0, "t": 540, "d": [178], "a": 1 }, + { "px": [280,24], "src": [224,128], "f": 0, "t": 540, "d": [179], "a": 1 }, + { "px": [288,24], "src": [224,128], "f": 0, "t": 540, "d": [180], "a": 1 }, + { "px": [296,24], "src": [224,128], "f": 0, "t": 540, "d": [181], "a": 1 }, + { "px": [304,24], "src": [224,128], "f": 0, "t": 540, "d": [182], "a": 1 }, + { "px": [312,24], "src": [224,128], "f": 0, "t": 540, "d": [183], "a": 1 }, + { "px": [320,24], "src": [224,128], "f": 0, "t": 540, "d": [184], "a": 1 }, + { "px": [328,24], "src": [224,128], "f": 0, "t": 540, "d": [185], "a": 1 }, + { "px": [336,24], "src": [224,128], "f": 0, "t": 540, "d": [186], "a": 1 }, + { "px": [344,24], "src": [224,128], "f": 0, "t": 540, "d": [187], "a": 1 }, + { "px": [352,24], "src": [224,128], "f": 0, "t": 540, "d": [188], "a": 1 }, + { "px": [360,24], "src": [224,128], "f": 0, "t": 540, "d": [189], "a": 1 }, + { "px": [368,24], "src": [224,128], "f": 0, "t": 540, "d": [190], "a": 1 }, + { "px": [376,24], "src": [232,128], "f": 0, "t": 541, "d": [191], "a": 1 }, + { "px": [0,32], "src": [216,128], "f": 0, "t": 539, "d": [192], "a": 1 }, + { "px": [8,32], "src": [224,128], "f": 0, "t": 540, "d": [193], "a": 1 }, + { "px": [16,32], "src": [224,128], "f": 0, "t": 540, "d": [194], "a": 1 }, + { "px": [24,32], "src": [224,128], "f": 0, "t": 540, "d": [195], "a": 1 }, + { "px": [32,32], "src": [224,128], "f": 0, "t": 540, "d": [196], "a": 1 }, + { "px": [40,32], "src": [224,128], "f": 0, "t": 540, "d": [197], "a": 1 }, + { "px": [48,32], "src": [224,128], "f": 0, "t": 540, "d": [198], "a": 1 }, + { "px": [56,32], "src": [224,128], "f": 0, "t": 540, "d": [199], "a": 1 }, + { "px": [64,32], "src": [224,128], "f": 0, "t": 540, "d": [200], "a": 1 }, + { "px": [72,32], "src": [224,128], "f": 0, "t": 540, "d": [201], "a": 1 }, + { "px": [80,32], "src": [224,128], "f": 0, "t": 540, "d": [202], "a": 1 }, + { "px": [88,32], "src": [224,128], "f": 0, "t": 540, "d": [203], "a": 1 }, + { "px": [96,32], "src": [224,128], "f": 0, "t": 540, "d": [204], "a": 1 }, + { "px": [104,32], "src": [224,128], "f": 0, "t": 540, "d": [205], "a": 1 }, + { "px": [112,32], "src": [224,128], "f": 0, "t": 540, "d": [206], "a": 1 }, + { "px": [120,32], "src": [224,128], "f": 0, "t": 540, "d": [207], "a": 1 }, + { "px": [128,32], "src": [224,128], "f": 0, "t": 540, "d": [208], "a": 1 }, + { "px": [136,32], "src": [224,128], "f": 0, "t": 540, "d": [209], "a": 1 }, + { "px": [144,32], "src": [224,128], "f": 0, "t": 540, "d": [210], "a": 1 }, + { "px": [152,32], "src": [224,128], "f": 0, "t": 540, "d": [211], "a": 1 }, + { "px": [160,32], "src": [224,128], "f": 0, "t": 540, "d": [212], "a": 1 }, + { "px": [168,32], "src": [224,128], "f": 0, "t": 540, "d": [213], "a": 1 }, + { "px": [176,32], "src": [224,128], "f": 0, "t": 540, "d": [214], "a": 1 }, + { "px": [184,32], "src": [224,128], "f": 0, "t": 540, "d": [215], "a": 1 }, + { "px": [192,32], "src": [224,128], "f": 0, "t": 540, "d": [216], "a": 1 }, + { "px": [200,32], "src": [224,128], "f": 0, "t": 540, "d": [217], "a": 1 }, + { "px": [208,32], "src": [224,128], "f": 0, "t": 540, "d": [218], "a": 1 }, + { "px": [216,32], "src": [224,128], "f": 0, "t": 540, "d": [219], "a": 1 }, + { "px": [224,32], "src": [224,128], "f": 0, "t": 540, "d": [220], "a": 1 }, + { "px": [232,32], "src": [224,128], "f": 0, "t": 540, "d": [221], "a": 1 }, + { "px": [240,32], "src": [224,128], "f": 0, "t": 540, "d": [222], "a": 1 }, + { "px": [248,32], "src": [224,128], "f": 0, "t": 540, "d": [223], "a": 1 }, + { "px": [256,32], "src": [224,128], "f": 0, "t": 540, "d": [224], "a": 1 }, + { "px": [264,32], "src": [224,128], "f": 0, "t": 540, "d": [225], "a": 1 }, + { "px": [272,32], "src": [224,128], "f": 0, "t": 540, "d": [226], "a": 1 }, + { "px": [280,32], "src": [224,128], "f": 0, "t": 540, "d": [227], "a": 1 }, + { "px": [288,32], "src": [224,128], "f": 0, "t": 540, "d": [228], "a": 1 }, + { "px": [296,32], "src": [224,128], "f": 0, "t": 540, "d": [229], "a": 1 }, + { "px": [304,32], "src": [224,128], "f": 0, "t": 540, "d": [230], "a": 1 }, + { "px": [312,32], "src": [224,128], "f": 0, "t": 540, "d": [231], "a": 1 }, + { "px": [320,32], "src": [224,128], "f": 0, "t": 540, "d": [232], "a": 1 }, + { "px": [328,32], "src": [224,128], "f": 0, "t": 540, "d": [233], "a": 1 }, + { "px": [336,32], "src": [224,128], "f": 0, "t": 540, "d": [234], "a": 1 }, + { "px": [344,32], "src": [224,128], "f": 0, "t": 540, "d": [235], "a": 1 }, + { "px": [352,32], "src": [224,128], "f": 0, "t": 540, "d": [236], "a": 1 }, + { "px": [360,32], "src": [224,128], "f": 0, "t": 540, "d": [237], "a": 1 }, + { "px": [368,32], "src": [224,128], "f": 0, "t": 540, "d": [238], "a": 1 }, + { "px": [376,32], "src": [232,128], "f": 0, "t": 541, "d": [239], "a": 1 }, + { "px": [0,40], "src": [216,128], "f": 0, "t": 539, "d": [240], "a": 1 }, + { "px": [8,40], "src": [224,128], "f": 0, "t": 540, "d": [241], "a": 1 }, + { "px": [16,40], "src": [224,128], "f": 0, "t": 540, "d": [242], "a": 1 }, + { "px": [24,40], "src": [224,128], "f": 0, "t": 540, "d": [243], "a": 1 }, + { "px": [32,40], "src": [224,128], "f": 0, "t": 540, "d": [244], "a": 1 }, + { "px": [40,40], "src": [224,128], "f": 0, "t": 540, "d": [245], "a": 1 }, + { "px": [48,40], "src": [224,128], "f": 0, "t": 540, "d": [246], "a": 1 }, + { "px": [56,40], "src": [224,128], "f": 0, "t": 540, "d": [247], "a": 1 }, + { "px": [64,40], "src": [224,128], "f": 0, "t": 540, "d": [248], "a": 1 }, + { "px": [72,40], "src": [224,128], "f": 0, "t": 540, "d": [249], "a": 1 }, + { "px": [80,40], "src": [224,128], "f": 0, "t": 540, "d": [250], "a": 1 }, + { "px": [88,40], "src": [224,128], "f": 0, "t": 540, "d": [251], "a": 1 }, + { "px": [96,40], "src": [224,128], "f": 0, "t": 540, "d": [252], "a": 1 }, + { "px": [104,40], "src": [224,128], "f": 0, "t": 540, "d": [253], "a": 1 }, + { "px": [112,40], "src": [224,128], "f": 0, "t": 540, "d": [254], "a": 1 }, + { "px": [120,40], "src": [224,128], "f": 0, "t": 540, "d": [255], "a": 1 }, + { "px": [128,40], "src": [224,128], "f": 0, "t": 540, "d": [256], "a": 1 }, + { "px": [136,40], "src": [224,128], "f": 0, "t": 540, "d": [257], "a": 1 }, + { "px": [144,40], "src": [224,128], "f": 0, "t": 540, "d": [258], "a": 1 }, + { "px": [152,40], "src": [224,128], "f": 0, "t": 540, "d": [259], "a": 1 }, + { "px": [160,40], "src": [224,128], "f": 0, "t": 540, "d": [260], "a": 1 }, + { "px": [168,40], "src": [224,128], "f": 0, "t": 540, "d": [261], "a": 1 }, + { "px": [176,40], "src": [224,128], "f": 0, "t": 540, "d": [262], "a": 1 }, + { "px": [184,40], "src": [224,128], "f": 0, "t": 540, "d": [263], "a": 1 }, + { "px": [192,40], "src": [224,128], "f": 0, "t": 540, "d": [264], "a": 1 }, + { "px": [200,40], "src": [224,128], "f": 0, "t": 540, "d": [265], "a": 1 }, + { "px": [208,40], "src": [224,128], "f": 0, "t": 540, "d": [266], "a": 1 }, + { "px": [216,40], "src": [224,128], "f": 0, "t": 540, "d": [267], "a": 1 }, + { "px": [224,40], "src": [224,128], "f": 0, "t": 540, "d": [268], "a": 1 }, + { "px": [232,40], "src": [224,128], "f": 0, "t": 540, "d": [269], "a": 1 }, + { "px": [240,40], "src": [224,128], "f": 0, "t": 540, "d": [270], "a": 1 }, + { "px": [248,40], "src": [224,128], "f": 0, "t": 540, "d": [271], "a": 1 }, + { "px": [256,40], "src": [224,128], "f": 0, "t": 540, "d": [272], "a": 1 }, + { "px": [264,40], "src": [224,128], "f": 0, "t": 540, "d": [273], "a": 1 }, + { "px": [272,40], "src": [224,128], "f": 0, "t": 540, "d": [274], "a": 1 }, + { "px": [280,40], "src": [224,128], "f": 0, "t": 540, "d": [275], "a": 1 }, + { "px": [288,40], "src": [224,128], "f": 0, "t": 540, "d": [276], "a": 1 }, + { "px": [296,40], "src": [224,128], "f": 0, "t": 540, "d": [277], "a": 1 }, + { "px": [304,40], "src": [224,128], "f": 0, "t": 540, "d": [278], "a": 1 }, + { "px": [312,40], "src": [224,128], "f": 0, "t": 540, "d": [279], "a": 1 }, + { "px": [320,40], "src": [224,128], "f": 0, "t": 540, "d": [280], "a": 1 }, + { "px": [328,40], "src": [224,128], "f": 0, "t": 540, "d": [281], "a": 1 }, + { "px": [336,40], "src": [224,128], "f": 0, "t": 540, "d": [282], "a": 1 }, + { "px": [344,40], "src": [224,128], "f": 0, "t": 540, "d": [283], "a": 1 }, + { "px": [352,40], "src": [224,128], "f": 0, "t": 540, "d": [284], "a": 1 }, + { "px": [360,40], "src": [224,128], "f": 0, "t": 540, "d": [285], "a": 1 }, + { "px": [368,40], "src": [224,128], "f": 0, "t": 540, "d": [286], "a": 1 }, + { "px": [376,40], "src": [232,128], "f": 0, "t": 541, "d": [287], "a": 1 }, + { "px": [0,48], "src": [216,128], "f": 0, "t": 539, "d": [288], "a": 1 }, + { "px": [8,48], "src": [224,128], "f": 0, "t": 540, "d": [289], "a": 1 }, + { "px": [16,48], "src": [224,128], "f": 0, "t": 540, "d": [290], "a": 1 }, + { "px": [24,48], "src": [224,128], "f": 0, "t": 540, "d": [291], "a": 1 }, + { "px": [32,48], "src": [224,128], "f": 0, "t": 540, "d": [292], "a": 1 }, + { "px": [40,48], "src": [224,128], "f": 0, "t": 540, "d": [293], "a": 1 }, + { "px": [48,48], "src": [224,128], "f": 0, "t": 540, "d": [294], "a": 1 }, + { "px": [56,48], "src": [224,128], "f": 0, "t": 540, "d": [295], "a": 1 }, + { "px": [64,48], "src": [224,128], "f": 0, "t": 540, "d": [296], "a": 1 }, + { "px": [72,48], "src": [224,128], "f": 0, "t": 540, "d": [297], "a": 1 }, + { "px": [80,48], "src": [224,128], "f": 0, "t": 540, "d": [298], "a": 1 }, + { "px": [88,48], "src": [224,128], "f": 0, "t": 540, "d": [299], "a": 1 }, + { "px": [96,48], "src": [224,128], "f": 0, "t": 540, "d": [300], "a": 1 }, + { "px": [104,48], "src": [224,128], "f": 0, "t": 540, "d": [301], "a": 1 }, + { "px": [112,48], "src": [224,128], "f": 0, "t": 540, "d": [302], "a": 1 }, + { "px": [120,48], "src": [224,128], "f": 0, "t": 540, "d": [303], "a": 1 }, + { "px": [128,48], "src": [224,128], "f": 0, "t": 540, "d": [304], "a": 1 }, + { "px": [136,48], "src": [224,128], "f": 0, "t": 540, "d": [305], "a": 1 }, + { "px": [144,48], "src": [224,128], "f": 0, "t": 540, "d": [306], "a": 1 }, + { "px": [152,48], "src": [224,128], "f": 0, "t": 540, "d": [307], "a": 1 }, + { "px": [160,48], "src": [224,128], "f": 0, "t": 540, "d": [308], "a": 1 }, + { "px": [168,48], "src": [224,128], "f": 0, "t": 540, "d": [309], "a": 1 }, + { "px": [176,48], "src": [224,128], "f": 0, "t": 540, "d": [310], "a": 1 }, + { "px": [184,48], "src": [224,128], "f": 0, "t": 540, "d": [311], "a": 1 }, + { "px": [192,48], "src": [224,128], "f": 0, "t": 540, "d": [312], "a": 1 }, + { "px": [200,48], "src": [224,128], "f": 0, "t": 540, "d": [313], "a": 1 }, + { "px": [208,48], "src": [224,128], "f": 0, "t": 540, "d": [314], "a": 1 }, + { "px": [216,48], "src": [224,128], "f": 0, "t": 540, "d": [315], "a": 1 }, + { "px": [224,48], "src": [224,128], "f": 0, "t": 540, "d": [316], "a": 1 }, + { "px": [232,48], "src": [224,128], "f": 0, "t": 540, "d": [317], "a": 1 }, + { "px": [240,48], "src": [224,128], "f": 0, "t": 540, "d": [318], "a": 1 }, + { "px": [248,48], "src": [224,128], "f": 0, "t": 540, "d": [319], "a": 1 }, + { "px": [256,48], "src": [224,128], "f": 0, "t": 540, "d": [320], "a": 1 }, + { "px": [264,48], "src": [224,128], "f": 0, "t": 540, "d": [321], "a": 1 }, + { "px": [272,48], "src": [224,128], "f": 0, "t": 540, "d": [322], "a": 1 }, + { "px": [280,48], "src": [224,128], "f": 0, "t": 540, "d": [323], "a": 1 }, + { "px": [288,48], "src": [224,128], "f": 0, "t": 540, "d": [324], "a": 1 }, + { "px": [296,48], "src": [224,128], "f": 0, "t": 540, "d": [325], "a": 1 }, + { "px": [304,48], "src": [224,128], "f": 0, "t": 540, "d": [326], "a": 1 }, + { "px": [312,48], "src": [224,128], "f": 0, "t": 540, "d": [327], "a": 1 }, + { "px": [320,48], "src": [224,128], "f": 0, "t": 540, "d": [328], "a": 1 }, + { "px": [328,48], "src": [224,128], "f": 0, "t": 540, "d": [329], "a": 1 }, + { "px": [336,48], "src": [224,128], "f": 0, "t": 540, "d": [330], "a": 1 }, + { "px": [344,48], "src": [224,128], "f": 0, "t": 540, "d": [331], "a": 1 }, + { "px": [352,48], "src": [224,128], "f": 0, "t": 540, "d": [332], "a": 1 }, + { "px": [360,48], "src": [224,128], "f": 0, "t": 540, "d": [333], "a": 1 }, + { "px": [368,48], "src": [224,128], "f": 0, "t": 540, "d": [334], "a": 1 }, + { "px": [376,48], "src": [232,128], "f": 0, "t": 541, "d": [335], "a": 1 }, + { "px": [0,56], "src": [216,128], "f": 0, "t": 539, "d": [336], "a": 1 }, + { "px": [8,56], "src": [224,128], "f": 0, "t": 540, "d": [337], "a": 1 }, + { "px": [16,56], "src": [224,128], "f": 0, "t": 540, "d": [338], "a": 1 }, + { "px": [24,56], "src": [224,128], "f": 0, "t": 540, "d": [339], "a": 1 }, + { "px": [32,56], "src": [224,128], "f": 0, "t": 540, "d": [340], "a": 1 }, + { "px": [40,56], "src": [224,128], "f": 0, "t": 540, "d": [341], "a": 1 }, + { "px": [48,56], "src": [224,128], "f": 0, "t": 540, "d": [342], "a": 1 }, + { "px": [56,56], "src": [224,128], "f": 0, "t": 540, "d": [343], "a": 1 }, + { "px": [64,56], "src": [224,128], "f": 0, "t": 540, "d": [344], "a": 1 }, + { "px": [72,56], "src": [224,128], "f": 0, "t": 540, "d": [345], "a": 1 }, + { "px": [80,56], "src": [224,128], "f": 0, "t": 540, "d": [346], "a": 1 }, + { "px": [88,56], "src": [224,128], "f": 0, "t": 540, "d": [347], "a": 1 }, + { "px": [96,56], "src": [224,128], "f": 0, "t": 540, "d": [348], "a": 1 }, + { "px": [104,56], "src": [224,128], "f": 0, "t": 540, "d": [349], "a": 1 }, + { "px": [112,56], "src": [224,128], "f": 0, "t": 540, "d": [350], "a": 1 }, + { "px": [120,56], "src": [224,128], "f": 0, "t": 540, "d": [351], "a": 1 }, + { "px": [128,56], "src": [224,128], "f": 0, "t": 540, "d": [352], "a": 1 }, + { "px": [136,56], "src": [224,128], "f": 0, "t": 540, "d": [353], "a": 1 }, + { "px": [144,56], "src": [224,128], "f": 0, "t": 540, "d": [354], "a": 1 }, + { "px": [152,56], "src": [224,128], "f": 0, "t": 540, "d": [355], "a": 1 }, + { "px": [160,56], "src": [224,128], "f": 0, "t": 540, "d": [356], "a": 1 }, + { "px": [168,56], "src": [224,128], "f": 0, "t": 540, "d": [357], "a": 1 }, + { "px": [176,56], "src": [224,128], "f": 0, "t": 540, "d": [358], "a": 1 }, + { "px": [184,56], "src": [224,128], "f": 0, "t": 540, "d": [359], "a": 1 }, + { "px": [192,56], "src": [224,128], "f": 0, "t": 540, "d": [360], "a": 1 }, + { "px": [200,56], "src": [224,128], "f": 0, "t": 540, "d": [361], "a": 1 }, + { "px": [208,56], "src": [224,128], "f": 0, "t": 540, "d": [362], "a": 1 }, + { "px": [216,56], "src": [224,128], "f": 0, "t": 540, "d": [363], "a": 1 }, + { "px": [224,56], "src": [224,128], "f": 0, "t": 540, "d": [364], "a": 1 }, + { "px": [232,56], "src": [224,128], "f": 0, "t": 540, "d": [365], "a": 1 }, + { "px": [240,56], "src": [224,128], "f": 0, "t": 540, "d": [366], "a": 1 }, + { "px": [248,56], "src": [224,128], "f": 0, "t": 540, "d": [367], "a": 1 }, + { "px": [256,56], "src": [224,128], "f": 0, "t": 540, "d": [368], "a": 1 }, + { "px": [264,56], "src": [224,128], "f": 0, "t": 540, "d": [369], "a": 1 }, + { "px": [272,56], "src": [224,128], "f": 0, "t": 540, "d": [370], "a": 1 }, + { "px": [280,56], "src": [224,128], "f": 0, "t": 540, "d": [371], "a": 1 }, + { "px": [288,56], "src": [224,128], "f": 0, "t": 540, "d": [372], "a": 1 }, + { "px": [296,56], "src": [224,128], "f": 0, "t": 540, "d": [373], "a": 1 }, + { "px": [304,56], "src": [224,128], "f": 0, "t": 540, "d": [374], "a": 1 }, + { "px": [312,56], "src": [224,128], "f": 0, "t": 540, "d": [375], "a": 1 }, + { "px": [320,56], "src": [224,128], "f": 0, "t": 540, "d": [376], "a": 1 }, + { "px": [328,56], "src": [224,128], "f": 0, "t": 540, "d": [377], "a": 1 }, + { "px": [336,56], "src": [224,128], "f": 0, "t": 540, "d": [378], "a": 1 }, + { "px": [344,56], "src": [224,128], "f": 0, "t": 540, "d": [379], "a": 1 }, + { "px": [352,56], "src": [224,128], "f": 0, "t": 540, "d": [380], "a": 1 }, + { "px": [360,56], "src": [224,128], "f": 0, "t": 540, "d": [381], "a": 1 }, + { "px": [368,56], "src": [224,128], "f": 0, "t": 540, "d": [382], "a": 1 }, + { "px": [376,56], "src": [232,128], "f": 0, "t": 541, "d": [383], "a": 1 }, + { "px": [0,64], "src": [216,128], "f": 0, "t": 539, "d": [384], "a": 1 }, + { "px": [8,64], "src": [224,128], "f": 0, "t": 540, "d": [385], "a": 1 }, + { "px": [16,64], "src": [224,128], "f": 0, "t": 540, "d": [386], "a": 1 }, + { "px": [24,64], "src": [224,128], "f": 0, "t": 540, "d": [387], "a": 1 }, + { "px": [32,64], "src": [224,128], "f": 0, "t": 540, "d": [388], "a": 1 }, + { "px": [40,64], "src": [224,128], "f": 0, "t": 540, "d": [389], "a": 1 }, + { "px": [48,64], "src": [224,128], "f": 0, "t": 540, "d": [390], "a": 1 }, + { "px": [56,64], "src": [224,128], "f": 0, "t": 540, "d": [391], "a": 1 }, + { "px": [64,64], "src": [224,128], "f": 0, "t": 540, "d": [392], "a": 1 }, + { "px": [72,64], "src": [224,128], "f": 0, "t": 540, "d": [393], "a": 1 }, + { "px": [80,64], "src": [224,128], "f": 0, "t": 540, "d": [394], "a": 1 }, + { "px": [88,64], "src": [224,128], "f": 0, "t": 540, "d": [395], "a": 1 }, + { "px": [96,64], "src": [224,128], "f": 0, "t": 540, "d": [396], "a": 1 }, + { "px": [104,64], "src": [224,128], "f": 0, "t": 540, "d": [397], "a": 1 }, + { "px": [112,64], "src": [224,128], "f": 0, "t": 540, "d": [398], "a": 1 }, + { "px": [120,64], "src": [224,128], "f": 0, "t": 540, "d": [399], "a": 1 }, + { "px": [128,64], "src": [224,128], "f": 0, "t": 540, "d": [400], "a": 1 }, + { "px": [136,64], "src": [224,128], "f": 0, "t": 540, "d": [401], "a": 1 }, + { "px": [144,64], "src": [224,128], "f": 0, "t": 540, "d": [402], "a": 1 }, + { "px": [152,64], "src": [224,128], "f": 0, "t": 540, "d": [403], "a": 1 }, + { "px": [160,64], "src": [224,128], "f": 0, "t": 540, "d": [404], "a": 1 }, + { "px": [168,64], "src": [224,128], "f": 0, "t": 540, "d": [405], "a": 1 }, + { "px": [176,64], "src": [224,128], "f": 0, "t": 540, "d": [406], "a": 1 }, + { "px": [184,64], "src": [224,128], "f": 0, "t": 540, "d": [407], "a": 1 }, + { "px": [192,64], "src": [224,128], "f": 0, "t": 540, "d": [408], "a": 1 }, + { "px": [200,64], "src": [224,128], "f": 0, "t": 540, "d": [409], "a": 1 }, + { "px": [208,64], "src": [224,128], "f": 0, "t": 540, "d": [410], "a": 1 }, + { "px": [216,64], "src": [224,128], "f": 0, "t": 540, "d": [411], "a": 1 }, + { "px": [224,64], "src": [224,128], "f": 0, "t": 540, "d": [412], "a": 1 }, + { "px": [232,64], "src": [224,128], "f": 0, "t": 540, "d": [413], "a": 1 }, + { "px": [240,64], "src": [224,128], "f": 0, "t": 540, "d": [414], "a": 1 }, + { "px": [248,64], "src": [224,128], "f": 0, "t": 540, "d": [415], "a": 1 }, + { "px": [256,64], "src": [224,128], "f": 0, "t": 540, "d": [416], "a": 1 }, + { "px": [264,64], "src": [224,128], "f": 0, "t": 540, "d": [417], "a": 1 }, + { "px": [272,64], "src": [224,128], "f": 0, "t": 540, "d": [418], "a": 1 }, + { "px": [280,64], "src": [224,128], "f": 0, "t": 540, "d": [419], "a": 1 }, + { "px": [288,64], "src": [224,128], "f": 0, "t": 540, "d": [420], "a": 1 }, + { "px": [296,64], "src": [224,128], "f": 0, "t": 540, "d": [421], "a": 1 }, + { "px": [304,64], "src": [224,128], "f": 0, "t": 540, "d": [422], "a": 1 }, + { "px": [312,64], "src": [224,128], "f": 0, "t": 540, "d": [423], "a": 1 }, + { "px": [320,64], "src": [224,128], "f": 0, "t": 540, "d": [424], "a": 1 }, + { "px": [328,64], "src": [224,128], "f": 0, "t": 540, "d": [425], "a": 1 }, + { "px": [336,64], "src": [224,128], "f": 0, "t": 540, "d": [426], "a": 1 }, + { "px": [344,64], "src": [224,128], "f": 0, "t": 540, "d": [427], "a": 1 }, + { "px": [352,64], "src": [224,128], "f": 0, "t": 540, "d": [428], "a": 1 }, + { "px": [360,64], "src": [224,128], "f": 0, "t": 540, "d": [429], "a": 1 }, + { "px": [368,64], "src": [224,128], "f": 0, "t": 540, "d": [430], "a": 1 }, + { "px": [376,64], "src": [232,128], "f": 0, "t": 541, "d": [431], "a": 1 }, + { "px": [0,72], "src": [216,128], "f": 0, "t": 539, "d": [432], "a": 1 }, + { "px": [8,72], "src": [224,128], "f": 0, "t": 540, "d": [433], "a": 1 }, + { "px": [16,72], "src": [224,128], "f": 0, "t": 540, "d": [434], "a": 1 }, + { "px": [24,72], "src": [224,128], "f": 0, "t": 540, "d": [435], "a": 1 }, + { "px": [32,72], "src": [224,128], "f": 0, "t": 540, "d": [436], "a": 1 }, + { "px": [40,72], "src": [224,128], "f": 0, "t": 540, "d": [437], "a": 1 }, + { "px": [48,72], "src": [224,128], "f": 0, "t": 540, "d": [438], "a": 1 }, + { "px": [56,72], "src": [224,128], "f": 0, "t": 540, "d": [439], "a": 1 }, + { "px": [64,72], "src": [224,128], "f": 0, "t": 540, "d": [440], "a": 1 }, + { "px": [72,72], "src": [224,128], "f": 0, "t": 540, "d": [441], "a": 1 }, + { "px": [80,72], "src": [224,128], "f": 0, "t": 540, "d": [442], "a": 1 }, + { "px": [88,72], "src": [224,128], "f": 0, "t": 540, "d": [443], "a": 1 }, + { "px": [96,72], "src": [224,128], "f": 0, "t": 540, "d": [444], "a": 1 }, + { "px": [104,72], "src": [224,128], "f": 0, "t": 540, "d": [445], "a": 1 }, + { "px": [112,72], "src": [224,128], "f": 0, "t": 540, "d": [446], "a": 1 }, + { "px": [120,72], "src": [224,128], "f": 0, "t": 540, "d": [447], "a": 1 }, + { "px": [128,72], "src": [224,128], "f": 0, "t": 540, "d": [448], "a": 1 }, + { "px": [136,72], "src": [224,128], "f": 0, "t": 540, "d": [449], "a": 1 }, + { "px": [144,72], "src": [224,128], "f": 0, "t": 540, "d": [450], "a": 1 }, + { "px": [152,72], "src": [224,128], "f": 0, "t": 540, "d": [451], "a": 1 }, + { "px": [160,72], "src": [224,128], "f": 0, "t": 540, "d": [452], "a": 1 }, + { "px": [168,72], "src": [224,128], "f": 0, "t": 540, "d": [453], "a": 1 }, + { "px": [176,72], "src": [224,128], "f": 0, "t": 540, "d": [454], "a": 1 }, + { "px": [184,72], "src": [224,128], "f": 0, "t": 540, "d": [455], "a": 1 }, + { "px": [192,72], "src": [224,128], "f": 0, "t": 540, "d": [456], "a": 1 }, + { "px": [200,72], "src": [224,128], "f": 0, "t": 540, "d": [457], "a": 1 }, + { "px": [208,72], "src": [224,128], "f": 0, "t": 540, "d": [458], "a": 1 }, + { "px": [216,72], "src": [224,128], "f": 0, "t": 540, "d": [459], "a": 1 }, + { "px": [224,72], "src": [224,128], "f": 0, "t": 540, "d": [460], "a": 1 }, + { "px": [232,72], "src": [224,128], "f": 0, "t": 540, "d": [461], "a": 1 }, + { "px": [240,72], "src": [224,128], "f": 0, "t": 540, "d": [462], "a": 1 }, + { "px": [248,72], "src": [224,128], "f": 0, "t": 540, "d": [463], "a": 1 }, + { "px": [256,72], "src": [224,128], "f": 0, "t": 540, "d": [464], "a": 1 }, + { "px": [264,72], "src": [224,128], "f": 0, "t": 540, "d": [465], "a": 1 }, + { "px": [272,72], "src": [224,128], "f": 0, "t": 540, "d": [466], "a": 1 }, + { "px": [280,72], "src": [224,128], "f": 0, "t": 540, "d": [467], "a": 1 }, + { "px": [288,72], "src": [224,128], "f": 0, "t": 540, "d": [468], "a": 1 }, + { "px": [296,72], "src": [224,128], "f": 0, "t": 540, "d": [469], "a": 1 }, + { "px": [304,72], "src": [224,128], "f": 0, "t": 540, "d": [470], "a": 1 }, + { "px": [312,72], "src": [224,128], "f": 0, "t": 540, "d": [471], "a": 1 }, + { "px": [320,72], "src": [224,128], "f": 0, "t": 540, "d": [472], "a": 1 }, + { "px": [328,72], "src": [224,128], "f": 0, "t": 540, "d": [473], "a": 1 }, + { "px": [336,72], "src": [224,128], "f": 0, "t": 540, "d": [474], "a": 1 }, + { "px": [344,72], "src": [224,128], "f": 0, "t": 540, "d": [475], "a": 1 }, + { "px": [352,72], "src": [224,128], "f": 0, "t": 540, "d": [476], "a": 1 }, + { "px": [360,72], "src": [224,128], "f": 0, "t": 540, "d": [477], "a": 1 }, + { "px": [368,72], "src": [224,128], "f": 0, "t": 540, "d": [478], "a": 1 }, + { "px": [376,72], "src": [232,128], "f": 0, "t": 541, "d": [479], "a": 1 }, + { "px": [0,80], "src": [216,128], "f": 0, "t": 539, "d": [480], "a": 1 }, + { "px": [8,80], "src": [224,128], "f": 0, "t": 540, "d": [481], "a": 1 }, + { "px": [16,80], "src": [224,128], "f": 0, "t": 540, "d": [482], "a": 1 }, + { "px": [24,80], "src": [224,128], "f": 0, "t": 540, "d": [483], "a": 1 }, + { "px": [32,80], "src": [224,128], "f": 0, "t": 540, "d": [484], "a": 1 }, + { "px": [40,80], "src": [224,128], "f": 0, "t": 540, "d": [485], "a": 1 }, + { "px": [48,80], "src": [224,128], "f": 0, "t": 540, "d": [486], "a": 1 }, + { "px": [56,80], "src": [224,128], "f": 0, "t": 540, "d": [487], "a": 1 }, + { "px": [64,80], "src": [224,128], "f": 0, "t": 540, "d": [488], "a": 1 }, + { "px": [72,80], "src": [224,128], "f": 0, "t": 540, "d": [489], "a": 1 }, + { "px": [80,80], "src": [224,128], "f": 0, "t": 540, "d": [490], "a": 1 }, + { "px": [88,80], "src": [224,128], "f": 0, "t": 540, "d": [491], "a": 1 }, + { "px": [96,80], "src": [224,128], "f": 0, "t": 540, "d": [492], "a": 1 }, + { "px": [104,80], "src": [224,128], "f": 0, "t": 540, "d": [493], "a": 1 }, + { "px": [112,80], "src": [224,128], "f": 0, "t": 540, "d": [494], "a": 1 }, + { "px": [120,80], "src": [224,128], "f": 0, "t": 540, "d": [495], "a": 1 }, + { "px": [128,80], "src": [224,128], "f": 0, "t": 540, "d": [496], "a": 1 }, + { "px": [136,80], "src": [224,128], "f": 0, "t": 540, "d": [497], "a": 1 }, + { "px": [144,80], "src": [224,128], "f": 0, "t": 540, "d": [498], "a": 1 }, + { "px": [152,80], "src": [224,128], "f": 0, "t": 540, "d": [499], "a": 1 }, + { "px": [160,80], "src": [224,128], "f": 0, "t": 540, "d": [500], "a": 1 }, + { "px": [168,80], "src": [224,128], "f": 0, "t": 540, "d": [501], "a": 1 }, + { "px": [176,80], "src": [224,128], "f": 0, "t": 540, "d": [502], "a": 1 }, + { "px": [184,80], "src": [224,128], "f": 0, "t": 540, "d": [503], "a": 1 }, + { "px": [192,80], "src": [224,128], "f": 0, "t": 540, "d": [504], "a": 1 }, + { "px": [200,80], "src": [224,128], "f": 0, "t": 540, "d": [505], "a": 1 }, + { "px": [208,80], "src": [224,128], "f": 0, "t": 540, "d": [506], "a": 1 }, + { "px": [216,80], "src": [224,128], "f": 0, "t": 540, "d": [507], "a": 1 }, + { "px": [224,80], "src": [224,128], "f": 0, "t": 540, "d": [508], "a": 1 }, + { "px": [232,80], "src": [224,128], "f": 0, "t": 540, "d": [509], "a": 1 }, + { "px": [240,80], "src": [224,128], "f": 0, "t": 540, "d": [510], "a": 1 }, + { "px": [248,80], "src": [224,128], "f": 0, "t": 540, "d": [511], "a": 1 }, + { "px": [256,80], "src": [224,128], "f": 0, "t": 540, "d": [512], "a": 1 }, + { "px": [264,80], "src": [224,128], "f": 0, "t": 540, "d": [513], "a": 1 }, + { "px": [272,80], "src": [224,128], "f": 0, "t": 540, "d": [514], "a": 1 }, + { "px": [280,80], "src": [224,128], "f": 0, "t": 540, "d": [515], "a": 1 }, + { "px": [288,80], "src": [224,128], "f": 0, "t": 540, "d": [516], "a": 1 }, + { "px": [296,80], "src": [224,128], "f": 0, "t": 540, "d": [517], "a": 1 }, + { "px": [304,80], "src": [224,128], "f": 0, "t": 540, "d": [518], "a": 1 }, + { "px": [312,80], "src": [224,128], "f": 0, "t": 540, "d": [519], "a": 1 }, + { "px": [320,80], "src": [224,128], "f": 0, "t": 540, "d": [520], "a": 1 }, + { "px": [328,80], "src": [224,128], "f": 0, "t": 540, "d": [521], "a": 1 }, + { "px": [336,80], "src": [224,128], "f": 0, "t": 540, "d": [522], "a": 1 }, + { "px": [344,80], "src": [224,128], "f": 0, "t": 540, "d": [523], "a": 1 }, + { "px": [352,80], "src": [224,128], "f": 0, "t": 540, "d": [524], "a": 1 }, + { "px": [360,80], "src": [224,128], "f": 0, "t": 540, "d": [525], "a": 1 }, + { "px": [368,80], "src": [224,128], "f": 0, "t": 540, "d": [526], "a": 1 }, + { "px": [376,80], "src": [232,128], "f": 0, "t": 541, "d": [527], "a": 1 }, + { "px": [0,88], "src": [216,128], "f": 0, "t": 539, "d": [528], "a": 1 }, + { "px": [8,88], "src": [224,128], "f": 0, "t": 540, "d": [529], "a": 1 }, + { "px": [16,88], "src": [224,128], "f": 0, "t": 540, "d": [530], "a": 1 }, + { "px": [24,88], "src": [224,128], "f": 0, "t": 540, "d": [531], "a": 1 }, + { "px": [32,88], "src": [224,128], "f": 0, "t": 540, "d": [532], "a": 1 }, + { "px": [40,88], "src": [224,128], "f": 0, "t": 540, "d": [533], "a": 1 }, + { "px": [48,88], "src": [224,128], "f": 0, "t": 540, "d": [534], "a": 1 }, + { "px": [56,88], "src": [224,128], "f": 0, "t": 540, "d": [535], "a": 1 }, + { "px": [64,88], "src": [224,128], "f": 0, "t": 540, "d": [536], "a": 1 }, + { "px": [72,88], "src": [224,128], "f": 0, "t": 540, "d": [537], "a": 1 }, + { "px": [80,88], "src": [224,128], "f": 0, "t": 540, "d": [538], "a": 1 }, + { "px": [88,88], "src": [224,128], "f": 0, "t": 540, "d": [539], "a": 1 }, + { "px": [96,88], "src": [224,128], "f": 0, "t": 540, "d": [540], "a": 1 }, + { "px": [104,88], "src": [224,128], "f": 0, "t": 540, "d": [541], "a": 1 }, + { "px": [112,88], "src": [224,128], "f": 0, "t": 540, "d": [542], "a": 1 }, + { "px": [120,88], "src": [224,128], "f": 0, "t": 540, "d": [543], "a": 1 }, + { "px": [128,88], "src": [224,128], "f": 0, "t": 540, "d": [544], "a": 1 }, + { "px": [136,88], "src": [224,128], "f": 0, "t": 540, "d": [545], "a": 1 }, + { "px": [144,88], "src": [224,128], "f": 0, "t": 540, "d": [546], "a": 1 }, + { "px": [152,88], "src": [224,128], "f": 0, "t": 540, "d": [547], "a": 1 }, + { "px": [160,88], "src": [224,128], "f": 0, "t": 540, "d": [548], "a": 1 }, + { "px": [168,88], "src": [224,128], "f": 0, "t": 540, "d": [549], "a": 1 }, + { "px": [176,88], "src": [224,128], "f": 0, "t": 540, "d": [550], "a": 1 }, + { "px": [184,88], "src": [224,128], "f": 0, "t": 540, "d": [551], "a": 1 }, + { "px": [192,88], "src": [224,128], "f": 0, "t": 540, "d": [552], "a": 1 }, + { "px": [200,88], "src": [224,128], "f": 0, "t": 540, "d": [553], "a": 1 }, + { "px": [208,88], "src": [224,128], "f": 0, "t": 540, "d": [554], "a": 1 }, + { "px": [216,88], "src": [224,128], "f": 0, "t": 540, "d": [555], "a": 1 }, + { "px": [224,88], "src": [224,128], "f": 0, "t": 540, "d": [556], "a": 1 }, + { "px": [232,88], "src": [224,128], "f": 0, "t": 540, "d": [557], "a": 1 }, + { "px": [240,88], "src": [224,128], "f": 0, "t": 540, "d": [558], "a": 1 }, + { "px": [248,88], "src": [224,128], "f": 0, "t": 540, "d": [559], "a": 1 }, + { "px": [256,88], "src": [224,128], "f": 0, "t": 540, "d": [560], "a": 1 }, + { "px": [264,88], "src": [224,128], "f": 0, "t": 540, "d": [561], "a": 1 }, + { "px": [272,88], "src": [224,128], "f": 0, "t": 540, "d": [562], "a": 1 }, + { "px": [280,88], "src": [224,128], "f": 0, "t": 540, "d": [563], "a": 1 }, + { "px": [288,88], "src": [224,128], "f": 0, "t": 540, "d": [564], "a": 1 }, + { "px": [296,88], "src": [224,128], "f": 0, "t": 540, "d": [565], "a": 1 }, + { "px": [304,88], "src": [224,128], "f": 0, "t": 540, "d": [566], "a": 1 }, + { "px": [312,88], "src": [224,128], "f": 0, "t": 540, "d": [567], "a": 1 }, + { "px": [320,88], "src": [224,128], "f": 0, "t": 540, "d": [568], "a": 1 }, + { "px": [328,88], "src": [224,128], "f": 0, "t": 540, "d": [569], "a": 1 }, + { "px": [336,88], "src": [224,128], "f": 0, "t": 540, "d": [570], "a": 1 }, + { "px": [344,88], "src": [224,128], "f": 0, "t": 540, "d": [571], "a": 1 }, + { "px": [352,88], "src": [224,128], "f": 0, "t": 540, "d": [572], "a": 1 }, + { "px": [360,88], "src": [224,128], "f": 0, "t": 540, "d": [573], "a": 1 }, + { "px": [368,88], "src": [224,128], "f": 0, "t": 540, "d": [574], "a": 1 }, + { "px": [376,88], "src": [232,128], "f": 0, "t": 541, "d": [575], "a": 1 }, + { "px": [0,96], "src": [216,128], "f": 0, "t": 539, "d": [576], "a": 1 }, + { "px": [8,96], "src": [224,128], "f": 0, "t": 540, "d": [577], "a": 1 }, + { "px": [16,96], "src": [224,128], "f": 0, "t": 540, "d": [578], "a": 1 }, + { "px": [24,96], "src": [224,128], "f": 0, "t": 540, "d": [579], "a": 1 }, + { "px": [32,96], "src": [224,128], "f": 0, "t": 540, "d": [580], "a": 1 }, + { "px": [40,96], "src": [224,128], "f": 0, "t": 540, "d": [581], "a": 1 }, + { "px": [48,96], "src": [224,128], "f": 0, "t": 540, "d": [582], "a": 1 }, + { "px": [56,96], "src": [224,128], "f": 0, "t": 540, "d": [583], "a": 1 }, + { "px": [64,96], "src": [224,128], "f": 0, "t": 540, "d": [584], "a": 1 }, + { "px": [72,96], "src": [224,128], "f": 0, "t": 540, "d": [585], "a": 1 }, + { "px": [80,96], "src": [224,128], "f": 0, "t": 540, "d": [586], "a": 1 }, + { "px": [88,96], "src": [224,128], "f": 0, "t": 540, "d": [587], "a": 1 }, + { "px": [96,96], "src": [224,128], "f": 0, "t": 540, "d": [588], "a": 1 }, + { "px": [104,96], "src": [224,128], "f": 0, "t": 540, "d": [589], "a": 1 }, + { "px": [112,96], "src": [224,128], "f": 0, "t": 540, "d": [590], "a": 1 }, + { "px": [120,96], "src": [224,128], "f": 0, "t": 540, "d": [591], "a": 1 }, + { "px": [128,96], "src": [224,128], "f": 0, "t": 540, "d": [592], "a": 1 }, + { "px": [136,96], "src": [224,128], "f": 0, "t": 540, "d": [593], "a": 1 }, + { "px": [144,96], "src": [224,128], "f": 0, "t": 540, "d": [594], "a": 1 }, + { "px": [152,96], "src": [224,128], "f": 0, "t": 540, "d": [595], "a": 1 }, + { "px": [160,96], "src": [224,128], "f": 0, "t": 540, "d": [596], "a": 1 }, + { "px": [168,96], "src": [224,128], "f": 0, "t": 540, "d": [597], "a": 1 }, + { "px": [176,96], "src": [224,128], "f": 0, "t": 540, "d": [598], "a": 1 }, + { "px": [184,96], "src": [224,128], "f": 0, "t": 540, "d": [599], "a": 1 }, + { "px": [192,96], "src": [224,128], "f": 0, "t": 540, "d": [600], "a": 1 }, + { "px": [200,96], "src": [224,128], "f": 0, "t": 540, "d": [601], "a": 1 }, + { "px": [208,96], "src": [224,128], "f": 0, "t": 540, "d": [602], "a": 1 }, + { "px": [216,96], "src": [224,128], "f": 0, "t": 540, "d": [603], "a": 1 }, + { "px": [224,96], "src": [224,128], "f": 0, "t": 540, "d": [604], "a": 1 }, + { "px": [232,96], "src": [224,128], "f": 0, "t": 540, "d": [605], "a": 1 }, + { "px": [240,96], "src": [224,128], "f": 0, "t": 540, "d": [606], "a": 1 }, + { "px": [248,96], "src": [224,128], "f": 0, "t": 540, "d": [607], "a": 1 }, + { "px": [256,96], "src": [224,128], "f": 0, "t": 540, "d": [608], "a": 1 }, + { "px": [264,96], "src": [224,128], "f": 0, "t": 540, "d": [609], "a": 1 }, + { "px": [272,96], "src": [224,128], "f": 0, "t": 540, "d": [610], "a": 1 }, + { "px": [280,96], "src": [224,128], "f": 0, "t": 540, "d": [611], "a": 1 }, + { "px": [288,96], "src": [224,128], "f": 0, "t": 540, "d": [612], "a": 1 }, + { "px": [296,96], "src": [224,128], "f": 0, "t": 540, "d": [613], "a": 1 }, + { "px": [304,96], "src": [224,128], "f": 0, "t": 540, "d": [614], "a": 1 }, + { "px": [312,96], "src": [224,128], "f": 0, "t": 540, "d": [615], "a": 1 }, + { "px": [320,96], "src": [224,128], "f": 0, "t": 540, "d": [616], "a": 1 }, + { "px": [328,96], "src": [224,128], "f": 0, "t": 540, "d": [617], "a": 1 }, + { "px": [336,96], "src": [224,128], "f": 0, "t": 540, "d": [618], "a": 1 }, + { "px": [344,96], "src": [224,128], "f": 0, "t": 540, "d": [619], "a": 1 }, + { "px": [352,96], "src": [224,128], "f": 0, "t": 540, "d": [620], "a": 1 }, + { "px": [360,96], "src": [224,128], "f": 0, "t": 540, "d": [621], "a": 1 }, + { "px": [368,96], "src": [224,128], "f": 0, "t": 540, "d": [622], "a": 1 }, + { "px": [376,96], "src": [232,128], "f": 0, "t": 541, "d": [623], "a": 1 }, + { "px": [0,104], "src": [216,128], "f": 0, "t": 539, "d": [624], "a": 1 }, + { "px": [8,104], "src": [224,128], "f": 0, "t": 540, "d": [625], "a": 1 }, + { "px": [16,104], "src": [224,128], "f": 0, "t": 540, "d": [626], "a": 1 }, + { "px": [24,104], "src": [224,128], "f": 0, "t": 540, "d": [627], "a": 1 }, + { "px": [32,104], "src": [224,128], "f": 0, "t": 540, "d": [628], "a": 1 }, + { "px": [40,104], "src": [224,128], "f": 0, "t": 540, "d": [629], "a": 1 }, + { "px": [48,104], "src": [224,128], "f": 0, "t": 540, "d": [630], "a": 1 }, + { "px": [56,104], "src": [224,128], "f": 0, "t": 540, "d": [631], "a": 1 }, + { "px": [64,104], "src": [224,128], "f": 0, "t": 540, "d": [632], "a": 1 }, + { "px": [72,104], "src": [224,128], "f": 0, "t": 540, "d": [633], "a": 1 }, + { "px": [80,104], "src": [224,128], "f": 0, "t": 540, "d": [634], "a": 1 }, + { "px": [88,104], "src": [224,128], "f": 0, "t": 540, "d": [635], "a": 1 }, + { "px": [96,104], "src": [224,128], "f": 0, "t": 540, "d": [636], "a": 1 }, + { "px": [104,104], "src": [224,128], "f": 0, "t": 540, "d": [637], "a": 1 }, + { "px": [112,104], "src": [224,128], "f": 0, "t": 540, "d": [638], "a": 1 }, + { "px": [120,104], "src": [224,128], "f": 0, "t": 540, "d": [639], "a": 1 }, + { "px": [128,104], "src": [224,128], "f": 0, "t": 540, "d": [640], "a": 1 }, + { "px": [136,104], "src": [224,128], "f": 0, "t": 540, "d": [641], "a": 1 }, + { "px": [144,104], "src": [224,128], "f": 0, "t": 540, "d": [642], "a": 1 }, + { "px": [152,104], "src": [224,128], "f": 0, "t": 540, "d": [643], "a": 1 }, + { "px": [160,104], "src": [224,128], "f": 0, "t": 540, "d": [644], "a": 1 }, + { "px": [168,104], "src": [224,128], "f": 0, "t": 540, "d": [645], "a": 1 }, + { "px": [176,104], "src": [224,128], "f": 0, "t": 540, "d": [646], "a": 1 }, + { "px": [184,104], "src": [224,128], "f": 0, "t": 540, "d": [647], "a": 1 }, + { "px": [192,104], "src": [224,128], "f": 0, "t": 540, "d": [648], "a": 1 }, + { "px": [200,104], "src": [224,128], "f": 0, "t": 540, "d": [649], "a": 1 }, + { "px": [208,104], "src": [224,128], "f": 0, "t": 540, "d": [650], "a": 1 }, + { "px": [216,104], "src": [224,128], "f": 0, "t": 540, "d": [651], "a": 1 }, + { "px": [224,104], "src": [224,128], "f": 0, "t": 540, "d": [652], "a": 1 }, + { "px": [232,104], "src": [224,128], "f": 0, "t": 540, "d": [653], "a": 1 }, + { "px": [240,104], "src": [224,128], "f": 0, "t": 540, "d": [654], "a": 1 }, + { "px": [248,104], "src": [224,128], "f": 0, "t": 540, "d": [655], "a": 1 }, + { "px": [256,104], "src": [224,128], "f": 0, "t": 540, "d": [656], "a": 1 }, + { "px": [264,104], "src": [224,128], "f": 0, "t": 540, "d": [657], "a": 1 }, + { "px": [272,104], "src": [224,128], "f": 0, "t": 540, "d": [658], "a": 1 }, + { "px": [280,104], "src": [224,128], "f": 0, "t": 540, "d": [659], "a": 1 }, + { "px": [288,104], "src": [224,128], "f": 0, "t": 540, "d": [660], "a": 1 }, + { "px": [296,104], "src": [224,128], "f": 0, "t": 540, "d": [661], "a": 1 }, + { "px": [304,104], "src": [224,128], "f": 0, "t": 540, "d": [662], "a": 1 }, + { "px": [312,104], "src": [224,128], "f": 0, "t": 540, "d": [663], "a": 1 }, + { "px": [320,104], "src": [224,128], "f": 0, "t": 540, "d": [664], "a": 1 }, + { "px": [328,104], "src": [224,128], "f": 0, "t": 540, "d": [665], "a": 1 }, + { "px": [336,104], "src": [224,128], "f": 0, "t": 540, "d": [666], "a": 1 }, + { "px": [344,104], "src": [224,128], "f": 0, "t": 540, "d": [667], "a": 1 }, + { "px": [352,104], "src": [224,128], "f": 0, "t": 540, "d": [668], "a": 1 }, + { "px": [360,104], "src": [224,128], "f": 0, "t": 540, "d": [669], "a": 1 }, + { "px": [368,104], "src": [224,128], "f": 0, "t": 540, "d": [670], "a": 1 }, + { "px": [376,104], "src": [232,128], "f": 0, "t": 541, "d": [671], "a": 1 }, + { "px": [0,112], "src": [216,128], "f": 0, "t": 539, "d": [672], "a": 1 }, + { "px": [8,112], "src": [224,128], "f": 0, "t": 540, "d": [673], "a": 1 }, + { "px": [16,112], "src": [224,128], "f": 0, "t": 540, "d": [674], "a": 1 }, + { "px": [24,112], "src": [224,128], "f": 0, "t": 540, "d": [675], "a": 1 }, + { "px": [32,112], "src": [224,128], "f": 0, "t": 540, "d": [676], "a": 1 }, + { "px": [40,112], "src": [224,128], "f": 0, "t": 540, "d": [677], "a": 1 }, + { "px": [48,112], "src": [224,128], "f": 0, "t": 540, "d": [678], "a": 1 }, + { "px": [56,112], "src": [224,128], "f": 0, "t": 540, "d": [679], "a": 1 }, + { "px": [64,112], "src": [224,128], "f": 0, "t": 540, "d": [680], "a": 1 }, + { "px": [72,112], "src": [224,128], "f": 0, "t": 540, "d": [681], "a": 1 }, + { "px": [80,112], "src": [224,128], "f": 0, "t": 540, "d": [682], "a": 1 }, + { "px": [88,112], "src": [224,128], "f": 0, "t": 540, "d": [683], "a": 1 }, + { "px": [96,112], "src": [224,128], "f": 0, "t": 540, "d": [684], "a": 1 }, + { "px": [104,112], "src": [224,128], "f": 0, "t": 540, "d": [685], "a": 1 }, + { "px": [112,112], "src": [224,128], "f": 0, "t": 540, "d": [686], "a": 1 }, + { "px": [120,112], "src": [224,128], "f": 0, "t": 540, "d": [687], "a": 1 }, + { "px": [128,112], "src": [224,128], "f": 0, "t": 540, "d": [688], "a": 1 }, + { "px": [136,112], "src": [224,128], "f": 0, "t": 540, "d": [689], "a": 1 }, + { "px": [144,112], "src": [224,128], "f": 0, "t": 540, "d": [690], "a": 1 }, + { "px": [152,112], "src": [224,128], "f": 0, "t": 540, "d": [691], "a": 1 }, + { "px": [160,112], "src": [224,128], "f": 0, "t": 540, "d": [692], "a": 1 }, + { "px": [168,112], "src": [224,128], "f": 0, "t": 540, "d": [693], "a": 1 }, + { "px": [176,112], "src": [224,128], "f": 0, "t": 540, "d": [694], "a": 1 }, + { "px": [184,112], "src": [224,128], "f": 0, "t": 540, "d": [695], "a": 1 }, + { "px": [192,112], "src": [224,128], "f": 0, "t": 540, "d": [696], "a": 1 }, + { "px": [200,112], "src": [224,128], "f": 0, "t": 540, "d": [697], "a": 1 }, + { "px": [208,112], "src": [224,128], "f": 0, "t": 540, "d": [698], "a": 1 }, + { "px": [216,112], "src": [224,128], "f": 0, "t": 540, "d": [699], "a": 1 }, + { "px": [224,112], "src": [224,128], "f": 0, "t": 540, "d": [700], "a": 1 }, + { "px": [232,112], "src": [224,128], "f": 0, "t": 540, "d": [701], "a": 1 }, + { "px": [240,112], "src": [224,128], "f": 0, "t": 540, "d": [702], "a": 1 }, + { "px": [248,112], "src": [224,128], "f": 0, "t": 540, "d": [703], "a": 1 }, + { "px": [256,112], "src": [224,128], "f": 0, "t": 540, "d": [704], "a": 1 }, + { "px": [264,112], "src": [224,128], "f": 0, "t": 540, "d": [705], "a": 1 }, + { "px": [272,112], "src": [224,128], "f": 0, "t": 540, "d": [706], "a": 1 }, + { "px": [280,112], "src": [224,128], "f": 0, "t": 540, "d": [707], "a": 1 }, + { "px": [288,112], "src": [224,128], "f": 0, "t": 540, "d": [708], "a": 1 }, + { "px": [296,112], "src": [224,128], "f": 0, "t": 540, "d": [709], "a": 1 }, + { "px": [304,112], "src": [224,128], "f": 0, "t": 540, "d": [710], "a": 1 }, + { "px": [312,112], "src": [224,128], "f": 0, "t": 540, "d": [711], "a": 1 }, + { "px": [320,112], "src": [224,128], "f": 0, "t": 540, "d": [712], "a": 1 }, + { "px": [328,112], "src": [224,128], "f": 0, "t": 540, "d": [713], "a": 1 }, + { "px": [336,112], "src": [224,128], "f": 0, "t": 540, "d": [714], "a": 1 }, + { "px": [344,112], "src": [224,128], "f": 0, "t": 540, "d": [715], "a": 1 }, + { "px": [352,112], "src": [224,128], "f": 0, "t": 540, "d": [716], "a": 1 }, + { "px": [360,112], "src": [224,128], "f": 0, "t": 540, "d": [717], "a": 1 }, + { "px": [368,112], "src": [224,128], "f": 0, "t": 540, "d": [718], "a": 1 }, + { "px": [376,112], "src": [232,128], "f": 0, "t": 541, "d": [719], "a": 1 }, + { "px": [0,120], "src": [216,128], "f": 0, "t": 539, "d": [720], "a": 1 }, + { "px": [8,120], "src": [224,128], "f": 0, "t": 540, "d": [721], "a": 1 }, + { "px": [16,120], "src": [224,128], "f": 0, "t": 540, "d": [722], "a": 1 }, + { "px": [24,120], "src": [224,128], "f": 0, "t": 540, "d": [723], "a": 1 }, + { "px": [32,120], "src": [224,128], "f": 0, "t": 540, "d": [724], "a": 1 }, + { "px": [40,120], "src": [224,128], "f": 0, "t": 540, "d": [725], "a": 1 }, + { "px": [48,120], "src": [224,128], "f": 0, "t": 540, "d": [726], "a": 1 }, + { "px": [56,120], "src": [224,128], "f": 0, "t": 540, "d": [727], "a": 1 }, + { "px": [64,120], "src": [224,128], "f": 0, "t": 540, "d": [728], "a": 1 }, + { "px": [72,120], "src": [224,128], "f": 0, "t": 540, "d": [729], "a": 1 }, + { "px": [80,120], "src": [224,128], "f": 0, "t": 540, "d": [730], "a": 1 }, + { "px": [88,120], "src": [224,128], "f": 0, "t": 540, "d": [731], "a": 1 }, + { "px": [96,120], "src": [224,128], "f": 0, "t": 540, "d": [732], "a": 1 }, + { "px": [104,120], "src": [224,128], "f": 0, "t": 540, "d": [733], "a": 1 }, + { "px": [112,120], "src": [224,128], "f": 0, "t": 540, "d": [734], "a": 1 }, + { "px": [120,120], "src": [224,128], "f": 0, "t": 540, "d": [735], "a": 1 }, + { "px": [128,120], "src": [224,128], "f": 0, "t": 540, "d": [736], "a": 1 }, + { "px": [136,120], "src": [224,128], "f": 0, "t": 540, "d": [737], "a": 1 }, + { "px": [144,120], "src": [224,128], "f": 0, "t": 540, "d": [738], "a": 1 }, + { "px": [152,120], "src": [224,128], "f": 0, "t": 540, "d": [739], "a": 1 }, + { "px": [160,120], "src": [224,128], "f": 0, "t": 540, "d": [740], "a": 1 }, + { "px": [168,120], "src": [224,128], "f": 0, "t": 540, "d": [741], "a": 1 }, + { "px": [176,120], "src": [224,128], "f": 0, "t": 540, "d": [742], "a": 1 }, + { "px": [184,120], "src": [224,128], "f": 0, "t": 540, "d": [743], "a": 1 }, + { "px": [192,120], "src": [224,128], "f": 0, "t": 540, "d": [744], "a": 1 }, + { "px": [200,120], "src": [224,128], "f": 0, "t": 540, "d": [745], "a": 1 }, + { "px": [208,120], "src": [224,128], "f": 0, "t": 540, "d": [746], "a": 1 }, + { "px": [216,120], "src": [224,128], "f": 0, "t": 540, "d": [747], "a": 1 }, + { "px": [224,120], "src": [224,128], "f": 0, "t": 540, "d": [748], "a": 1 }, + { "px": [232,120], "src": [224,128], "f": 0, "t": 540, "d": [749], "a": 1 }, + { "px": [240,120], "src": [224,128], "f": 0, "t": 540, "d": [750], "a": 1 }, + { "px": [248,120], "src": [224,128], "f": 0, "t": 540, "d": [751], "a": 1 }, + { "px": [256,120], "src": [224,128], "f": 0, "t": 540, "d": [752], "a": 1 }, + { "px": [264,120], "src": [224,128], "f": 0, "t": 540, "d": [753], "a": 1 }, + { "px": [272,120], "src": [224,128], "f": 0, "t": 540, "d": [754], "a": 1 }, + { "px": [280,120], "src": [224,128], "f": 0, "t": 540, "d": [755], "a": 1 }, + { "px": [288,120], "src": [224,128], "f": 0, "t": 540, "d": [756], "a": 1 }, + { "px": [296,120], "src": [224,128], "f": 0, "t": 540, "d": [757], "a": 1 }, + { "px": [304,120], "src": [224,128], "f": 0, "t": 540, "d": [758], "a": 1 }, + { "px": [312,120], "src": [224,128], "f": 0, "t": 540, "d": [759], "a": 1 }, + { "px": [320,120], "src": [224,128], "f": 0, "t": 540, "d": [760], "a": 1 }, + { "px": [328,120], "src": [224,128], "f": 0, "t": 540, "d": [761], "a": 1 }, + { "px": [336,120], "src": [224,128], "f": 0, "t": 540, "d": [762], "a": 1 }, + { "px": [344,120], "src": [224,128], "f": 0, "t": 540, "d": [763], "a": 1 }, + { "px": [352,120], "src": [224,128], "f": 0, "t": 540, "d": [764], "a": 1 }, + { "px": [360,120], "src": [224,128], "f": 0, "t": 540, "d": [765], "a": 1 }, + { "px": [368,120], "src": [224,128], "f": 0, "t": 540, "d": [766], "a": 1 }, + { "px": [376,120], "src": [232,128], "f": 0, "t": 541, "d": [767], "a": 1 }, + { "px": [0,128], "src": [216,128], "f": 0, "t": 539, "d": [768], "a": 1 }, + { "px": [8,128], "src": [224,128], "f": 0, "t": 540, "d": [769], "a": 1 }, + { "px": [16,128], "src": [224,128], "f": 0, "t": 540, "d": [770], "a": 1 }, + { "px": [24,128], "src": [224,128], "f": 0, "t": 540, "d": [771], "a": 1 }, + { "px": [32,128], "src": [224,128], "f": 0, "t": 540, "d": [772], "a": 1 }, + { "px": [40,128], "src": [224,128], "f": 0, "t": 540, "d": [773], "a": 1 }, + { "px": [48,128], "src": [224,128], "f": 0, "t": 540, "d": [774], "a": 1 }, + { "px": [56,128], "src": [224,128], "f": 0, "t": 540, "d": [775], "a": 1 }, + { "px": [64,128], "src": [224,128], "f": 0, "t": 540, "d": [776], "a": 1 }, + { "px": [72,128], "src": [224,128], "f": 0, "t": 540, "d": [777], "a": 1 }, + { "px": [80,128], "src": [224,128], "f": 0, "t": 540, "d": [778], "a": 1 }, + { "px": [88,128], "src": [224,128], "f": 0, "t": 540, "d": [779], "a": 1 }, + { "px": [96,128], "src": [224,128], "f": 0, "t": 540, "d": [780], "a": 1 }, + { "px": [104,128], "src": [224,128], "f": 0, "t": 540, "d": [781], "a": 1 }, + { "px": [112,128], "src": [224,128], "f": 0, "t": 540, "d": [782], "a": 1 }, + { "px": [120,128], "src": [224,128], "f": 0, "t": 540, "d": [783], "a": 1 }, + { "px": [128,128], "src": [224,128], "f": 0, "t": 540, "d": [784], "a": 1 }, + { "px": [136,128], "src": [224,128], "f": 0, "t": 540, "d": [785], "a": 1 }, + { "px": [144,128], "src": [224,128], "f": 0, "t": 540, "d": [786], "a": 1 }, + { "px": [152,128], "src": [224,128], "f": 0, "t": 540, "d": [787], "a": 1 }, + { "px": [160,128], "src": [224,128], "f": 0, "t": 540, "d": [788], "a": 1 }, + { "px": [168,128], "src": [224,128], "f": 0, "t": 540, "d": [789], "a": 1 }, + { "px": [176,128], "src": [224,128], "f": 0, "t": 540, "d": [790], "a": 1 }, + { "px": [184,128], "src": [224,128], "f": 0, "t": 540, "d": [791], "a": 1 }, + { "px": [192,128], "src": [224,128], "f": 0, "t": 540, "d": [792], "a": 1 }, + { "px": [200,128], "src": [224,128], "f": 0, "t": 540, "d": [793], "a": 1 }, + { "px": [208,128], "src": [224,128], "f": 0, "t": 540, "d": [794], "a": 1 }, + { "px": [216,128], "src": [224,128], "f": 0, "t": 540, "d": [795], "a": 1 }, + { "px": [224,128], "src": [224,128], "f": 0, "t": 540, "d": [796], "a": 1 }, + { "px": [232,128], "src": [224,128], "f": 0, "t": 540, "d": [797], "a": 1 }, + { "px": [240,128], "src": [224,128], "f": 0, "t": 540, "d": [798], "a": 1 }, + { "px": [248,128], "src": [224,128], "f": 0, "t": 540, "d": [799], "a": 1 }, + { "px": [256,128], "src": [224,128], "f": 0, "t": 540, "d": [800], "a": 1 }, + { "px": [264,128], "src": [224,128], "f": 0, "t": 540, "d": [801], "a": 1 }, + { "px": [272,128], "src": [224,128], "f": 0, "t": 540, "d": [802], "a": 1 }, + { "px": [280,128], "src": [224,128], "f": 0, "t": 540, "d": [803], "a": 1 }, + { "px": [288,128], "src": [224,128], "f": 0, "t": 540, "d": [804], "a": 1 }, + { "px": [296,128], "src": [224,128], "f": 0, "t": 540, "d": [805], "a": 1 }, + { "px": [304,128], "src": [224,128], "f": 0, "t": 540, "d": [806], "a": 1 }, + { "px": [312,128], "src": [224,128], "f": 0, "t": 540, "d": [807], "a": 1 }, + { "px": [320,128], "src": [224,128], "f": 0, "t": 540, "d": [808], "a": 1 }, + { "px": [328,128], "src": [224,128], "f": 0, "t": 540, "d": [809], "a": 1 }, + { "px": [336,128], "src": [224,128], "f": 0, "t": 540, "d": [810], "a": 1 }, + { "px": [344,128], "src": [224,128], "f": 0, "t": 540, "d": [811], "a": 1 }, + { "px": [352,128], "src": [224,128], "f": 0, "t": 540, "d": [812], "a": 1 }, + { "px": [360,128], "src": [224,128], "f": 0, "t": 540, "d": [813], "a": 1 }, + { "px": [368,128], "src": [224,128], "f": 0, "t": 540, "d": [814], "a": 1 }, + { "px": [376,128], "src": [232,128], "f": 0, "t": 541, "d": [815], "a": 1 }, + { "px": [0,136], "src": [216,128], "f": 0, "t": 539, "d": [816], "a": 1 }, + { "px": [8,136], "src": [224,128], "f": 0, "t": 540, "d": [817], "a": 1 }, + { "px": [16,136], "src": [224,128], "f": 0, "t": 540, "d": [818], "a": 1 }, + { "px": [24,136], "src": [224,128], "f": 0, "t": 540, "d": [819], "a": 1 }, + { "px": [32,136], "src": [224,128], "f": 0, "t": 540, "d": [820], "a": 1 }, + { "px": [40,136], "src": [224,128], "f": 0, "t": 540, "d": [821], "a": 1 }, + { "px": [48,136], "src": [224,128], "f": 0, "t": 540, "d": [822], "a": 1 }, + { "px": [56,136], "src": [224,128], "f": 0, "t": 540, "d": [823], "a": 1 }, + { "px": [64,136], "src": [224,128], "f": 0, "t": 540, "d": [824], "a": 1 }, + { "px": [72,136], "src": [224,128], "f": 0, "t": 540, "d": [825], "a": 1 }, + { "px": [80,136], "src": [224,128], "f": 0, "t": 540, "d": [826], "a": 1 }, + { "px": [88,136], "src": [224,128], "f": 0, "t": 540, "d": [827], "a": 1 }, + { "px": [96,136], "src": [224,128], "f": 0, "t": 540, "d": [828], "a": 1 }, + { "px": [104,136], "src": [224,128], "f": 0, "t": 540, "d": [829], "a": 1 }, + { "px": [112,136], "src": [224,128], "f": 0, "t": 540, "d": [830], "a": 1 }, + { "px": [120,136], "src": [224,128], "f": 0, "t": 540, "d": [831], "a": 1 }, + { "px": [128,136], "src": [224,128], "f": 0, "t": 540, "d": [832], "a": 1 }, + { "px": [136,136], "src": [224,128], "f": 0, "t": 540, "d": [833], "a": 1 }, + { "px": [144,136], "src": [224,128], "f": 0, "t": 540, "d": [834], "a": 1 }, + { "px": [152,136], "src": [224,128], "f": 0, "t": 540, "d": [835], "a": 1 }, + { "px": [160,136], "src": [224,128], "f": 0, "t": 540, "d": [836], "a": 1 }, + { "px": [168,136], "src": [224,128], "f": 0, "t": 540, "d": [837], "a": 1 }, + { "px": [176,136], "src": [224,128], "f": 0, "t": 540, "d": [838], "a": 1 }, + { "px": [184,136], "src": [224,128], "f": 0, "t": 540, "d": [839], "a": 1 }, + { "px": [192,136], "src": [224,128], "f": 0, "t": 540, "d": [840], "a": 1 }, + { "px": [200,136], "src": [224,128], "f": 0, "t": 540, "d": [841], "a": 1 }, + { "px": [208,136], "src": [224,128], "f": 0, "t": 540, "d": [842], "a": 1 }, + { "px": [216,136], "src": [224,128], "f": 0, "t": 540, "d": [843], "a": 1 }, + { "px": [224,136], "src": [224,128], "f": 0, "t": 540, "d": [844], "a": 1 }, + { "px": [232,136], "src": [224,128], "f": 0, "t": 540, "d": [845], "a": 1 }, + { "px": [240,136], "src": [224,128], "f": 0, "t": 540, "d": [846], "a": 1 }, + { "px": [248,136], "src": [224,128], "f": 0, "t": 540, "d": [847], "a": 1 }, + { "px": [256,136], "src": [224,128], "f": 0, "t": 540, "d": [848], "a": 1 }, + { "px": [264,136], "src": [224,128], "f": 0, "t": 540, "d": [849], "a": 1 }, + { "px": [272,136], "src": [224,128], "f": 0, "t": 540, "d": [850], "a": 1 }, + { "px": [280,136], "src": [224,128], "f": 0, "t": 540, "d": [851], "a": 1 }, + { "px": [288,136], "src": [224,128], "f": 0, "t": 540, "d": [852], "a": 1 }, + { "px": [296,136], "src": [224,128], "f": 0, "t": 540, "d": [853], "a": 1 }, + { "px": [304,136], "src": [224,128], "f": 0, "t": 540, "d": [854], "a": 1 }, + { "px": [312,136], "src": [224,128], "f": 0, "t": 540, "d": [855], "a": 1 }, + { "px": [320,136], "src": [224,128], "f": 0, "t": 540, "d": [856], "a": 1 }, + { "px": [328,136], "src": [224,128], "f": 0, "t": 540, "d": [857], "a": 1 }, + { "px": [336,136], "src": [224,128], "f": 0, "t": 540, "d": [858], "a": 1 }, + { "px": [344,136], "src": [224,128], "f": 0, "t": 540, "d": [859], "a": 1 }, + { "px": [352,136], "src": [224,128], "f": 0, "t": 540, "d": [860], "a": 1 }, + { "px": [360,136], "src": [224,128], "f": 0, "t": 540, "d": [861], "a": 1 }, + { "px": [368,136], "src": [224,128], "f": 0, "t": 540, "d": [862], "a": 1 }, + { "px": [376,136], "src": [232,128], "f": 0, "t": 541, "d": [863], "a": 1 }, + { "px": [0,144], "src": [216,128], "f": 0, "t": 539, "d": [864], "a": 1 }, + { "px": [8,144], "src": [224,128], "f": 0, "t": 540, "d": [865], "a": 1 }, + { "px": [16,144], "src": [224,128], "f": 0, "t": 540, "d": [866], "a": 1 }, + { "px": [24,144], "src": [224,128], "f": 0, "t": 540, "d": [867], "a": 1 }, + { "px": [32,144], "src": [224,128], "f": 0, "t": 540, "d": [868], "a": 1 }, + { "px": [40,144], "src": [224,128], "f": 0, "t": 540, "d": [869], "a": 1 }, + { "px": [48,144], "src": [224,128], "f": 0, "t": 540, "d": [870], "a": 1 }, + { "px": [56,144], "src": [224,128], "f": 0, "t": 540, "d": [871], "a": 1 }, + { "px": [64,144], "src": [224,128], "f": 0, "t": 540, "d": [872], "a": 1 }, + { "px": [72,144], "src": [224,128], "f": 0, "t": 540, "d": [873], "a": 1 }, + { "px": [80,144], "src": [224,128], "f": 0, "t": 540, "d": [874], "a": 1 }, + { "px": [88,144], "src": [224,128], "f": 0, "t": 540, "d": [875], "a": 1 }, + { "px": [96,144], "src": [224,128], "f": 0, "t": 540, "d": [876], "a": 1 }, + { "px": [104,144], "src": [224,128], "f": 0, "t": 540, "d": [877], "a": 1 }, + { "px": [112,144], "src": [224,128], "f": 0, "t": 540, "d": [878], "a": 1 }, + { "px": [120,144], "src": [224,128], "f": 0, "t": 540, "d": [879], "a": 1 }, + { "px": [128,144], "src": [224,128], "f": 0, "t": 540, "d": [880], "a": 1 }, + { "px": [136,144], "src": [224,128], "f": 0, "t": 540, "d": [881], "a": 1 }, + { "px": [144,144], "src": [224,128], "f": 0, "t": 540, "d": [882], "a": 1 }, + { "px": [152,144], "src": [224,128], "f": 0, "t": 540, "d": [883], "a": 1 }, + { "px": [160,144], "src": [224,128], "f": 0, "t": 540, "d": [884], "a": 1 }, + { "px": [168,144], "src": [224,128], "f": 0, "t": 540, "d": [885], "a": 1 }, + { "px": [176,144], "src": [224,128], "f": 0, "t": 540, "d": [886], "a": 1 }, + { "px": [184,144], "src": [224,128], "f": 0, "t": 540, "d": [887], "a": 1 }, + { "px": [192,144], "src": [224,128], "f": 0, "t": 540, "d": [888], "a": 1 }, + { "px": [200,144], "src": [224,128], "f": 0, "t": 540, "d": [889], "a": 1 }, + { "px": [208,144], "src": [224,128], "f": 0, "t": 540, "d": [890], "a": 1 }, + { "px": [216,144], "src": [224,128], "f": 0, "t": 540, "d": [891], "a": 1 }, + { "px": [224,144], "src": [224,128], "f": 0, "t": 540, "d": [892], "a": 1 }, + { "px": [232,144], "src": [224,128], "f": 0, "t": 540, "d": [893], "a": 1 }, + { "px": [240,144], "src": [224,128], "f": 0, "t": 540, "d": [894], "a": 1 }, + { "px": [248,144], "src": [224,128], "f": 0, "t": 540, "d": [895], "a": 1 }, + { "px": [256,144], "src": [224,128], "f": 0, "t": 540, "d": [896], "a": 1 }, + { "px": [264,144], "src": [224,128], "f": 0, "t": 540, "d": [897], "a": 1 }, + { "px": [272,144], "src": [224,128], "f": 0, "t": 540, "d": [898], "a": 1 }, + { "px": [280,144], "src": [224,128], "f": 0, "t": 540, "d": [899], "a": 1 }, + { "px": [288,144], "src": [224,128], "f": 0, "t": 540, "d": [900], "a": 1 }, + { "px": [296,144], "src": [224,128], "f": 0, "t": 540, "d": [901], "a": 1 }, + { "px": [304,144], "src": [224,128], "f": 0, "t": 540, "d": [902], "a": 1 }, + { "px": [312,144], "src": [224,128], "f": 0, "t": 540, "d": [903], "a": 1 }, + { "px": [320,144], "src": [224,128], "f": 0, "t": 540, "d": [904], "a": 1 }, + { "px": [328,144], "src": [224,128], "f": 0, "t": 540, "d": [905], "a": 1 }, + { "px": [336,144], "src": [224,128], "f": 0, "t": 540, "d": [906], "a": 1 }, + { "px": [344,144], "src": [224,128], "f": 0, "t": 540, "d": [907], "a": 1 }, + { "px": [352,144], "src": [224,128], "f": 0, "t": 540, "d": [908], "a": 1 }, + { "px": [360,144], "src": [224,128], "f": 0, "t": 540, "d": [909], "a": 1 }, + { "px": [368,144], "src": [224,128], "f": 0, "t": 540, "d": [910], "a": 1 }, + { "px": [376,144], "src": [232,128], "f": 0, "t": 541, "d": [911], "a": 1 }, + { "px": [0,152], "src": [216,128], "f": 0, "t": 539, "d": [912], "a": 1 }, + { "px": [8,152], "src": [224,128], "f": 0, "t": 540, "d": [913], "a": 1 }, + { "px": [16,152], "src": [224,128], "f": 0, "t": 540, "d": [914], "a": 1 }, + { "px": [24,152], "src": [224,128], "f": 0, "t": 540, "d": [915], "a": 1 }, + { "px": [32,152], "src": [224,128], "f": 0, "t": 540, "d": [916], "a": 1 }, + { "px": [40,152], "src": [224,128], "f": 0, "t": 540, "d": [917], "a": 1 }, + { "px": [48,152], "src": [224,128], "f": 0, "t": 540, "d": [918], "a": 1 }, + { "px": [56,152], "src": [224,128], "f": 0, "t": 540, "d": [919], "a": 1 }, + { "px": [64,152], "src": [224,128], "f": 0, "t": 540, "d": [920], "a": 1 }, + { "px": [72,152], "src": [224,128], "f": 0, "t": 540, "d": [921], "a": 1 }, + { "px": [80,152], "src": [224,128], "f": 0, "t": 540, "d": [922], "a": 1 }, + { "px": [88,152], "src": [224,128], "f": 0, "t": 540, "d": [923], "a": 1 }, + { "px": [96,152], "src": [224,128], "f": 0, "t": 540, "d": [924], "a": 1 }, + { "px": [104,152], "src": [224,128], "f": 0, "t": 540, "d": [925], "a": 1 }, + { "px": [112,152], "src": [224,128], "f": 0, "t": 540, "d": [926], "a": 1 }, + { "px": [120,152], "src": [224,128], "f": 0, "t": 540, "d": [927], "a": 1 }, + { "px": [128,152], "src": [224,128], "f": 0, "t": 540, "d": [928], "a": 1 }, + { "px": [136,152], "src": [224,128], "f": 0, "t": 540, "d": [929], "a": 1 }, + { "px": [144,152], "src": [224,128], "f": 0, "t": 540, "d": [930], "a": 1 }, + { "px": [152,152], "src": [224,128], "f": 0, "t": 540, "d": [931], "a": 1 }, + { "px": [160,152], "src": [224,128], "f": 0, "t": 540, "d": [932], "a": 1 }, + { "px": [168,152], "src": [224,128], "f": 0, "t": 540, "d": [933], "a": 1 }, + { "px": [176,152], "src": [224,128], "f": 0, "t": 540, "d": [934], "a": 1 }, + { "px": [184,152], "src": [224,128], "f": 0, "t": 540, "d": [935], "a": 1 }, + { "px": [192,152], "src": [224,128], "f": 0, "t": 540, "d": [936], "a": 1 }, + { "px": [200,152], "src": [224,128], "f": 0, "t": 540, "d": [937], "a": 1 }, + { "px": [208,152], "src": [224,128], "f": 0, "t": 540, "d": [938], "a": 1 }, + { "px": [216,152], "src": [224,128], "f": 0, "t": 540, "d": [939], "a": 1 }, + { "px": [224,152], "src": [224,128], "f": 0, "t": 540, "d": [940], "a": 1 }, + { "px": [232,152], "src": [224,128], "f": 0, "t": 540, "d": [941], "a": 1 }, + { "px": [240,152], "src": [224,128], "f": 0, "t": 540, "d": [942], "a": 1 }, + { "px": [248,152], "src": [224,128], "f": 0, "t": 540, "d": [943], "a": 1 }, + { "px": [256,152], "src": [224,128], "f": 0, "t": 540, "d": [944], "a": 1 }, + { "px": [264,152], "src": [224,128], "f": 0, "t": 540, "d": [945], "a": 1 }, + { "px": [272,152], "src": [224,128], "f": 0, "t": 540, "d": [946], "a": 1 }, + { "px": [280,152], "src": [224,128], "f": 0, "t": 540, "d": [947], "a": 1 }, + { "px": [288,152], "src": [224,128], "f": 0, "t": 540, "d": [948], "a": 1 }, + { "px": [296,152], "src": [224,128], "f": 0, "t": 540, "d": [949], "a": 1 }, + { "px": [304,152], "src": [224,128], "f": 0, "t": 540, "d": [950], "a": 1 }, + { "px": [312,152], "src": [224,128], "f": 0, "t": 540, "d": [951], "a": 1 }, + { "px": [320,152], "src": [224,128], "f": 0, "t": 540, "d": [952], "a": 1 }, + { "px": [328,152], "src": [224,128], "f": 0, "t": 540, "d": [953], "a": 1 }, + { "px": [336,152], "src": [224,128], "f": 0, "t": 540, "d": [954], "a": 1 }, + { "px": [344,152], "src": [224,128], "f": 0, "t": 540, "d": [955], "a": 1 }, + { "px": [352,152], "src": [224,128], "f": 0, "t": 540, "d": [956], "a": 1 }, + { "px": [360,152], "src": [224,128], "f": 0, "t": 540, "d": [957], "a": 1 }, + { "px": [368,152], "src": [224,128], "f": 0, "t": 540, "d": [958], "a": 1 }, + { "px": [376,152], "src": [232,128], "f": 0, "t": 541, "d": [959], "a": 1 }, + { "px": [0,160], "src": [216,128], "f": 0, "t": 539, "d": [960], "a": 1 }, + { "px": [8,160], "src": [224,128], "f": 0, "t": 540, "d": [961], "a": 1 }, + { "px": [16,160], "src": [224,128], "f": 0, "t": 540, "d": [962], "a": 1 }, + { "px": [24,160], "src": [224,128], "f": 0, "t": 540, "d": [963], "a": 1 }, + { "px": [32,160], "src": [224,128], "f": 0, "t": 540, "d": [964], "a": 1 }, + { "px": [40,160], "src": [224,128], "f": 0, "t": 540, "d": [965], "a": 1 }, + { "px": [48,160], "src": [224,128], "f": 0, "t": 540, "d": [966], "a": 1 }, + { "px": [56,160], "src": [224,128], "f": 0, "t": 540, "d": [967], "a": 1 }, + { "px": [64,160], "src": [224,128], "f": 0, "t": 540, "d": [968], "a": 1 }, + { "px": [72,160], "src": [224,128], "f": 0, "t": 540, "d": [969], "a": 1 }, + { "px": [80,160], "src": [224,128], "f": 0, "t": 540, "d": [970], "a": 1 }, + { "px": [88,160], "src": [224,128], "f": 0, "t": 540, "d": [971], "a": 1 }, + { "px": [96,160], "src": [224,128], "f": 0, "t": 540, "d": [972], "a": 1 }, + { "px": [104,160], "src": [224,128], "f": 0, "t": 540, "d": [973], "a": 1 }, + { "px": [112,160], "src": [224,128], "f": 0, "t": 540, "d": [974], "a": 1 }, + { "px": [120,160], "src": [224,128], "f": 0, "t": 540, "d": [975], "a": 1 }, + { "px": [128,160], "src": [224,128], "f": 0, "t": 540, "d": [976], "a": 1 }, + { "px": [136,160], "src": [224,128], "f": 0, "t": 540, "d": [977], "a": 1 }, + { "px": [144,160], "src": [224,128], "f": 0, "t": 540, "d": [978], "a": 1 }, + { "px": [152,160], "src": [224,128], "f": 0, "t": 540, "d": [979], "a": 1 }, + { "px": [160,160], "src": [224,128], "f": 0, "t": 540, "d": [980], "a": 1 }, + { "px": [168,160], "src": [224,128], "f": 0, "t": 540, "d": [981], "a": 1 }, + { "px": [176,160], "src": [224,128], "f": 0, "t": 540, "d": [982], "a": 1 }, + { "px": [184,160], "src": [224,128], "f": 0, "t": 540, "d": [983], "a": 1 }, + { "px": [192,160], "src": [224,128], "f": 0, "t": 540, "d": [984], "a": 1 }, + { "px": [200,160], "src": [224,128], "f": 0, "t": 540, "d": [985], "a": 1 }, + { "px": [208,160], "src": [224,128], "f": 0, "t": 540, "d": [986], "a": 1 }, + { "px": [216,160], "src": [224,128], "f": 0, "t": 540, "d": [987], "a": 1 }, + { "px": [224,160], "src": [224,128], "f": 0, "t": 540, "d": [988], "a": 1 }, + { "px": [232,160], "src": [224,128], "f": 0, "t": 540, "d": [989], "a": 1 }, + { "px": [240,160], "src": [224,128], "f": 0, "t": 540, "d": [990], "a": 1 }, + { "px": [248,160], "src": [224,128], "f": 0, "t": 540, "d": [991], "a": 1 }, + { "px": [256,160], "src": [224,128], "f": 0, "t": 540, "d": [992], "a": 1 }, + { "px": [264,160], "src": [224,128], "f": 0, "t": 540, "d": [993], "a": 1 }, + { "px": [272,160], "src": [224,128], "f": 0, "t": 540, "d": [994], "a": 1 }, + { "px": [280,160], "src": [224,128], "f": 0, "t": 540, "d": [995], "a": 1 }, + { "px": [288,160], "src": [224,128], "f": 0, "t": 540, "d": [996], "a": 1 }, + { "px": [296,160], "src": [224,128], "f": 0, "t": 540, "d": [997], "a": 1 }, + { "px": [304,160], "src": [224,128], "f": 0, "t": 540, "d": [998], "a": 1 }, + { "px": [312,160], "src": [224,128], "f": 0, "t": 540, "d": [999], "a": 1 }, + { "px": [320,160], "src": [224,128], "f": 0, "t": 540, "d": [1000], "a": 1 }, + { "px": [328,160], "src": [224,128], "f": 0, "t": 540, "d": [1001], "a": 1 }, + { "px": [336,160], "src": [224,128], "f": 0, "t": 540, "d": [1002], "a": 1 }, + { "px": [344,160], "src": [224,128], "f": 0, "t": 540, "d": [1003], "a": 1 }, + { "px": [352,160], "src": [224,128], "f": 0, "t": 540, "d": [1004], "a": 1 }, + { "px": [360,160], "src": [224,128], "f": 0, "t": 540, "d": [1005], "a": 1 }, + { "px": [368,160], "src": [224,128], "f": 0, "t": 540, "d": [1006], "a": 1 }, + { "px": [376,160], "src": [232,128], "f": 0, "t": 541, "d": [1007], "a": 1 }, + { "px": [0,168], "src": [216,128], "f": 0, "t": 539, "d": [1008], "a": 1 }, + { "px": [8,168], "src": [224,128], "f": 0, "t": 540, "d": [1009], "a": 1 }, + { "px": [16,168], "src": [224,128], "f": 0, "t": 540, "d": [1010], "a": 1 }, + { "px": [24,168], "src": [224,128], "f": 0, "t": 540, "d": [1011], "a": 1 }, + { "px": [32,168], "src": [224,128], "f": 0, "t": 540, "d": [1012], "a": 1 }, + { "px": [40,168], "src": [224,128], "f": 0, "t": 540, "d": [1013], "a": 1 }, + { "px": [48,168], "src": [224,128], "f": 0, "t": 540, "d": [1014], "a": 1 }, + { "px": [56,168], "src": [224,128], "f": 0, "t": 540, "d": [1015], "a": 1 }, + { "px": [64,168], "src": [224,128], "f": 0, "t": 540, "d": [1016], "a": 1 }, + { "px": [72,168], "src": [224,128], "f": 0, "t": 540, "d": [1017], "a": 1 }, + { "px": [80,168], "src": [224,128], "f": 0, "t": 540, "d": [1018], "a": 1 }, + { "px": [88,168], "src": [224,128], "f": 0, "t": 540, "d": [1019], "a": 1 }, + { "px": [96,168], "src": [224,128], "f": 0, "t": 540, "d": [1020], "a": 1 }, + { "px": [104,168], "src": [224,128], "f": 0, "t": 540, "d": [1021], "a": 1 }, + { "px": [112,168], "src": [224,128], "f": 0, "t": 540, "d": [1022], "a": 1 }, + { "px": [120,168], "src": [224,128], "f": 0, "t": 540, "d": [1023], "a": 1 }, + { "px": [128,168], "src": [224,128], "f": 0, "t": 540, "d": [1024], "a": 1 }, + { "px": [136,168], "src": [224,128], "f": 0, "t": 540, "d": [1025], "a": 1 }, + { "px": [144,168], "src": [224,128], "f": 0, "t": 540, "d": [1026], "a": 1 }, + { "px": [152,168], "src": [224,128], "f": 0, "t": 540, "d": [1027], "a": 1 }, + { "px": [160,168], "src": [224,128], "f": 0, "t": 540, "d": [1028], "a": 1 }, + { "px": [168,168], "src": [224,128], "f": 0, "t": 540, "d": [1029], "a": 1 }, + { "px": [176,168], "src": [224,128], "f": 0, "t": 540, "d": [1030], "a": 1 }, + { "px": [184,168], "src": [224,128], "f": 0, "t": 540, "d": [1031], "a": 1 }, + { "px": [192,168], "src": [224,128], "f": 0, "t": 540, "d": [1032], "a": 1 }, + { "px": [200,168], "src": [224,128], "f": 0, "t": 540, "d": [1033], "a": 1 }, + { "px": [208,168], "src": [224,128], "f": 0, "t": 540, "d": [1034], "a": 1 }, + { "px": [216,168], "src": [224,128], "f": 0, "t": 540, "d": [1035], "a": 1 }, + { "px": [224,168], "src": [224,128], "f": 0, "t": 540, "d": [1036], "a": 1 }, + { "px": [232,168], "src": [224,128], "f": 0, "t": 540, "d": [1037], "a": 1 }, + { "px": [240,168], "src": [224,128], "f": 0, "t": 540, "d": [1038], "a": 1 }, + { "px": [248,168], "src": [224,128], "f": 0, "t": 540, "d": [1039], "a": 1 }, + { "px": [256,168], "src": [224,128], "f": 0, "t": 540, "d": [1040], "a": 1 }, + { "px": [264,168], "src": [224,128], "f": 0, "t": 540, "d": [1041], "a": 1 }, + { "px": [272,168], "src": [224,128], "f": 0, "t": 540, "d": [1042], "a": 1 }, + { "px": [280,168], "src": [224,128], "f": 0, "t": 540, "d": [1043], "a": 1 }, + { "px": [288,168], "src": [224,128], "f": 0, "t": 540, "d": [1044], "a": 1 }, + { "px": [296,168], "src": [224,128], "f": 0, "t": 540, "d": [1045], "a": 1 }, + { "px": [304,168], "src": [224,128], "f": 0, "t": 540, "d": [1046], "a": 1 }, + { "px": [312,168], "src": [224,128], "f": 0, "t": 540, "d": [1047], "a": 1 }, + { "px": [320,168], "src": [224,128], "f": 0, "t": 540, "d": [1048], "a": 1 }, + { "px": [328,168], "src": [224,128], "f": 0, "t": 540, "d": [1049], "a": 1 }, + { "px": [336,168], "src": [224,128], "f": 0, "t": 540, "d": [1050], "a": 1 }, + { "px": [344,168], "src": [224,128], "f": 0, "t": 540, "d": [1051], "a": 1 }, + { "px": [352,168], "src": [224,128], "f": 0, "t": 540, "d": [1052], "a": 1 }, + { "px": [360,168], "src": [224,128], "f": 0, "t": 540, "d": [1053], "a": 1 }, + { "px": [368,168], "src": [224,128], "f": 0, "t": 540, "d": [1054], "a": 1 }, + { "px": [376,168], "src": [232,128], "f": 0, "t": 541, "d": [1055], "a": 1 }, + { "px": [0,176], "src": [216,128], "f": 0, "t": 539, "d": [1056], "a": 1 }, + { "px": [8,176], "src": [224,128], "f": 0, "t": 540, "d": [1057], "a": 1 }, + { "px": [16,176], "src": [224,128], "f": 0, "t": 540, "d": [1058], "a": 1 }, + { "px": [24,176], "src": [224,128], "f": 0, "t": 540, "d": [1059], "a": 1 }, + { "px": [32,176], "src": [224,128], "f": 0, "t": 540, "d": [1060], "a": 1 }, + { "px": [40,176], "src": [224,128], "f": 0, "t": 540, "d": [1061], "a": 1 }, + { "px": [48,176], "src": [224,128], "f": 0, "t": 540, "d": [1062], "a": 1 }, + { "px": [56,176], "src": [224,128], "f": 0, "t": 540, "d": [1063], "a": 1 }, + { "px": [64,176], "src": [224,128], "f": 0, "t": 540, "d": [1064], "a": 1 }, + { "px": [72,176], "src": [224,128], "f": 0, "t": 540, "d": [1065], "a": 1 }, + { "px": [80,176], "src": [224,128], "f": 0, "t": 540, "d": [1066], "a": 1 }, + { "px": [88,176], "src": [224,128], "f": 0, "t": 540, "d": [1067], "a": 1 }, + { "px": [96,176], "src": [224,128], "f": 0, "t": 540, "d": [1068], "a": 1 }, + { "px": [104,176], "src": [224,128], "f": 0, "t": 540, "d": [1069], "a": 1 }, + { "px": [112,176], "src": [224,128], "f": 0, "t": 540, "d": [1070], "a": 1 }, + { "px": [120,176], "src": [224,128], "f": 0, "t": 540, "d": [1071], "a": 1 }, + { "px": [128,176], "src": [224,128], "f": 0, "t": 540, "d": [1072], "a": 1 }, + { "px": [136,176], "src": [224,128], "f": 0, "t": 540, "d": [1073], "a": 1 }, + { "px": [144,176], "src": [224,128], "f": 0, "t": 540, "d": [1074], "a": 1 }, + { "px": [152,176], "src": [224,128], "f": 0, "t": 540, "d": [1075], "a": 1 }, + { "px": [160,176], "src": [224,128], "f": 0, "t": 540, "d": [1076], "a": 1 }, + { "px": [168,176], "src": [224,128], "f": 0, "t": 540, "d": [1077], "a": 1 }, + { "px": [176,176], "src": [224,128], "f": 0, "t": 540, "d": [1078], "a": 1 }, + { "px": [184,176], "src": [224,128], "f": 0, "t": 540, "d": [1079], "a": 1 }, + { "px": [192,176], "src": [224,128], "f": 0, "t": 540, "d": [1080], "a": 1 }, + { "px": [200,176], "src": [224,128], "f": 0, "t": 540, "d": [1081], "a": 1 }, + { "px": [208,176], "src": [224,128], "f": 0, "t": 540, "d": [1082], "a": 1 }, + { "px": [216,176], "src": [224,128], "f": 0, "t": 540, "d": [1083], "a": 1 }, + { "px": [224,176], "src": [224,128], "f": 0, "t": 540, "d": [1084], "a": 1 }, + { "px": [232,176], "src": [224,128], "f": 0, "t": 540, "d": [1085], "a": 1 }, + { "px": [240,176], "src": [224,128], "f": 0, "t": 540, "d": [1086], "a": 1 }, + { "px": [248,176], "src": [224,128], "f": 0, "t": 540, "d": [1087], "a": 1 }, + { "px": [256,176], "src": [224,128], "f": 0, "t": 540, "d": [1088], "a": 1 }, + { "px": [264,176], "src": [224,128], "f": 0, "t": 540, "d": [1089], "a": 1 }, + { "px": [272,176], "src": [224,128], "f": 0, "t": 540, "d": [1090], "a": 1 }, + { "px": [280,176], "src": [224,128], "f": 0, "t": 540, "d": [1091], "a": 1 }, + { "px": [288,176], "src": [224,128], "f": 0, "t": 540, "d": [1092], "a": 1 }, + { "px": [296,176], "src": [224,128], "f": 0, "t": 540, "d": [1093], "a": 1 }, + { "px": [304,176], "src": [224,128], "f": 0, "t": 540, "d": [1094], "a": 1 }, + { "px": [312,176], "src": [224,128], "f": 0, "t": 540, "d": [1095], "a": 1 }, + { "px": [320,176], "src": [224,128], "f": 0, "t": 540, "d": [1096], "a": 1 }, + { "px": [328,176], "src": [224,128], "f": 0, "t": 540, "d": [1097], "a": 1 }, + { "px": [336,176], "src": [224,128], "f": 0, "t": 540, "d": [1098], "a": 1 }, + { "px": [344,176], "src": [224,128], "f": 0, "t": 540, "d": [1099], "a": 1 }, + { "px": [352,176], "src": [224,128], "f": 0, "t": 540, "d": [1100], "a": 1 }, + { "px": [360,176], "src": [224,128], "f": 0, "t": 540, "d": [1101], "a": 1 }, + { "px": [368,176], "src": [224,128], "f": 0, "t": 540, "d": [1102], "a": 1 }, + { "px": [376,176], "src": [232,128], "f": 0, "t": 541, "d": [1103], "a": 1 }, + { "px": [0,184], "src": [216,128], "f": 0, "t": 539, "d": [1104], "a": 1 }, + { "px": [8,184], "src": [224,128], "f": 0, "t": 540, "d": [1105], "a": 1 }, + { "px": [16,184], "src": [224,128], "f": 0, "t": 540, "d": [1106], "a": 1 }, + { "px": [24,184], "src": [224,128], "f": 0, "t": 540, "d": [1107], "a": 1 }, + { "px": [32,184], "src": [224,128], "f": 0, "t": 540, "d": [1108], "a": 1 }, + { "px": [40,184], "src": [224,128], "f": 0, "t": 540, "d": [1109], "a": 1 }, + { "px": [48,184], "src": [224,128], "f": 0, "t": 540, "d": [1110], "a": 1 }, + { "px": [56,184], "src": [224,128], "f": 0, "t": 540, "d": [1111], "a": 1 }, + { "px": [64,184], "src": [224,128], "f": 0, "t": 540, "d": [1112], "a": 1 }, + { "px": [72,184], "src": [224,128], "f": 0, "t": 540, "d": [1113], "a": 1 }, + { "px": [80,184], "src": [224,128], "f": 0, "t": 540, "d": [1114], "a": 1 }, + { "px": [88,184], "src": [224,128], "f": 0, "t": 540, "d": [1115], "a": 1 }, + { "px": [96,184], "src": [224,128], "f": 0, "t": 540, "d": [1116], "a": 1 }, + { "px": [104,184], "src": [224,128], "f": 0, "t": 540, "d": [1117], "a": 1 }, + { "px": [112,184], "src": [224,128], "f": 0, "t": 540, "d": [1118], "a": 1 }, + { "px": [120,184], "src": [224,128], "f": 0, "t": 540, "d": [1119], "a": 1 }, + { "px": [128,184], "src": [224,128], "f": 0, "t": 540, "d": [1120], "a": 1 }, + { "px": [136,184], "src": [224,128], "f": 0, "t": 540, "d": [1121], "a": 1 }, + { "px": [144,184], "src": [224,128], "f": 0, "t": 540, "d": [1122], "a": 1 }, + { "px": [152,184], "src": [224,128], "f": 0, "t": 540, "d": [1123], "a": 1 }, + { "px": [160,184], "src": [224,128], "f": 0, "t": 540, "d": [1124], "a": 1 }, + { "px": [168,184], "src": [224,128], "f": 0, "t": 540, "d": [1125], "a": 1 }, + { "px": [176,184], "src": [224,128], "f": 0, "t": 540, "d": [1126], "a": 1 }, + { "px": [184,184], "src": [224,128], "f": 0, "t": 540, "d": [1127], "a": 1 }, + { "px": [192,184], "src": [224,128], "f": 0, "t": 540, "d": [1128], "a": 1 }, + { "px": [200,184], "src": [224,128], "f": 0, "t": 540, "d": [1129], "a": 1 }, + { "px": [208,184], "src": [224,128], "f": 0, "t": 540, "d": [1130], "a": 1 }, + { "px": [216,184], "src": [224,128], "f": 0, "t": 540, "d": [1131], "a": 1 }, + { "px": [224,184], "src": [224,128], "f": 0, "t": 540, "d": [1132], "a": 1 }, + { "px": [232,184], "src": [224,128], "f": 0, "t": 540, "d": [1133], "a": 1 }, + { "px": [240,184], "src": [224,128], "f": 0, "t": 540, "d": [1134], "a": 1 }, + { "px": [248,184], "src": [224,128], "f": 0, "t": 540, "d": [1135], "a": 1 }, + { "px": [256,184], "src": [224,128], "f": 0, "t": 540, "d": [1136], "a": 1 }, + { "px": [264,184], "src": [224,128], "f": 0, "t": 540, "d": [1137], "a": 1 }, + { "px": [272,184], "src": [224,128], "f": 0, "t": 540, "d": [1138], "a": 1 }, + { "px": [280,184], "src": [224,128], "f": 0, "t": 540, "d": [1139], "a": 1 }, + { "px": [288,184], "src": [224,128], "f": 0, "t": 540, "d": [1140], "a": 1 }, + { "px": [296,184], "src": [224,128], "f": 0, "t": 540, "d": [1141], "a": 1 }, + { "px": [304,184], "src": [224,128], "f": 0, "t": 540, "d": [1142], "a": 1 }, + { "px": [312,184], "src": [224,128], "f": 0, "t": 540, "d": [1143], "a": 1 }, + { "px": [320,184], "src": [224,128], "f": 0, "t": 540, "d": [1144], "a": 1 }, + { "px": [328,184], "src": [224,128], "f": 0, "t": 540, "d": [1145], "a": 1 }, + { "px": [336,184], "src": [224,128], "f": 0, "t": 540, "d": [1146], "a": 1 }, + { "px": [344,184], "src": [224,128], "f": 0, "t": 540, "d": [1147], "a": 1 }, + { "px": [352,184], "src": [224,128], "f": 0, "t": 540, "d": [1148], "a": 1 }, + { "px": [360,184], "src": [224,128], "f": 0, "t": 540, "d": [1149], "a": 1 }, + { "px": [368,184], "src": [224,128], "f": 0, "t": 540, "d": [1150], "a": 1 }, + { "px": [376,184], "src": [232,128], "f": 0, "t": 541, "d": [1151], "a": 1 }, + { "px": [0,192], "src": [216,128], "f": 0, "t": 539, "d": [1152], "a": 1 }, + { "px": [8,192], "src": [224,128], "f": 0, "t": 540, "d": [1153], "a": 1 }, + { "px": [16,192], "src": [224,128], "f": 0, "t": 540, "d": [1154], "a": 1 }, + { "px": [24,192], "src": [224,128], "f": 0, "t": 540, "d": [1155], "a": 1 }, + { "px": [32,192], "src": [224,128], "f": 0, "t": 540, "d": [1156], "a": 1 }, + { "px": [40,192], "src": [224,128], "f": 0, "t": 540, "d": [1157], "a": 1 }, + { "px": [48,192], "src": [224,128], "f": 0, "t": 540, "d": [1158], "a": 1 }, + { "px": [56,192], "src": [224,128], "f": 0, "t": 540, "d": [1159], "a": 1 }, + { "px": [64,192], "src": [224,128], "f": 0, "t": 540, "d": [1160], "a": 1 }, + { "px": [72,192], "src": [224,128], "f": 0, "t": 540, "d": [1161], "a": 1 }, + { "px": [80,192], "src": [224,128], "f": 0, "t": 540, "d": [1162], "a": 1 }, + { "px": [88,192], "src": [224,128], "f": 0, "t": 540, "d": [1163], "a": 1 }, + { "px": [96,192], "src": [224,128], "f": 0, "t": 540, "d": [1164], "a": 1 }, + { "px": [104,192], "src": [224,128], "f": 0, "t": 540, "d": [1165], "a": 1 }, + { "px": [112,192], "src": [224,128], "f": 0, "t": 540, "d": [1166], "a": 1 }, + { "px": [120,192], "src": [224,128], "f": 0, "t": 540, "d": [1167], "a": 1 }, + { "px": [128,192], "src": [224,128], "f": 0, "t": 540, "d": [1168], "a": 1 }, + { "px": [136,192], "src": [224,128], "f": 0, "t": 540, "d": [1169], "a": 1 }, + { "px": [144,192], "src": [224,128], "f": 0, "t": 540, "d": [1170], "a": 1 }, + { "px": [152,192], "src": [224,128], "f": 0, "t": 540, "d": [1171], "a": 1 }, + { "px": [160,192], "src": [224,128], "f": 0, "t": 540, "d": [1172], "a": 1 }, + { "px": [168,192], "src": [224,128], "f": 0, "t": 540, "d": [1173], "a": 1 }, + { "px": [176,192], "src": [224,128], "f": 0, "t": 540, "d": [1174], "a": 1 }, + { "px": [184,192], "src": [224,128], "f": 0, "t": 540, "d": [1175], "a": 1 }, + { "px": [192,192], "src": [224,128], "f": 0, "t": 540, "d": [1176], "a": 1 }, + { "px": [200,192], "src": [224,128], "f": 0, "t": 540, "d": [1177], "a": 1 }, + { "px": [208,192], "src": [224,128], "f": 0, "t": 540, "d": [1178], "a": 1 }, + { "px": [216,192], "src": [224,128], "f": 0, "t": 540, "d": [1179], "a": 1 }, + { "px": [224,192], "src": [224,128], "f": 0, "t": 540, "d": [1180], "a": 1 }, + { "px": [232,192], "src": [224,128], "f": 0, "t": 540, "d": [1181], "a": 1 }, + { "px": [240,192], "src": [224,128], "f": 0, "t": 540, "d": [1182], "a": 1 }, + { "px": [248,192], "src": [224,128], "f": 0, "t": 540, "d": [1183], "a": 1 }, + { "px": [256,192], "src": [224,128], "f": 0, "t": 540, "d": [1184], "a": 1 }, + { "px": [264,192], "src": [224,128], "f": 0, "t": 540, "d": [1185], "a": 1 }, + { "px": [272,192], "src": [224,128], "f": 0, "t": 540, "d": [1186], "a": 1 }, + { "px": [280,192], "src": [224,128], "f": 0, "t": 540, "d": [1187], "a": 1 }, + { "px": [288,192], "src": [224,128], "f": 0, "t": 540, "d": [1188], "a": 1 }, + { "px": [296,192], "src": [224,128], "f": 0, "t": 540, "d": [1189], "a": 1 }, + { "px": [304,192], "src": [224,128], "f": 0, "t": 540, "d": [1190], "a": 1 }, + { "px": [312,192], "src": [224,128], "f": 0, "t": 540, "d": [1191], "a": 1 }, + { "px": [320,192], "src": [224,128], "f": 0, "t": 540, "d": [1192], "a": 1 }, + { "px": [328,192], "src": [224,128], "f": 0, "t": 540, "d": [1193], "a": 1 }, + { "px": [336,192], "src": [224,128], "f": 0, "t": 540, "d": [1194], "a": 1 }, + { "px": [344,192], "src": [224,128], "f": 0, "t": 540, "d": [1195], "a": 1 }, + { "px": [352,192], "src": [224,128], "f": 0, "t": 540, "d": [1196], "a": 1 }, + { "px": [360,192], "src": [224,128], "f": 0, "t": 540, "d": [1197], "a": 1 }, + { "px": [368,192], "src": [224,128], "f": 0, "t": 540, "d": [1198], "a": 1 }, + { "px": [376,192], "src": [232,128], "f": 0, "t": 541, "d": [1199], "a": 1 }, + { "px": [0,200], "src": [216,128], "f": 0, "t": 539, "d": [1200], "a": 1 }, + { "px": [8,200], "src": [224,128], "f": 0, "t": 540, "d": [1201], "a": 1 }, + { "px": [16,200], "src": [224,128], "f": 0, "t": 540, "d": [1202], "a": 1 }, + { "px": [24,200], "src": [224,128], "f": 0, "t": 540, "d": [1203], "a": 1 }, + { "px": [32,200], "src": [224,128], "f": 0, "t": 540, "d": [1204], "a": 1 }, + { "px": [40,200], "src": [224,128], "f": 0, "t": 540, "d": [1205], "a": 1 }, + { "px": [48,200], "src": [224,128], "f": 0, "t": 540, "d": [1206], "a": 1 }, + { "px": [56,200], "src": [224,128], "f": 0, "t": 540, "d": [1207], "a": 1 }, + { "px": [64,200], "src": [224,128], "f": 0, "t": 540, "d": [1208], "a": 1 }, + { "px": [72,200], "src": [224,128], "f": 0, "t": 540, "d": [1209], "a": 1 }, + { "px": [80,200], "src": [224,128], "f": 0, "t": 540, "d": [1210], "a": 1 }, + { "px": [88,200], "src": [224,128], "f": 0, "t": 540, "d": [1211], "a": 1 }, + { "px": [96,200], "src": [224,128], "f": 0, "t": 540, "d": [1212], "a": 1 }, + { "px": [104,200], "src": [224,128], "f": 0, "t": 540, "d": [1213], "a": 1 }, + { "px": [112,200], "src": [224,128], "f": 0, "t": 540, "d": [1214], "a": 1 }, + { "px": [120,200], "src": [224,128], "f": 0, "t": 540, "d": [1215], "a": 1 }, + { "px": [128,200], "src": [224,128], "f": 0, "t": 540, "d": [1216], "a": 1 }, + { "px": [136,200], "src": [224,128], "f": 0, "t": 540, "d": [1217], "a": 1 }, + { "px": [144,200], "src": [224,128], "f": 0, "t": 540, "d": [1218], "a": 1 }, + { "px": [152,200], "src": [224,128], "f": 0, "t": 540, "d": [1219], "a": 1 }, + { "px": [160,200], "src": [224,128], "f": 0, "t": 540, "d": [1220], "a": 1 }, + { "px": [168,200], "src": [224,128], "f": 0, "t": 540, "d": [1221], "a": 1 }, + { "px": [176,200], "src": [224,128], "f": 0, "t": 540, "d": [1222], "a": 1 }, + { "px": [184,200], "src": [224,128], "f": 0, "t": 540, "d": [1223], "a": 1 }, + { "px": [192,200], "src": [224,128], "f": 0, "t": 540, "d": [1224], "a": 1 }, + { "px": [200,200], "src": [224,128], "f": 0, "t": 540, "d": [1225], "a": 1 }, + { "px": [208,200], "src": [224,128], "f": 0, "t": 540, "d": [1226], "a": 1 }, + { "px": [216,200], "src": [224,128], "f": 0, "t": 540, "d": [1227], "a": 1 }, + { "px": [224,200], "src": [224,128], "f": 0, "t": 540, "d": [1228], "a": 1 }, + { "px": [232,200], "src": [224,128], "f": 0, "t": 540, "d": [1229], "a": 1 }, + { "px": [240,200], "src": [224,128], "f": 0, "t": 540, "d": [1230], "a": 1 }, + { "px": [248,200], "src": [224,128], "f": 0, "t": 540, "d": [1231], "a": 1 }, + { "px": [256,200], "src": [224,128], "f": 0, "t": 540, "d": [1232], "a": 1 }, + { "px": [264,200], "src": [224,128], "f": 0, "t": 540, "d": [1233], "a": 1 }, + { "px": [272,200], "src": [224,128], "f": 0, "t": 540, "d": [1234], "a": 1 }, + { "px": [280,200], "src": [224,128], "f": 0, "t": 540, "d": [1235], "a": 1 }, + { "px": [288,200], "src": [224,128], "f": 0, "t": 540, "d": [1236], "a": 1 }, + { "px": [296,200], "src": [224,128], "f": 0, "t": 540, "d": [1237], "a": 1 }, + { "px": [304,200], "src": [224,128], "f": 0, "t": 540, "d": [1238], "a": 1 }, + { "px": [312,200], "src": [224,128], "f": 0, "t": 540, "d": [1239], "a": 1 }, + { "px": [320,200], "src": [224,128], "f": 0, "t": 540, "d": [1240], "a": 1 }, + { "px": [328,200], "src": [224,128], "f": 0, "t": 540, "d": [1241], "a": 1 }, + { "px": [336,200], "src": [224,128], "f": 0, "t": 540, "d": [1242], "a": 1 }, + { "px": [344,200], "src": [224,128], "f": 0, "t": 540, "d": [1243], "a": 1 }, + { "px": [352,200], "src": [224,128], "f": 0, "t": 540, "d": [1244], "a": 1 }, + { "px": [360,200], "src": [224,128], "f": 0, "t": 540, "d": [1245], "a": 1 }, + { "px": [368,200], "src": [224,128], "f": 0, "t": 540, "d": [1246], "a": 1 }, + { "px": [376,200], "src": [232,128], "f": 0, "t": 541, "d": [1247], "a": 1 }, + { "px": [0,208], "src": [216,128], "f": 0, "t": 539, "d": [1248], "a": 1 }, + { "px": [8,208], "src": [224,128], "f": 0, "t": 540, "d": [1249], "a": 1 }, + { "px": [16,208], "src": [224,128], "f": 0, "t": 540, "d": [1250], "a": 1 }, + { "px": [24,208], "src": [224,128], "f": 0, "t": 540, "d": [1251], "a": 1 }, + { "px": [32,208], "src": [224,128], "f": 0, "t": 540, "d": [1252], "a": 1 }, + { "px": [40,208], "src": [224,128], "f": 0, "t": 540, "d": [1253], "a": 1 }, + { "px": [48,208], "src": [224,128], "f": 0, "t": 540, "d": [1254], "a": 1 }, + { "px": [56,208], "src": [224,128], "f": 0, "t": 540, "d": [1255], "a": 1 }, + { "px": [64,208], "src": [224,128], "f": 0, "t": 540, "d": [1256], "a": 1 }, + { "px": [72,208], "src": [224,128], "f": 0, "t": 540, "d": [1257], "a": 1 }, + { "px": [80,208], "src": [224,128], "f": 0, "t": 540, "d": [1258], "a": 1 }, + { "px": [88,208], "src": [224,128], "f": 0, "t": 540, "d": [1259], "a": 1 }, + { "px": [96,208], "src": [224,128], "f": 0, "t": 540, "d": [1260], "a": 1 }, + { "px": [104,208], "src": [224,128], "f": 0, "t": 540, "d": [1261], "a": 1 }, + { "px": [112,208], "src": [224,128], "f": 0, "t": 540, "d": [1262], "a": 1 }, + { "px": [120,208], "src": [224,128], "f": 0, "t": 540, "d": [1263], "a": 1 }, + { "px": [128,208], "src": [224,128], "f": 0, "t": 540, "d": [1264], "a": 1 }, + { "px": [136,208], "src": [224,128], "f": 0, "t": 540, "d": [1265], "a": 1 }, + { "px": [144,208], "src": [224,128], "f": 0, "t": 540, "d": [1266], "a": 1 }, + { "px": [152,208], "src": [224,128], "f": 0, "t": 540, "d": [1267], "a": 1 }, + { "px": [160,208], "src": [224,128], "f": 0, "t": 540, "d": [1268], "a": 1 }, + { "px": [168,208], "src": [224,128], "f": 0, "t": 540, "d": [1269], "a": 1 }, + { "px": [176,208], "src": [224,128], "f": 0, "t": 540, "d": [1270], "a": 1 }, + { "px": [184,208], "src": [224,128], "f": 0, "t": 540, "d": [1271], "a": 1 }, + { "px": [192,208], "src": [224,128], "f": 0, "t": 540, "d": [1272], "a": 1 }, + { "px": [200,208], "src": [224,128], "f": 0, "t": 540, "d": [1273], "a": 1 }, + { "px": [208,208], "src": [224,128], "f": 0, "t": 540, "d": [1274], "a": 1 }, + { "px": [216,208], "src": [224,128], "f": 0, "t": 540, "d": [1275], "a": 1 }, + { "px": [224,208], "src": [224,128], "f": 0, "t": 540, "d": [1276], "a": 1 }, + { "px": [232,208], "src": [224,128], "f": 0, "t": 540, "d": [1277], "a": 1 }, + { "px": [240,208], "src": [224,128], "f": 0, "t": 540, "d": [1278], "a": 1 }, + { "px": [248,208], "src": [224,128], "f": 0, "t": 540, "d": [1279], "a": 1 }, + { "px": [256,208], "src": [224,128], "f": 0, "t": 540, "d": [1280], "a": 1 }, + { "px": [264,208], "src": [224,128], "f": 0, "t": 540, "d": [1281], "a": 1 }, + { "px": [272,208], "src": [224,128], "f": 0, "t": 540, "d": [1282], "a": 1 }, + { "px": [280,208], "src": [224,128], "f": 0, "t": 540, "d": [1283], "a": 1 }, + { "px": [288,208], "src": [224,128], "f": 0, "t": 540, "d": [1284], "a": 1 }, + { "px": [296,208], "src": [224,128], "f": 0, "t": 540, "d": [1285], "a": 1 }, + { "px": [304,208], "src": [224,128], "f": 0, "t": 540, "d": [1286], "a": 1 }, + { "px": [312,208], "src": [224,128], "f": 0, "t": 540, "d": [1287], "a": 1 }, + { "px": [320,208], "src": [224,128], "f": 0, "t": 540, "d": [1288], "a": 1 }, + { "px": [328,208], "src": [224,128], "f": 0, "t": 540, "d": [1289], "a": 1 }, + { "px": [336,208], "src": [224,128], "f": 0, "t": 540, "d": [1290], "a": 1 }, + { "px": [344,208], "src": [224,128], "f": 0, "t": 540, "d": [1291], "a": 1 }, + { "px": [352,208], "src": [224,128], "f": 0, "t": 540, "d": [1292], "a": 1 }, + { "px": [360,208], "src": [224,128], "f": 0, "t": 540, "d": [1293], "a": 1 }, + { "px": [368,208], "src": [224,128], "f": 0, "t": 540, "d": [1294], "a": 1 }, + { "px": [376,208], "src": [232,128], "f": 0, "t": 541, "d": [1295], "a": 1 }, + { "px": [0,216], "src": [216,128], "f": 0, "t": 539, "d": [1296], "a": 1 }, + { "px": [8,216], "src": [224,128], "f": 0, "t": 540, "d": [1297], "a": 1 }, + { "px": [16,216], "src": [224,128], "f": 0, "t": 540, "d": [1298], "a": 1 }, + { "px": [24,216], "src": [224,128], "f": 0, "t": 540, "d": [1299], "a": 1 }, + { "px": [32,216], "src": [224,128], "f": 0, "t": 540, "d": [1300], "a": 1 }, + { "px": [40,216], "src": [224,128], "f": 0, "t": 540, "d": [1301], "a": 1 }, + { "px": [48,216], "src": [224,128], "f": 0, "t": 540, "d": [1302], "a": 1 }, + { "px": [56,216], "src": [224,128], "f": 0, "t": 540, "d": [1303], "a": 1 }, + { "px": [64,216], "src": [224,128], "f": 0, "t": 540, "d": [1304], "a": 1 }, + { "px": [72,216], "src": [224,128], "f": 0, "t": 540, "d": [1305], "a": 1 }, + { "px": [80,216], "src": [224,128], "f": 0, "t": 540, "d": [1306], "a": 1 }, + { "px": [88,216], "src": [224,128], "f": 0, "t": 540, "d": [1307], "a": 1 }, + { "px": [96,216], "src": [224,128], "f": 0, "t": 540, "d": [1308], "a": 1 }, + { "px": [104,216], "src": [224,128], "f": 0, "t": 540, "d": [1309], "a": 1 }, + { "px": [112,216], "src": [224,128], "f": 0, "t": 540, "d": [1310], "a": 1 }, + { "px": [120,216], "src": [224,128], "f": 0, "t": 540, "d": [1311], "a": 1 }, + { "px": [128,216], "src": [224,128], "f": 0, "t": 540, "d": [1312], "a": 1 }, + { "px": [136,216], "src": [224,128], "f": 0, "t": 540, "d": [1313], "a": 1 }, + { "px": [144,216], "src": [224,128], "f": 0, "t": 540, "d": [1314], "a": 1 }, + { "px": [152,216], "src": [224,128], "f": 0, "t": 540, "d": [1315], "a": 1 }, + { "px": [160,216], "src": [224,128], "f": 0, "t": 540, "d": [1316], "a": 1 }, + { "px": [168,216], "src": [224,128], "f": 0, "t": 540, "d": [1317], "a": 1 }, + { "px": [176,216], "src": [224,128], "f": 0, "t": 540, "d": [1318], "a": 1 }, + { "px": [184,216], "src": [224,128], "f": 0, "t": 540, "d": [1319], "a": 1 }, + { "px": [192,216], "src": [224,128], "f": 0, "t": 540, "d": [1320], "a": 1 }, + { "px": [200,216], "src": [224,128], "f": 0, "t": 540, "d": [1321], "a": 1 }, + { "px": [208,216], "src": [224,128], "f": 0, "t": 540, "d": [1322], "a": 1 }, + { "px": [216,216], "src": [224,128], "f": 0, "t": 540, "d": [1323], "a": 1 }, + { "px": [224,216], "src": [224,128], "f": 0, "t": 540, "d": [1324], "a": 1 }, + { "px": [232,216], "src": [224,128], "f": 0, "t": 540, "d": [1325], "a": 1 }, + { "px": [240,216], "src": [224,128], "f": 0, "t": 540, "d": [1326], "a": 1 }, + { "px": [248,216], "src": [224,128], "f": 0, "t": 540, "d": [1327], "a": 1 }, + { "px": [256,216], "src": [224,128], "f": 0, "t": 540, "d": [1328], "a": 1 }, + { "px": [264,216], "src": [224,128], "f": 0, "t": 540, "d": [1329], "a": 1 }, + { "px": [272,216], "src": [224,128], "f": 0, "t": 540, "d": [1330], "a": 1 }, + { "px": [280,216], "src": [224,128], "f": 0, "t": 540, "d": [1331], "a": 1 }, + { "px": [288,216], "src": [224,128], "f": 0, "t": 540, "d": [1332], "a": 1 }, + { "px": [296,216], "src": [224,128], "f": 0, "t": 540, "d": [1333], "a": 1 }, + { "px": [304,216], "src": [224,128], "f": 0, "t": 540, "d": [1334], "a": 1 }, + { "px": [312,216], "src": [224,128], "f": 0, "t": 540, "d": [1335], "a": 1 }, + { "px": [320,216], "src": [224,128], "f": 0, "t": 540, "d": [1336], "a": 1 }, + { "px": [328,216], "src": [224,128], "f": 0, "t": 540, "d": [1337], "a": 1 }, + { "px": [336,216], "src": [224,128], "f": 0, "t": 540, "d": [1338], "a": 1 }, + { "px": [344,216], "src": [224,128], "f": 0, "t": 540, "d": [1339], "a": 1 }, + { "px": [352,216], "src": [224,128], "f": 0, "t": 540, "d": [1340], "a": 1 }, + { "px": [360,216], "src": [224,128], "f": 0, "t": 540, "d": [1341], "a": 1 }, + { "px": [368,216], "src": [224,128], "f": 0, "t": 540, "d": [1342], "a": 1 }, + { "px": [376,216], "src": [232,128], "f": 0, "t": 541, "d": [1343], "a": 1 }, + { "px": [0,224], "src": [216,128], "f": 0, "t": 539, "d": [1344], "a": 1 }, + { "px": [8,224], "src": [224,128], "f": 0, "t": 540, "d": [1345], "a": 1 }, + { "px": [16,224], "src": [224,128], "f": 0, "t": 540, "d": [1346], "a": 1 }, + { "px": [24,224], "src": [224,128], "f": 0, "t": 540, "d": [1347], "a": 1 }, + { "px": [32,224], "src": [224,128], "f": 0, "t": 540, "d": [1348], "a": 1 }, + { "px": [40,224], "src": [224,128], "f": 0, "t": 540, "d": [1349], "a": 1 }, + { "px": [48,224], "src": [224,128], "f": 0, "t": 540, "d": [1350], "a": 1 }, + { "px": [56,224], "src": [224,128], "f": 0, "t": 540, "d": [1351], "a": 1 }, + { "px": [64,224], "src": [224,128], "f": 0, "t": 540, "d": [1352], "a": 1 }, + { "px": [72,224], "src": [224,128], "f": 0, "t": 540, "d": [1353], "a": 1 }, + { "px": [80,224], "src": [224,128], "f": 0, "t": 540, "d": [1354], "a": 1 }, + { "px": [88,224], "src": [224,128], "f": 0, "t": 540, "d": [1355], "a": 1 }, + { "px": [96,224], "src": [224,128], "f": 0, "t": 540, "d": [1356], "a": 1 }, + { "px": [104,224], "src": [224,128], "f": 0, "t": 540, "d": [1357], "a": 1 }, + { "px": [112,224], "src": [224,128], "f": 0, "t": 540, "d": [1358], "a": 1 }, + { "px": [120,224], "src": [224,128], "f": 0, "t": 540, "d": [1359], "a": 1 }, + { "px": [128,224], "src": [224,128], "f": 0, "t": 540, "d": [1360], "a": 1 }, + { "px": [136,224], "src": [224,128], "f": 0, "t": 540, "d": [1361], "a": 1 }, + { "px": [144,224], "src": [224,128], "f": 0, "t": 540, "d": [1362], "a": 1 }, + { "px": [152,224], "src": [224,128], "f": 0, "t": 540, "d": [1363], "a": 1 }, + { "px": [160,224], "src": [224,128], "f": 0, "t": 540, "d": [1364], "a": 1 }, + { "px": [168,224], "src": [224,128], "f": 0, "t": 540, "d": [1365], "a": 1 }, + { "px": [176,224], "src": [224,128], "f": 0, "t": 540, "d": [1366], "a": 1 }, + { "px": [184,224], "src": [224,128], "f": 0, "t": 540, "d": [1367], "a": 1 }, + { "px": [192,224], "src": [224,128], "f": 0, "t": 540, "d": [1368], "a": 1 }, + { "px": [200,224], "src": [224,128], "f": 0, "t": 540, "d": [1369], "a": 1 }, + { "px": [208,224], "src": [224,128], "f": 0, "t": 540, "d": [1370], "a": 1 }, + { "px": [216,224], "src": [224,128], "f": 0, "t": 540, "d": [1371], "a": 1 }, + { "px": [224,224], "src": [224,128], "f": 0, "t": 540, "d": [1372], "a": 1 }, + { "px": [232,224], "src": [224,128], "f": 0, "t": 540, "d": [1373], "a": 1 }, + { "px": [240,224], "src": [224,128], "f": 0, "t": 540, "d": [1374], "a": 1 }, + { "px": [248,224], "src": [224,128], "f": 0, "t": 540, "d": [1375], "a": 1 }, + { "px": [256,224], "src": [224,128], "f": 0, "t": 540, "d": [1376], "a": 1 }, + { "px": [264,224], "src": [224,128], "f": 0, "t": 540, "d": [1377], "a": 1 }, + { "px": [272,224], "src": [224,128], "f": 0, "t": 540, "d": [1378], "a": 1 }, + { "px": [280,224], "src": [224,128], "f": 0, "t": 540, "d": [1379], "a": 1 }, + { "px": [288,224], "src": [224,128], "f": 0, "t": 540, "d": [1380], "a": 1 }, + { "px": [296,224], "src": [224,128], "f": 0, "t": 540, "d": [1381], "a": 1 }, + { "px": [304,224], "src": [224,128], "f": 0, "t": 540, "d": [1382], "a": 1 }, + { "px": [312,224], "src": [224,128], "f": 0, "t": 540, "d": [1383], "a": 1 }, + { "px": [320,224], "src": [224,128], "f": 0, "t": 540, "d": [1384], "a": 1 }, + { "px": [328,224], "src": [224,128], "f": 0, "t": 540, "d": [1385], "a": 1 }, + { "px": [336,224], "src": [224,128], "f": 0, "t": 540, "d": [1386], "a": 1 }, + { "px": [344,224], "src": [224,128], "f": 0, "t": 540, "d": [1387], "a": 1 }, + { "px": [352,224], "src": [224,128], "f": 0, "t": 540, "d": [1388], "a": 1 }, + { "px": [360,224], "src": [224,128], "f": 0, "t": 540, "d": [1389], "a": 1 }, + { "px": [368,224], "src": [224,128], "f": 0, "t": 540, "d": [1390], "a": 1 }, + { "px": [376,224], "src": [232,128], "f": 0, "t": 541, "d": [1391], "a": 1 }, + { "px": [0,232], "src": [216,128], "f": 0, "t": 539, "d": [1392], "a": 1 }, + { "px": [8,232], "src": [224,128], "f": 0, "t": 540, "d": [1393], "a": 1 }, + { "px": [16,232], "src": [224,128], "f": 0, "t": 540, "d": [1394], "a": 1 }, + { "px": [24,232], "src": [224,128], "f": 0, "t": 540, "d": [1395], "a": 1 }, + { "px": [32,232], "src": [224,128], "f": 0, "t": 540, "d": [1396], "a": 1 }, + { "px": [40,232], "src": [224,128], "f": 0, "t": 540, "d": [1397], "a": 1 }, + { "px": [48,232], "src": [224,128], "f": 0, "t": 540, "d": [1398], "a": 1 }, + { "px": [56,232], "src": [224,128], "f": 0, "t": 540, "d": [1399], "a": 1 }, + { "px": [64,232], "src": [224,128], "f": 0, "t": 540, "d": [1400], "a": 1 }, + { "px": [72,232], "src": [224,128], "f": 0, "t": 540, "d": [1401], "a": 1 }, + { "px": [80,232], "src": [224,128], "f": 0, "t": 540, "d": [1402], "a": 1 }, + { "px": [88,232], "src": [224,128], "f": 0, "t": 540, "d": [1403], "a": 1 }, + { "px": [96,232], "src": [224,128], "f": 0, "t": 540, "d": [1404], "a": 1 }, + { "px": [104,232], "src": [224,128], "f": 0, "t": 540, "d": [1405], "a": 1 }, + { "px": [112,232], "src": [224,128], "f": 0, "t": 540, "d": [1406], "a": 1 }, + { "px": [120,232], "src": [224,128], "f": 0, "t": 540, "d": [1407], "a": 1 }, + { "px": [128,232], "src": [224,128], "f": 0, "t": 540, "d": [1408], "a": 1 }, + { "px": [136,232], "src": [224,128], "f": 0, "t": 540, "d": [1409], "a": 1 }, + { "px": [144,232], "src": [224,128], "f": 0, "t": 540, "d": [1410], "a": 1 }, + { "px": [152,232], "src": [224,128], "f": 0, "t": 540, "d": [1411], "a": 1 }, + { "px": [160,232], "src": [224,128], "f": 0, "t": 540, "d": [1412], "a": 1 }, + { "px": [168,232], "src": [224,128], "f": 0, "t": 540, "d": [1413], "a": 1 }, + { "px": [176,232], "src": [224,128], "f": 0, "t": 540, "d": [1414], "a": 1 }, + { "px": [184,232], "src": [224,128], "f": 0, "t": 540, "d": [1415], "a": 1 }, + { "px": [192,232], "src": [224,128], "f": 0, "t": 540, "d": [1416], "a": 1 }, + { "px": [200,232], "src": [224,128], "f": 0, "t": 540, "d": [1417], "a": 1 }, + { "px": [208,232], "src": [224,128], "f": 0, "t": 540, "d": [1418], "a": 1 }, + { "px": [216,232], "src": [224,128], "f": 0, "t": 540, "d": [1419], "a": 1 }, + { "px": [224,232], "src": [224,128], "f": 0, "t": 540, "d": [1420], "a": 1 }, + { "px": [232,232], "src": [224,128], "f": 0, "t": 540, "d": [1421], "a": 1 }, + { "px": [240,232], "src": [224,128], "f": 0, "t": 540, "d": [1422], "a": 1 }, + { "px": [248,232], "src": [224,128], "f": 0, "t": 540, "d": [1423], "a": 1 }, + { "px": [256,232], "src": [224,128], "f": 0, "t": 540, "d": [1424], "a": 1 }, + { "px": [264,232], "src": [224,128], "f": 0, "t": 540, "d": [1425], "a": 1 }, + { "px": [272,232], "src": [224,128], "f": 0, "t": 540, "d": [1426], "a": 1 }, + { "px": [280,232], "src": [224,128], "f": 0, "t": 540, "d": [1427], "a": 1 }, + { "px": [288,232], "src": [224,128], "f": 0, "t": 540, "d": [1428], "a": 1 }, + { "px": [296,232], "src": [224,128], "f": 0, "t": 540, "d": [1429], "a": 1 }, + { "px": [304,232], "src": [224,128], "f": 0, "t": 540, "d": [1430], "a": 1 }, + { "px": [312,232], "src": [224,128], "f": 0, "t": 540, "d": [1431], "a": 1 }, + { "px": [320,232], "src": [224,128], "f": 0, "t": 540, "d": [1432], "a": 1 }, + { "px": [328,232], "src": [224,128], "f": 0, "t": 540, "d": [1433], "a": 1 }, + { "px": [336,232], "src": [224,128], "f": 0, "t": 540, "d": [1434], "a": 1 }, + { "px": [344,232], "src": [224,128], "f": 0, "t": 540, "d": [1435], "a": 1 }, + { "px": [352,232], "src": [224,128], "f": 0, "t": 540, "d": [1436], "a": 1 }, + { "px": [360,232], "src": [224,128], "f": 0, "t": 540, "d": [1437], "a": 1 }, + { "px": [368,232], "src": [224,128], "f": 0, "t": 540, "d": [1438], "a": 1 }, + { "px": [376,232], "src": [232,128], "f": 0, "t": 541, "d": [1439], "a": 1 }, + { "px": [0,240], "src": [216,128], "f": 0, "t": 539, "d": [1440], "a": 1 }, + { "px": [8,240], "src": [224,128], "f": 0, "t": 540, "d": [1441], "a": 1 }, + { "px": [16,240], "src": [224,128], "f": 0, "t": 540, "d": [1442], "a": 1 }, + { "px": [24,240], "src": [224,128], "f": 0, "t": 540, "d": [1443], "a": 1 }, + { "px": [32,240], "src": [224,128], "f": 0, "t": 540, "d": [1444], "a": 1 }, + { "px": [40,240], "src": [224,128], "f": 0, "t": 540, "d": [1445], "a": 1 }, + { "px": [48,240], "src": [224,128], "f": 0, "t": 540, "d": [1446], "a": 1 }, + { "px": [56,240], "src": [224,128], "f": 0, "t": 540, "d": [1447], "a": 1 }, + { "px": [64,240], "src": [224,128], "f": 0, "t": 540, "d": [1448], "a": 1 }, + { "px": [72,240], "src": [224,128], "f": 0, "t": 540, "d": [1449], "a": 1 }, + { "px": [80,240], "src": [224,128], "f": 0, "t": 540, "d": [1450], "a": 1 }, + { "px": [88,240], "src": [224,128], "f": 0, "t": 540, "d": [1451], "a": 1 }, + { "px": [96,240], "src": [224,128], "f": 0, "t": 540, "d": [1452], "a": 1 }, + { "px": [104,240], "src": [224,128], "f": 0, "t": 540, "d": [1453], "a": 1 }, + { "px": [112,240], "src": [224,128], "f": 0, "t": 540, "d": [1454], "a": 1 }, + { "px": [120,240], "src": [224,128], "f": 0, "t": 540, "d": [1455], "a": 1 }, + { "px": [128,240], "src": [224,128], "f": 0, "t": 540, "d": [1456], "a": 1 }, + { "px": [136,240], "src": [224,128], "f": 0, "t": 540, "d": [1457], "a": 1 }, + { "px": [144,240], "src": [224,128], "f": 0, "t": 540, "d": [1458], "a": 1 }, + { "px": [152,240], "src": [224,128], "f": 0, "t": 540, "d": [1459], "a": 1 }, + { "px": [160,240], "src": [224,128], "f": 0, "t": 540, "d": [1460], "a": 1 }, + { "px": [168,240], "src": [224,128], "f": 0, "t": 540, "d": [1461], "a": 1 }, + { "px": [176,240], "src": [224,128], "f": 0, "t": 540, "d": [1462], "a": 1 }, + { "px": [184,240], "src": [224,128], "f": 0, "t": 540, "d": [1463], "a": 1 }, + { "px": [192,240], "src": [224,128], "f": 0, "t": 540, "d": [1464], "a": 1 }, + { "px": [200,240], "src": [224,128], "f": 0, "t": 540, "d": [1465], "a": 1 }, + { "px": [208,240], "src": [224,128], "f": 0, "t": 540, "d": [1466], "a": 1 }, + { "px": [216,240], "src": [224,128], "f": 0, "t": 540, "d": [1467], "a": 1 }, + { "px": [224,240], "src": [224,128], "f": 0, "t": 540, "d": [1468], "a": 1 }, + { "px": [232,240], "src": [224,128], "f": 0, "t": 540, "d": [1469], "a": 1 }, + { "px": [240,240], "src": [224,128], "f": 0, "t": 540, "d": [1470], "a": 1 }, + { "px": [248,240], "src": [224,128], "f": 0, "t": 540, "d": [1471], "a": 1 }, + { "px": [256,240], "src": [224,128], "f": 0, "t": 540, "d": [1472], "a": 1 }, + { "px": [264,240], "src": [224,128], "f": 0, "t": 540, "d": [1473], "a": 1 }, + { "px": [272,240], "src": [224,128], "f": 0, "t": 540, "d": [1474], "a": 1 }, + { "px": [280,240], "src": [224,128], "f": 0, "t": 540, "d": [1475], "a": 1 }, + { "px": [288,240], "src": [224,128], "f": 0, "t": 540, "d": [1476], "a": 1 }, + { "px": [296,240], "src": [224,128], "f": 0, "t": 540, "d": [1477], "a": 1 }, + { "px": [304,240], "src": [224,128], "f": 0, "t": 540, "d": [1478], "a": 1 }, + { "px": [312,240], "src": [224,128], "f": 0, "t": 540, "d": [1479], "a": 1 }, + { "px": [320,240], "src": [224,128], "f": 0, "t": 540, "d": [1480], "a": 1 }, + { "px": [328,240], "src": [224,128], "f": 0, "t": 540, "d": [1481], "a": 1 }, + { "px": [336,240], "src": [224,128], "f": 0, "t": 540, "d": [1482], "a": 1 }, + { "px": [344,240], "src": [224,128], "f": 0, "t": 540, "d": [1483], "a": 1 }, + { "px": [352,240], "src": [224,128], "f": 0, "t": 540, "d": [1484], "a": 1 }, + { "px": [360,240], "src": [224,128], "f": 0, "t": 540, "d": [1485], "a": 1 }, + { "px": [368,240], "src": [224,128], "f": 0, "t": 540, "d": [1486], "a": 1 }, + { "px": [376,240], "src": [232,128], "f": 0, "t": 541, "d": [1487], "a": 1 }, + { "px": [0,248], "src": [216,136], "f": 0, "t": 571, "d": [1488], "a": 1 }, + { "px": [8,248], "src": [224,136], "f": 0, "t": 572, "d": [1489], "a": 1 }, + { "px": [16,248], "src": [224,136], "f": 0, "t": 572, "d": [1490], "a": 1 }, + { "px": [24,248], "src": [224,136], "f": 0, "t": 572, "d": [1491], "a": 1 }, + { "px": [32,248], "src": [224,136], "f": 0, "t": 572, "d": [1492], "a": 1 }, + { "px": [40,248], "src": [224,136], "f": 0, "t": 572, "d": [1493], "a": 1 }, + { "px": [48,248], "src": [224,136], "f": 0, "t": 572, "d": [1494], "a": 1 }, + { "px": [56,248], "src": [224,136], "f": 0, "t": 572, "d": [1495], "a": 1 }, + { "px": [64,248], "src": [224,136], "f": 0, "t": 572, "d": [1496], "a": 1 }, + { "px": [72,248], "src": [224,136], "f": 0, "t": 572, "d": [1497], "a": 1 }, + { "px": [80,248], "src": [224,136], "f": 0, "t": 572, "d": [1498], "a": 1 }, + { "px": [88,248], "src": [224,136], "f": 0, "t": 572, "d": [1499], "a": 1 }, + { "px": [96,248], "src": [224,136], "f": 0, "t": 572, "d": [1500], "a": 1 }, + { "px": [104,248], "src": [224,136], "f": 0, "t": 572, "d": [1501], "a": 1 }, + { "px": [112,248], "src": [224,136], "f": 0, "t": 572, "d": [1502], "a": 1 }, + { "px": [120,248], "src": [224,136], "f": 0, "t": 572, "d": [1503], "a": 1 }, + { "px": [128,248], "src": [224,136], "f": 0, "t": 572, "d": [1504], "a": 1 }, + { "px": [136,248], "src": [224,136], "f": 0, "t": 572, "d": [1505], "a": 1 }, + { "px": [144,248], "src": [224,136], "f": 0, "t": 572, "d": [1506], "a": 1 }, + { "px": [152,248], "src": [224,136], "f": 0, "t": 572, "d": [1507], "a": 1 }, + { "px": [160,248], "src": [224,136], "f": 0, "t": 572, "d": [1508], "a": 1 }, + { "px": [168,248], "src": [224,136], "f": 0, "t": 572, "d": [1509], "a": 1 }, + { "px": [176,248], "src": [224,136], "f": 0, "t": 572, "d": [1510], "a": 1 }, + { "px": [184,248], "src": [224,136], "f": 0, "t": 572, "d": [1511], "a": 1 }, + { "px": [192,248], "src": [224,136], "f": 0, "t": 572, "d": [1512], "a": 1 }, + { "px": [200,248], "src": [224,136], "f": 0, "t": 572, "d": [1513], "a": 1 }, + { "px": [208,248], "src": [224,136], "f": 0, "t": 572, "d": [1514], "a": 1 }, + { "px": [216,248], "src": [224,136], "f": 0, "t": 572, "d": [1515], "a": 1 }, + { "px": [224,248], "src": [224,136], "f": 0, "t": 572, "d": [1516], "a": 1 }, + { "px": [232,248], "src": [224,136], "f": 0, "t": 572, "d": [1517], "a": 1 }, + { "px": [240,248], "src": [224,136], "f": 0, "t": 572, "d": [1518], "a": 1 }, + { "px": [248,248], "src": [224,136], "f": 0, "t": 572, "d": [1519], "a": 1 }, + { "px": [256,248], "src": [224,136], "f": 0, "t": 572, "d": [1520], "a": 1 }, + { "px": [264,248], "src": [224,136], "f": 0, "t": 572, "d": [1521], "a": 1 }, + { "px": [272,248], "src": [224,136], "f": 0, "t": 572, "d": [1522], "a": 1 }, + { "px": [280,248], "src": [224,136], "f": 0, "t": 572, "d": [1523], "a": 1 }, + { "px": [288,248], "src": [224,136], "f": 0, "t": 572, "d": [1524], "a": 1 }, + { "px": [296,248], "src": [224,136], "f": 0, "t": 572, "d": [1525], "a": 1 }, + { "px": [304,248], "src": [224,136], "f": 0, "t": 572, "d": [1526], "a": 1 }, + { "px": [312,248], "src": [224,136], "f": 0, "t": 572, "d": [1527], "a": 1 }, + { "px": [320,248], "src": [224,136], "f": 0, "t": 572, "d": [1528], "a": 1 }, + { "px": [328,248], "src": [224,136], "f": 0, "t": 572, "d": [1529], "a": 1 }, + { "px": [336,248], "src": [224,136], "f": 0, "t": 572, "d": [1530], "a": 1 }, + { "px": [344,248], "src": [224,136], "f": 0, "t": 572, "d": [1531], "a": 1 }, + { "px": [352,248], "src": [224,136], "f": 0, "t": 572, "d": [1532], "a": 1 }, + { "px": [360,248], "src": [224,136], "f": 0, "t": 572, "d": [1533], "a": 1 }, + { "px": [368,248], "src": [224,136], "f": 0, "t": 572, "d": [1534], "a": 1 }, + { "px": [376,248], "src": [232,136], "f": 0, "t": 573, "d": [1535], "a": 1 } + ], + "entityInstances": [] + } + ], + "__neighbours": [] + }, + { + "identifier": "SfxEditor", + "iid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "uid": 56, + "worldX": 416, + "worldY": 0, + "worldDepth": 0, + "pxWid": 384, + "pxHei": 256, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelId": 56, + "layerDefUid": 6, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 791332, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Dropdown", + "__grid": [12,4], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "5e14b916-fa90-11f0-a3f9-71de04f3e0fe", + "width": 192, + "height": 16, + "defUid": 2, + "px": [100,36], + "fieldInstances": [], + "__worldX": 516, + "__worldY": 36 + }, + { + "__identifier": "Button", + "__grid": [10,4], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "5e14b917-fa90-11f0-a3f9-6785f3b40f16", + "width": 13, + "height": 13, + "defUid": 1, + "px": [86,37], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": "RandomSfxModal", "__tile": null, "defUid": 13, "realEditorValues": [{ + "id": "V_String", + "params": ["RandomSfxModal"] + }] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Random", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Random"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 502, + "__worldY": 37 + }, + { + "__identifier": "Button", + "__grid": [36,4], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "5e14b918-fa90-11f0-a3f9-9ba06dec2d6d", + "width": 13, + "height": 13, + "defUid": 1, + "px": [293,37], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": "NameModal", "__tile": null, "defUid": 13, "realEditorValues": [{ + "id": "V_String", + "params": ["NameModal"] + }] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Gear", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Gear"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 709, + "__worldY": 37 + }, + { + "__identifier": "Button", + "__grid": [6,4], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "9e84bf10-fa90-11f0-ba84-b945bf57e9bc", + "width": 13, + "height": 13, + "defUid": 1, + "px": [48,38], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Play", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Play"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 464, + "__worldY": 38 + }, + { + "__identifier": "VelocityEditor", + "__grid": [20,24], + "__pivot": [0,0], + "__tags": [], + "__tile": null, + "__smartColor": "#63C74D", + "iid": "a22c4340-fa90-11f0-ba84-7dd5ac850aaa", + "width": 207, + "height": 44, + "defUid": 62, + "px": [164,198], + "fieldInstances": [], + "__worldX": 580, + "__worldY": 198 + }, + { + "__identifier": "Player", + "__grid": [2,4], + "__pivot": [0,0], + "__tags": ["Orchestrator"], + "__tile": null, + "__smartColor": "#0099DB", + "iid": "a3b3cda0-fa90-11f0-ba84-e36a05ae2a0b", + "width": 16, + "height": 16, + "defUid": 63, + "px": [16,32], + "fieldInstances": [ + { "__identifier": "SfxEditor", "__type": "EntityRef", "__value": { + "entityIid": "a7054290-fa90-11f0-ba84-3b073e91b88b", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 64, "realEditorValues": [{ + "id": "V_String", + "params": ["a7054290-fa90-11f0-ba84-3b073e91b88b"] + }] }, + { "__identifier": "VelocityEditor", "__type": "EntityRef", "__value": { + "entityIid": "a22c4340-fa90-11f0-ba84-7dd5ac850aaa", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 65, "realEditorValues": [{ + "id": "V_String", + "params": ["a22c4340-fa90-11f0-ba84-7dd5ac850aaa"] + }] }, + { "__identifier": "BPM", "__type": "EntityRef", "__value": { + "entityIid": "af836680-fa90-11f0-94ff-096356e668da", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 66, "realEditorValues": [{ + "id": "V_String", + "params": ["af836680-fa90-11f0-94ff-096356e668da"] + }] }, + { "__identifier": "SaveButton", "__type": "EntityRef", "__value": { + "entityIid": "6ba8e0c0-fa90-11f0-8348-1bb51e69295e", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 67, "realEditorValues": [{ + "id": "V_String", + "params": ["6ba8e0c0-fa90-11f0-8348-1bb51e69295e"] + }] }, + { "__identifier": "PlayButton", "__type": "EntityRef", "__value": { + "entityIid": "9e84bf10-fa90-11f0-ba84-b945bf57e9bc", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 68, "realEditorValues": [{ + "id": "V_String", + "params": ["9e84bf10-fa90-11f0-ba84-b945bf57e9bc"] + }] }, + { "__identifier": "ExportButton", "__type": "EntityRef", "__value": { + "entityIid": "682a6ec0-fa90-11f0-8348-9b69b054fb12", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 69, "realEditorValues": [{ + "id": "V_String", + "params": ["682a6ec0-fa90-11f0-8348-9b69b054fb12"] + }] }, + { "__identifier": "SfxSelector", "__type": "EntityRef", "__value": { + "entityIid": "5e14b916-fa90-11f0-a3f9-71de04f3e0fe", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 85, "realEditorValues": [{ + "id": "V_String", + "params": ["5e14b916-fa90-11f0-a3f9-71de04f3e0fe"] + }] } + ], + "__worldX": 432, + "__worldY": 32 + }, + { + "__identifier": "SfxEditor", + "__grid": [20,8], + "__pivot": [0,0], + "__tags": ["Orchestrator"], + "__tile": null, + "__smartColor": "#124E89", + "iid": "a7054290-fa90-11f0-ba84-3b073e91b88b", + "width": 207, + "height": 119, + "defUid": 59, + "px": [164,68], + "fieldInstances": [ + { "__identifier": "BPM", "__type": "EntityRef", "__value": { + "entityIid": "af836680-fa90-11f0-94ff-096356e668da", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 60, "realEditorValues": [{ + "id": "V_String", + "params": ["af836680-fa90-11f0-94ff-096356e668da"] + }] }, + { "__identifier": "Instrument", "__type": "EntityRef", "__value": { + "entityIid": "a97928c0-fa90-11f0-ba84-6b6725ae5b0d", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 61, "realEditorValues": [{ + "id": "V_String", + "params": ["a97928c0-fa90-11f0-ba84-6b6725ae5b0d"] + }] }, + { "__identifier": "Octave", "__type": "EntityRef", "__value": { + "entityIid": "536fc690-fa90-11f0-94ff-0d4a70c12fe8", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 87, "realEditorValues": [{ + "id": "V_String", + "params": ["536fc690-fa90-11f0-94ff-0d4a70c12fe8"] + }] }, + { "__identifier": "Volume", "__type": "EntityRef", "__value": { + "entityIid": "d0afa410-fa90-11f0-911a-01726ff4a6df", + "layerIid": "5e14b915-fa90-11f0-a3f9-ab74689b52a1", + "levelIid": "5e146af0-fa90-11f0-a3f9-6d10d9fa5d38", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 88, "realEditorValues": [{ + "id": "V_String", + "params": ["d0afa410-fa90-11f0-911a-01726ff4a6df"] + }] } + ], + "__worldX": 580, + "__worldY": 68 + }, + { + "__identifier": "Dropdown", + "__grid": [1,8], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "a97928c0-fa90-11f0-ba84-6b6725ae5b0d", + "width": 144, + "height": 16, + "defUid": 2, + "px": [8,68], + "fieldInstances": [], + "__worldX": 424, + "__worldY": 68 + }, + { + "__identifier": "TextButton", + "__grid": [0,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "4aff5de0-fa90-11f0-8348-77e313aaa6ba", + "width": 88, + "height": 16, + "defUid": 78, + "px": [4,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "←→ Instrument", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["←→ Instrument"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ false ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Green", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Green"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": "tiny-instrument-editor.lua", "__tile": null, "defUid": 83, "realEditorValues": [{ + "id": "V_String", + "params": ["tiny-instrument-editor.lua"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 420, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [12,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "4b8b2320-fa90-11f0-8348-8b37563fea15", + "width": 96, + "height": 16, + "defUid": 78, + "px": [96,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↑↓ sound effect", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↑↓ sound effect"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": true, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ true ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Yellow", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Yellow"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 512, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [45,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "6ba8e0c0-fa90-11f0-8348-1bb51e69295e", + "width": 18, + "height": 16, + "defUid": 78, + "px": [360,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↧↨", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↧↨"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Red", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Red"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": "Save", "__tile": null, "defUid": 84, "realEditorValues": [{ + "id": "V_String", + "params": ["Save"] + }] } + ], + "__worldX": 776, + "__worldY": 4 + }, + { + "__identifier": "Speaker", + "__grid": [3,20], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 72, "x": 208, "y": 0, "w": 48, "h": 96 }, + "__smartColor": "#C0CBDC", + "iid": "6acfc960-fa90-11f0-8348-5572045e70e2", + "width": 98, + "height": 96, + "defUid": 76, + "px": [28,160], + "fieldInstances": [], + "__worldX": 444, + "__worldY": 160 + }, + { + "__identifier": "TextButton", + "__grid": [39,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "682a6ec0-fa90-11f0-8348-9b69b054fb12", + "width": 44, + "height": 16, + "defUid": 78, + "px": [314,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "Export", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["Export"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["LigthBlue"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 730, + "__worldY": 4 + }, + { + "__identifier": "Fader", + "__grid": [7,15], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "d0afa410-fa90-11f0-911a-01726ff4a6df", + "width": 8, + "height": 36, + "defUid": 77, + "px": [58,120], + "fieldInstances": [], + "__worldX": 474, + "__worldY": 120 + }, + { + "__identifier": "Counter", + "__grid": [13,11], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": null, + "__smartColor": "#2CE8F5", + "iid": "536fc690-fa90-11f0-94ff-0d4a70c12fe8", + "width": 40, + "height": 16, + "defUid": 86, + "px": [110,92], + "fieldInstances": [], + "__worldX": 526, + "__worldY": 92 + }, + { + "__identifier": "Fader", + "__grid": [17,15], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "af836680-fa90-11f0-94ff-096356e668da", + "width": 8, + "height": 36, + "defUid": 77, + "px": [140,120], + "fieldInstances": [], + "__worldX": 556, + "__worldY": 120 + }, + { + "__identifier": "TextButton", + "__grid": [24,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "196c9920-fa90-11f0-9ead-450e952106e4", + "width": 60, + "height": 16, + "defUid": 78, + "px": [196,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↔↕Music", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↔↕Music"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Orange", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Orange"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": "tiny-music-editor.lua", "__tile": null, "defUid": 83, "realEditorValues": [{ + "id": "V_String", + "params": ["tiny-music-editor.lua"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 612, + "__worldY": 4 + } + ] + }, + { + "__identifier": "Panels", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "812baec1-fa90-11f0-ade1-13b121b15583", + "levelId": 56, + "layerDefUid": 75, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 810868, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Panel", + "__grid": [10,4], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "ad2fce20-fa90-11f0-8348-0d9a8ef0dab0", + "width": 232, + "height": 24, + "defUid": 71, + "px": [80,32], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 496, + "__worldY": 32 + }, + { + "__identifier": "Panel", + "__grid": [20,8], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "b0198090-fa90-11f0-8348-1b7f5c4daba7", + "width": 216, + "height": 128, + "defUid": 71, + "px": [160,64], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Notes", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Notes"] + }] } + ], + "__worldX": 576, + "__worldY": 64 + }, + { + "__identifier": "Panel", + "__grid": [20,24], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "b3465680-fa90-11f0-8348-afc81b80bc64", + "width": 216, + "height": 56, + "defUid": 71, + "px": [160,192], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Volume", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Volume"] + }] } + ], + "__worldX": 576, + "__worldY": 192 + }, + { + "__identifier": "Panel", + "__grid": [9,14], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "bd1ccd10-fa90-11f0-8348-6baae85937c0", + "width": 80, + "height": 48, + "defUid": 71, + "px": [76,112], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Speed", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Speed"] + }] } + ], + "__worldX": 492, + "__worldY": 112 + }, + { + "__identifier": "Panel", + "__grid": [0,14], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "c1fafc80-fa90-11f0-8348-673be7713b0d", + "width": 72, + "height": 48, + "defUid": 71, + "px": [4,112], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Volume", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Volume"] + }] } + ], + "__worldX": 420, + "__worldY": 112 + }, + { + "__identifier": "Panel", + "__grid": [0,11], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "c6496f10-fa90-11f0-8348-d9bfe0e484ba", + "width": 152, + "height": 24, + "defUid": 71, + "px": [4,88], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Octave", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Octave"] + }] } + ], + "__worldX": 420, + "__worldY": 88 + }, + { + "__identifier": "Panel", + "__grid": [0,8], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "ce1d5ee0-fa90-11f0-8348-7b7ae9cde07e", + "width": 152, + "height": 24, + "defUid": 71, + "px": [4,64], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": "Instrument", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Instrument"] + }] } + ], + "__worldX": 420, + "__worldY": 64 + } + ] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 72, + "__tilesetRelPath": "sprite-sheet.png", + "iid": "5e14b91e-fa90-11f0-a3f9-f3ffb39b4148", + "levelId": 56, + "layerDefUid": 10, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 2187549, + "overrideTilesetUid": null, + "gridTiles": [ + { "px": [0,0], "src": [216,104], "f": 0, "t": 443, "d": [0], "a": 1 }, + { "px": [8,0], "src": [224,104], "f": 0, "t": 444, "d": [1], "a": 1 }, + { "px": [16,0], "src": [224,104], "f": 0, "t": 444, "d": [2], "a": 1 }, + { "px": [24,0], "src": [224,104], "f": 0, "t": 444, "d": [3], "a": 1 }, + { "px": [32,0], "src": [224,104], "f": 0, "t": 444, "d": [4], "a": 1 }, + { "px": [40,0], "src": [224,104], "f": 0, "t": 444, "d": [5], "a": 1 }, + { "px": [48,0], "src": [224,104], "f": 0, "t": 444, "d": [6], "a": 1 }, + { "px": [56,0], "src": [224,104], "f": 0, "t": 444, "d": [7], "a": 1 }, + { "px": [64,0], "src": [224,104], "f": 0, "t": 444, "d": [8], "a": 1 }, + { "px": [72,0], "src": [224,104], "f": 0, "t": 444, "d": [9], "a": 1 }, + { "px": [80,0], "src": [224,104], "f": 0, "t": 444, "d": [10], "a": 1 }, + { "px": [88,0], "src": [224,104], "f": 0, "t": 444, "d": [11], "a": 1 }, + { "px": [96,0], "src": [224,104], "f": 0, "t": 444, "d": [12], "a": 1 }, + { "px": [104,0], "src": [224,104], "f": 0, "t": 444, "d": [13], "a": 1 }, + { "px": [112,0], "src": [224,104], "f": 0, "t": 444, "d": [14], "a": 1 }, + { "px": [120,0], "src": [224,104], "f": 0, "t": 444, "d": [15], "a": 1 }, + { "px": [128,0], "src": [224,104], "f": 0, "t": 444, "d": [16], "a": 1 }, + { "px": [136,0], "src": [224,104], "f": 0, "t": 444, "d": [17], "a": 1 }, + { "px": [144,0], "src": [224,104], "f": 0, "t": 444, "d": [18], "a": 1 }, + { "px": [152,0], "src": [224,104], "f": 0, "t": 444, "d": [19], "a": 1 }, + { "px": [160,0], "src": [224,104], "f": 0, "t": 444, "d": [20], "a": 1 }, + { "px": [168,0], "src": [224,104], "f": 0, "t": 444, "d": [21], "a": 1 }, + { "px": [176,0], "src": [224,104], "f": 0, "t": 444, "d": [22], "a": 1 }, + { "px": [184,0], "src": [224,104], "f": 0, "t": 444, "d": [23], "a": 1 }, + { "px": [192,0], "src": [224,104], "f": 0, "t": 444, "d": [24], "a": 1 }, + { "px": [200,0], "src": [224,104], "f": 0, "t": 444, "d": [25], "a": 1 }, + { "px": [208,0], "src": [224,104], "f": 0, "t": 444, "d": [26], "a": 1 }, + { "px": [216,0], "src": [224,104], "f": 0, "t": 444, "d": [27], "a": 1 }, + { "px": [224,0], "src": [224,104], "f": 0, "t": 444, "d": [28], "a": 1 }, + { "px": [232,0], "src": [224,104], "f": 0, "t": 444, "d": [29], "a": 1 }, + { "px": [240,0], "src": [224,104], "f": 0, "t": 444, "d": [30], "a": 1 }, + { "px": [248,0], "src": [224,104], "f": 0, "t": 444, "d": [31], "a": 1 }, + { "px": [256,0], "src": [224,104], "f": 0, "t": 444, "d": [32], "a": 1 }, + { "px": [264,0], "src": [224,104], "f": 0, "t": 444, "d": [33], "a": 1 }, + { "px": [272,0], "src": [224,104], "f": 0, "t": 444, "d": [34], "a": 1 }, + { "px": [280,0], "src": [224,104], "f": 0, "t": 444, "d": [35], "a": 1 }, + { "px": [288,0], "src": [224,104], "f": 0, "t": 444, "d": [36], "a": 1 }, + { "px": [296,0], "src": [224,104], "f": 0, "t": 444, "d": [37], "a": 1 }, + { "px": [304,0], "src": [224,104], "f": 0, "t": 444, "d": [38], "a": 1 }, + { "px": [312,0], "src": [224,104], "f": 0, "t": 444, "d": [39], "a": 1 }, + { "px": [320,0], "src": [224,104], "f": 0, "t": 444, "d": [40], "a": 1 }, + { "px": [328,0], "src": [224,104], "f": 0, "t": 444, "d": [41], "a": 1 }, + { "px": [336,0], "src": [224,104], "f": 0, "t": 444, "d": [42], "a": 1 }, + { "px": [344,0], "src": [224,104], "f": 0, "t": 444, "d": [43], "a": 1 }, + { "px": [352,0], "src": [224,104], "f": 0, "t": 444, "d": [44], "a": 1 }, + { "px": [360,0], "src": [224,104], "f": 0, "t": 444, "d": [45], "a": 1 }, + { "px": [368,0], "src": [224,104], "f": 0, "t": 444, "d": [46], "a": 1 }, + { "px": [376,0], "src": [232,104], "f": 0, "t": 445, "d": [47], "a": 1 }, + { "px": [0,8], "src": [216,112], "f": 0, "t": 475, "d": [48], "a": 1 }, + { "px": [8,8], "src": [224,112], "f": 0, "t": 476, "d": [49], "a": 1 }, + { "px": [16,8], "src": [224,112], "f": 0, "t": 476, "d": [50], "a": 1 }, + { "px": [24,8], "src": [224,112], "f": 0, "t": 476, "d": [51], "a": 1 }, + { "px": [32,8], "src": [224,112], "f": 0, "t": 476, "d": [52], "a": 1 }, + { "px": [40,8], "src": [224,112], "f": 0, "t": 476, "d": [53], "a": 1 }, + { "px": [48,8], "src": [224,112], "f": 0, "t": 476, "d": [54], "a": 1 }, + { "px": [56,8], "src": [224,112], "f": 0, "t": 476, "d": [55], "a": 1 }, + { "px": [64,8], "src": [224,112], "f": 0, "t": 476, "d": [56], "a": 1 }, + { "px": [72,8], "src": [224,112], "f": 0, "t": 476, "d": [57], "a": 1 }, + { "px": [80,8], "src": [224,112], "f": 0, "t": 476, "d": [58], "a": 1 }, + { "px": [88,8], "src": [224,112], "f": 0, "t": 476, "d": [59], "a": 1 }, + { "px": [96,8], "src": [224,112], "f": 0, "t": 476, "d": [60], "a": 1 }, + { "px": [104,8], "src": [224,112], "f": 0, "t": 476, "d": [61], "a": 1 }, + { "px": [112,8], "src": [224,112], "f": 0, "t": 476, "d": [62], "a": 1 }, + { "px": [120,8], "src": [224,112], "f": 0, "t": 476, "d": [63], "a": 1 }, + { "px": [128,8], "src": [224,112], "f": 0, "t": 476, "d": [64], "a": 1 }, + { "px": [136,8], "src": [224,112], "f": 0, "t": 476, "d": [65], "a": 1 }, + { "px": [144,8], "src": [224,112], "f": 0, "t": 476, "d": [66], "a": 1 }, + { "px": [152,8], "src": [224,112], "f": 0, "t": 476, "d": [67], "a": 1 }, + { "px": [160,8], "src": [224,112], "f": 0, "t": 476, "d": [68], "a": 1 }, + { "px": [168,8], "src": [224,112], "f": 0, "t": 476, "d": [69], "a": 1 }, + { "px": [176,8], "src": [224,112], "f": 0, "t": 476, "d": [70], "a": 1 }, + { "px": [184,8], "src": [224,112], "f": 0, "t": 476, "d": [71], "a": 1 }, + { "px": [192,8], "src": [224,112], "f": 0, "t": 476, "d": [72], "a": 1 }, + { "px": [200,8], "src": [224,112], "f": 0, "t": 476, "d": [73], "a": 1 }, + { "px": [208,8], "src": [224,112], "f": 0, "t": 476, "d": [74], "a": 1 }, + { "px": [216,8], "src": [224,112], "f": 0, "t": 476, "d": [75], "a": 1 }, + { "px": [224,8], "src": [224,112], "f": 0, "t": 476, "d": [76], "a": 1 }, + { "px": [232,8], "src": [224,112], "f": 0, "t": 476, "d": [77], "a": 1 }, + { "px": [240,8], "src": [224,112], "f": 0, "t": 476, "d": [78], "a": 1 }, + { "px": [248,8], "src": [224,112], "f": 0, "t": 476, "d": [79], "a": 1 }, + { "px": [256,8], "src": [224,112], "f": 0, "t": 476, "d": [80], "a": 1 }, + { "px": [264,8], "src": [224,112], "f": 0, "t": 476, "d": [81], "a": 1 }, + { "px": [272,8], "src": [224,112], "f": 0, "t": 476, "d": [82], "a": 1 }, + { "px": [280,8], "src": [224,112], "f": 0, "t": 476, "d": [83], "a": 1 }, + { "px": [288,8], "src": [224,112], "f": 0, "t": 476, "d": [84], "a": 1 }, + { "px": [296,8], "src": [224,112], "f": 0, "t": 476, "d": [85], "a": 1 }, + { "px": [304,8], "src": [224,112], "f": 0, "t": 476, "d": [86], "a": 1 }, + { "px": [312,8], "src": [224,112], "f": 0, "t": 476, "d": [87], "a": 1 }, + { "px": [320,8], "src": [224,112], "f": 0, "t": 476, "d": [88], "a": 1 }, + { "px": [328,8], "src": [224,112], "f": 0, "t": 476, "d": [89], "a": 1 }, + { "px": [336,8], "src": [224,112], "f": 0, "t": 476, "d": [90], "a": 1 }, + { "px": [344,8], "src": [224,112], "f": 0, "t": 476, "d": [91], "a": 1 }, + { "px": [352,8], "src": [224,112], "f": 0, "t": 476, "d": [92], "a": 1 }, + { "px": [360,8], "src": [224,112], "f": 0, "t": 476, "d": [93], "a": 1 }, + { "px": [368,8], "src": [224,112], "f": 0, "t": 476, "d": [94], "a": 1 }, + { "px": [376,8], "src": [232,112], "f": 0, "t": 477, "d": [95], "a": 1 }, + { "px": [0,16], "src": [216,120], "f": 0, "t": 507, "d": [96], "a": 1 }, + { "px": [8,16], "src": [224,120], "f": 0, "t": 508, "d": [97], "a": 1 }, + { "px": [16,16], "src": [224,120], "f": 0, "t": 508, "d": [98], "a": 1 }, + { "px": [24,16], "src": [224,120], "f": 0, "t": 508, "d": [99], "a": 1 }, + { "px": [32,16], "src": [224,120], "f": 0, "t": 508, "d": [100], "a": 1 }, + { "px": [40,16], "src": [224,120], "f": 0, "t": 508, "d": [101], "a": 1 }, + { "px": [48,16], "src": [224,120], "f": 0, "t": 508, "d": [102], "a": 1 }, + { "px": [56,16], "src": [224,120], "f": 0, "t": 508, "d": [103], "a": 1 }, + { "px": [64,16], "src": [224,120], "f": 0, "t": 508, "d": [104], "a": 1 }, + { "px": [72,16], "src": [224,120], "f": 0, "t": 508, "d": [105], "a": 1 }, + { "px": [80,16], "src": [224,120], "f": 0, "t": 508, "d": [106], "a": 1 }, + { "px": [88,16], "src": [224,120], "f": 0, "t": 508, "d": [107], "a": 1 }, + { "px": [96,16], "src": [224,120], "f": 0, "t": 508, "d": [108], "a": 1 }, + { "px": [104,16], "src": [224,120], "f": 0, "t": 508, "d": [109], "a": 1 }, + { "px": [112,16], "src": [224,120], "f": 0, "t": 508, "d": [110], "a": 1 }, + { "px": [120,16], "src": [224,120], "f": 0, "t": 508, "d": [111], "a": 1 }, + { "px": [128,16], "src": [224,120], "f": 0, "t": 508, "d": [112], "a": 1 }, + { "px": [136,16], "src": [224,120], "f": 0, "t": 508, "d": [113], "a": 1 }, + { "px": [144,16], "src": [224,120], "f": 0, "t": 508, "d": [114], "a": 1 }, + { "px": [152,16], "src": [224,120], "f": 0, "t": 508, "d": [115], "a": 1 }, + { "px": [160,16], "src": [224,120], "f": 0, "t": 508, "d": [116], "a": 1 }, + { "px": [168,16], "src": [224,120], "f": 0, "t": 508, "d": [117], "a": 1 }, + { "px": [176,16], "src": [224,120], "f": 0, "t": 508, "d": [118], "a": 1 }, + { "px": [184,16], "src": [224,120], "f": 0, "t": 508, "d": [119], "a": 1 }, + { "px": [192,16], "src": [224,120], "f": 0, "t": 508, "d": [120], "a": 1 }, + { "px": [200,16], "src": [224,120], "f": 0, "t": 508, "d": [121], "a": 1 }, + { "px": [208,16], "src": [224,120], "f": 0, "t": 508, "d": [122], "a": 1 }, + { "px": [216,16], "src": [224,120], "f": 0, "t": 508, "d": [123], "a": 1 }, + { "px": [224,16], "src": [224,120], "f": 0, "t": 508, "d": [124], "a": 1 }, + { "px": [232,16], "src": [224,120], "f": 0, "t": 508, "d": [125], "a": 1 }, + { "px": [240,16], "src": [224,120], "f": 0, "t": 508, "d": [126], "a": 1 }, + { "px": [248,16], "src": [224,120], "f": 0, "t": 508, "d": [127], "a": 1 }, + { "px": [256,16], "src": [224,120], "f": 0, "t": 508, "d": [128], "a": 1 }, + { "px": [264,16], "src": [224,120], "f": 0, "t": 508, "d": [129], "a": 1 }, + { "px": [272,16], "src": [224,120], "f": 0, "t": 508, "d": [130], "a": 1 }, + { "px": [280,16], "src": [224,120], "f": 0, "t": 508, "d": [131], "a": 1 }, + { "px": [288,16], "src": [224,120], "f": 0, "t": 508, "d": [132], "a": 1 }, + { "px": [296,16], "src": [224,120], "f": 0, "t": 508, "d": [133], "a": 1 }, + { "px": [304,16], "src": [224,120], "f": 0, "t": 508, "d": [134], "a": 1 }, + { "px": [312,16], "src": [224,120], "f": 0, "t": 508, "d": [135], "a": 1 }, + { "px": [320,16], "src": [224,120], "f": 0, "t": 508, "d": [136], "a": 1 }, + { "px": [328,16], "src": [224,120], "f": 0, "t": 508, "d": [137], "a": 1 }, + { "px": [336,16], "src": [224,120], "f": 0, "t": 508, "d": [138], "a": 1 }, + { "px": [344,16], "src": [224,120], "f": 0, "t": 508, "d": [139], "a": 1 }, + { "px": [352,16], "src": [224,120], "f": 0, "t": 508, "d": [140], "a": 1 }, + { "px": [360,16], "src": [224,120], "f": 0, "t": 508, "d": [141], "a": 1 }, + { "px": [368,16], "src": [224,120], "f": 0, "t": 508, "d": [142], "a": 1 }, + { "px": [376,16], "src": [232,120], "f": 0, "t": 509, "d": [143], "a": 1 }, + { "px": [0,24], "src": [216,128], "f": 0, "t": 539, "d": [144], "a": 1 }, + { "px": [8,24], "src": [224,128], "f": 0, "t": 540, "d": [145], "a": 1 }, + { "px": [16,24], "src": [224,128], "f": 0, "t": 540, "d": [146], "a": 1 }, + { "px": [24,24], "src": [224,128], "f": 0, "t": 540, "d": [147], "a": 1 }, + { "px": [32,24], "src": [224,128], "f": 0, "t": 540, "d": [148], "a": 1 }, + { "px": [40,24], "src": [224,128], "f": 0, "t": 540, "d": [149], "a": 1 }, + { "px": [48,24], "src": [224,128], "f": 0, "t": 540, "d": [150], "a": 1 }, + { "px": [56,24], "src": [224,128], "f": 0, "t": 540, "d": [151], "a": 1 }, + { "px": [64,24], "src": [224,128], "f": 0, "t": 540, "d": [152], "a": 1 }, + { "px": [72,24], "src": [224,128], "f": 0, "t": 540, "d": [153], "a": 1 }, + { "px": [80,24], "src": [224,128], "f": 0, "t": 540, "d": [154], "a": 1 }, + { "px": [88,24], "src": [224,128], "f": 0, "t": 540, "d": [155], "a": 1 }, + { "px": [96,24], "src": [224,128], "f": 0, "t": 540, "d": [156], "a": 1 }, + { "px": [104,24], "src": [224,128], "f": 0, "t": 540, "d": [157], "a": 1 }, + { "px": [112,24], "src": [224,128], "f": 0, "t": 540, "d": [158], "a": 1 }, + { "px": [120,24], "src": [224,128], "f": 0, "t": 540, "d": [159], "a": 1 }, + { "px": [128,24], "src": [224,128], "f": 0, "t": 540, "d": [160], "a": 1 }, + { "px": [136,24], "src": [224,128], "f": 0, "t": 540, "d": [161], "a": 1 }, + { "px": [144,24], "src": [224,128], "f": 0, "t": 540, "d": [162], "a": 1 }, + { "px": [152,24], "src": [224,128], "f": 0, "t": 540, "d": [163], "a": 1 }, + { "px": [160,24], "src": [224,128], "f": 0, "t": 540, "d": [164], "a": 1 }, + { "px": [168,24], "src": [224,128], "f": 0, "t": 540, "d": [165], "a": 1 }, + { "px": [176,24], "src": [224,128], "f": 0, "t": 540, "d": [166], "a": 1 }, + { "px": [184,24], "src": [224,128], "f": 0, "t": 540, "d": [167], "a": 1 }, + { "px": [192,24], "src": [224,128], "f": 0, "t": 540, "d": [168], "a": 1 }, + { "px": [200,24], "src": [224,128], "f": 0, "t": 540, "d": [169], "a": 1 }, + { "px": [208,24], "src": [224,128], "f": 0, "t": 540, "d": [170], "a": 1 }, + { "px": [216,24], "src": [224,128], "f": 0, "t": 540, "d": [171], "a": 1 }, + { "px": [224,24], "src": [224,128], "f": 0, "t": 540, "d": [172], "a": 1 }, + { "px": [232,24], "src": [224,128], "f": 0, "t": 540, "d": [173], "a": 1 }, + { "px": [240,24], "src": [224,128], "f": 0, "t": 540, "d": [174], "a": 1 }, + { "px": [248,24], "src": [224,128], "f": 0, "t": 540, "d": [175], "a": 1 }, + { "px": [256,24], "src": [224,128], "f": 0, "t": 540, "d": [176], "a": 1 }, + { "px": [264,24], "src": [224,128], "f": 0, "t": 540, "d": [177], "a": 1 }, + { "px": [272,24], "src": [224,128], "f": 0, "t": 540, "d": [178], "a": 1 }, + { "px": [280,24], "src": [224,128], "f": 0, "t": 540, "d": [179], "a": 1 }, + { "px": [288,24], "src": [224,128], "f": 0, "t": 540, "d": [180], "a": 1 }, + { "px": [296,24], "src": [224,128], "f": 0, "t": 540, "d": [181], "a": 1 }, + { "px": [304,24], "src": [224,128], "f": 0, "t": 540, "d": [182], "a": 1 }, + { "px": [312,24], "src": [224,128], "f": 0, "t": 540, "d": [183], "a": 1 }, + { "px": [320,24], "src": [224,128], "f": 0, "t": 540, "d": [184], "a": 1 }, + { "px": [328,24], "src": [224,128], "f": 0, "t": 540, "d": [185], "a": 1 }, + { "px": [336,24], "src": [224,128], "f": 0, "t": 540, "d": [186], "a": 1 }, + { "px": [344,24], "src": [224,128], "f": 0, "t": 540, "d": [187], "a": 1 }, + { "px": [352,24], "src": [224,128], "f": 0, "t": 540, "d": [188], "a": 1 }, + { "px": [360,24], "src": [224,128], "f": 0, "t": 540, "d": [189], "a": 1 }, + { "px": [368,24], "src": [224,128], "f": 0, "t": 540, "d": [190], "a": 1 }, + { "px": [376,24], "src": [232,128], "f": 0, "t": 541, "d": [191], "a": 1 }, + { "px": [0,32], "src": [216,128], "f": 0, "t": 539, "d": [192], "a": 1 }, + { "px": [8,32], "src": [224,128], "f": 0, "t": 540, "d": [193], "a": 1 }, + { "px": [16,32], "src": [224,128], "f": 0, "t": 540, "d": [194], "a": 1 }, + { "px": [24,32], "src": [224,128], "f": 0, "t": 540, "d": [195], "a": 1 }, + { "px": [32,32], "src": [224,128], "f": 0, "t": 540, "d": [196], "a": 1 }, + { "px": [40,32], "src": [224,128], "f": 0, "t": 540, "d": [197], "a": 1 }, + { "px": [48,32], "src": [224,128], "f": 0, "t": 540, "d": [198], "a": 1 }, + { "px": [56,32], "src": [224,128], "f": 0, "t": 540, "d": [199], "a": 1 }, + { "px": [64,32], "src": [224,128], "f": 0, "t": 540, "d": [200], "a": 1 }, + { "px": [72,32], "src": [224,128], "f": 0, "t": 540, "d": [201], "a": 1 }, + { "px": [80,32], "src": [224,128], "f": 0, "t": 540, "d": [202], "a": 1 }, + { "px": [88,32], "src": [224,128], "f": 0, "t": 540, "d": [203], "a": 1 }, + { "px": [96,32], "src": [224,128], "f": 0, "t": 540, "d": [204], "a": 1 }, + { "px": [104,32], "src": [224,128], "f": 0, "t": 540, "d": [205], "a": 1 }, + { "px": [112,32], "src": [224,128], "f": 0, "t": 540, "d": [206], "a": 1 }, + { "px": [120,32], "src": [224,128], "f": 0, "t": 540, "d": [207], "a": 1 }, + { "px": [128,32], "src": [224,128], "f": 0, "t": 540, "d": [208], "a": 1 }, + { "px": [136,32], "src": [224,128], "f": 0, "t": 540, "d": [209], "a": 1 }, + { "px": [144,32], "src": [224,128], "f": 0, "t": 540, "d": [210], "a": 1 }, + { "px": [152,32], "src": [224,128], "f": 0, "t": 540, "d": [211], "a": 1 }, + { "px": [160,32], "src": [224,128], "f": 0, "t": 540, "d": [212], "a": 1 }, + { "px": [168,32], "src": [224,128], "f": 0, "t": 540, "d": [213], "a": 1 }, + { "px": [176,32], "src": [224,128], "f": 0, "t": 540, "d": [214], "a": 1 }, + { "px": [184,32], "src": [224,128], "f": 0, "t": 540, "d": [215], "a": 1 }, + { "px": [192,32], "src": [224,128], "f": 0, "t": 540, "d": [216], "a": 1 }, + { "px": [200,32], "src": [224,128], "f": 0, "t": 540, "d": [217], "a": 1 }, + { "px": [208,32], "src": [224,128], "f": 0, "t": 540, "d": [218], "a": 1 }, + { "px": [216,32], "src": [224,128], "f": 0, "t": 540, "d": [219], "a": 1 }, + { "px": [224,32], "src": [224,128], "f": 0, "t": 540, "d": [220], "a": 1 }, + { "px": [232,32], "src": [224,128], "f": 0, "t": 540, "d": [221], "a": 1 }, + { "px": [240,32], "src": [224,128], "f": 0, "t": 540, "d": [222], "a": 1 }, + { "px": [248,32], "src": [224,128], "f": 0, "t": 540, "d": [223], "a": 1 }, + { "px": [256,32], "src": [224,128], "f": 0, "t": 540, "d": [224], "a": 1 }, + { "px": [264,32], "src": [224,128], "f": 0, "t": 540, "d": [225], "a": 1 }, + { "px": [272,32], "src": [224,128], "f": 0, "t": 540, "d": [226], "a": 1 }, + { "px": [280,32], "src": [224,128], "f": 0, "t": 540, "d": [227], "a": 1 }, + { "px": [288,32], "src": [224,128], "f": 0, "t": 540, "d": [228], "a": 1 }, + { "px": [296,32], "src": [224,128], "f": 0, "t": 540, "d": [229], "a": 1 }, + { "px": [304,32], "src": [224,128], "f": 0, "t": 540, "d": [230], "a": 1 }, + { "px": [312,32], "src": [224,128], "f": 0, "t": 540, "d": [231], "a": 1 }, + { "px": [320,32], "src": [224,128], "f": 0, "t": 540, "d": [232], "a": 1 }, + { "px": [328,32], "src": [224,128], "f": 0, "t": 540, "d": [233], "a": 1 }, + { "px": [336,32], "src": [224,128], "f": 0, "t": 540, "d": [234], "a": 1 }, + { "px": [344,32], "src": [224,128], "f": 0, "t": 540, "d": [235], "a": 1 }, + { "px": [352,32], "src": [224,128], "f": 0, "t": 540, "d": [236], "a": 1 }, + { "px": [360,32], "src": [224,128], "f": 0, "t": 540, "d": [237], "a": 1 }, + { "px": [368,32], "src": [224,128], "f": 0, "t": 540, "d": [238], "a": 1 }, + { "px": [376,32], "src": [232,128], "f": 0, "t": 541, "d": [239], "a": 1 }, + { "px": [0,40], "src": [216,128], "f": 0, "t": 539, "d": [240], "a": 1 }, + { "px": [8,40], "src": [224,128], "f": 0, "t": 540, "d": [241], "a": 1 }, + { "px": [16,40], "src": [224,128], "f": 0, "t": 540, "d": [242], "a": 1 }, + { "px": [24,40], "src": [224,128], "f": 0, "t": 540, "d": [243], "a": 1 }, + { "px": [32,40], "src": [224,128], "f": 0, "t": 540, "d": [244], "a": 1 }, + { "px": [40,40], "src": [224,128], "f": 0, "t": 540, "d": [245], "a": 1 }, + { "px": [48,40], "src": [224,128], "f": 0, "t": 540, "d": [246], "a": 1 }, + { "px": [56,40], "src": [224,128], "f": 0, "t": 540, "d": [247], "a": 1 }, + { "px": [64,40], "src": [224,128], "f": 0, "t": 540, "d": [248], "a": 1 }, + { "px": [72,40], "src": [224,128], "f": 0, "t": 540, "d": [249], "a": 1 }, + { "px": [80,40], "src": [224,128], "f": 0, "t": 540, "d": [250], "a": 1 }, + { "px": [88,40], "src": [224,128], "f": 0, "t": 540, "d": [251], "a": 1 }, + { "px": [96,40], "src": [224,128], "f": 0, "t": 540, "d": [252], "a": 1 }, + { "px": [104,40], "src": [224,128], "f": 0, "t": 540, "d": [253], "a": 1 }, + { "px": [112,40], "src": [224,128], "f": 0, "t": 540, "d": [254], "a": 1 }, + { "px": [120,40], "src": [224,128], "f": 0, "t": 540, "d": [255], "a": 1 }, + { "px": [128,40], "src": [224,128], "f": 0, "t": 540, "d": [256], "a": 1 }, + { "px": [136,40], "src": [224,128], "f": 0, "t": 540, "d": [257], "a": 1 }, + { "px": [144,40], "src": [224,128], "f": 0, "t": 540, "d": [258], "a": 1 }, + { "px": [152,40], "src": [224,128], "f": 0, "t": 540, "d": [259], "a": 1 }, + { "px": [160,40], "src": [224,128], "f": 0, "t": 540, "d": [260], "a": 1 }, + { "px": [168,40], "src": [224,128], "f": 0, "t": 540, "d": [261], "a": 1 }, + { "px": [176,40], "src": [224,128], "f": 0, "t": 540, "d": [262], "a": 1 }, + { "px": [184,40], "src": [224,128], "f": 0, "t": 540, "d": [263], "a": 1 }, + { "px": [192,40], "src": [224,128], "f": 0, "t": 540, "d": [264], "a": 1 }, + { "px": [200,40], "src": [224,128], "f": 0, "t": 540, "d": [265], "a": 1 }, + { "px": [208,40], "src": [224,128], "f": 0, "t": 540, "d": [266], "a": 1 }, + { "px": [216,40], "src": [224,128], "f": 0, "t": 540, "d": [267], "a": 1 }, + { "px": [224,40], "src": [224,128], "f": 0, "t": 540, "d": [268], "a": 1 }, + { "px": [232,40], "src": [224,128], "f": 0, "t": 540, "d": [269], "a": 1 }, + { "px": [240,40], "src": [224,128], "f": 0, "t": 540, "d": [270], "a": 1 }, + { "px": [248,40], "src": [224,128], "f": 0, "t": 540, "d": [271], "a": 1 }, + { "px": [256,40], "src": [224,128], "f": 0, "t": 540, "d": [272], "a": 1 }, + { "px": [264,40], "src": [224,128], "f": 0, "t": 540, "d": [273], "a": 1 }, + { "px": [272,40], "src": [224,128], "f": 0, "t": 540, "d": [274], "a": 1 }, + { "px": [280,40], "src": [224,128], "f": 0, "t": 540, "d": [275], "a": 1 }, + { "px": [288,40], "src": [224,128], "f": 0, "t": 540, "d": [276], "a": 1 }, + { "px": [296,40], "src": [224,128], "f": 0, "t": 540, "d": [277], "a": 1 }, + { "px": [304,40], "src": [224,128], "f": 0, "t": 540, "d": [278], "a": 1 }, + { "px": [312,40], "src": [224,128], "f": 0, "t": 540, "d": [279], "a": 1 }, + { "px": [320,40], "src": [224,128], "f": 0, "t": 540, "d": [280], "a": 1 }, + { "px": [328,40], "src": [224,128], "f": 0, "t": 540, "d": [281], "a": 1 }, + { "px": [336,40], "src": [224,128], "f": 0, "t": 540, "d": [282], "a": 1 }, + { "px": [344,40], "src": [224,128], "f": 0, "t": 540, "d": [283], "a": 1 }, + { "px": [352,40], "src": [224,128], "f": 0, "t": 540, "d": [284], "a": 1 }, + { "px": [360,40], "src": [224,128], "f": 0, "t": 540, "d": [285], "a": 1 }, + { "px": [368,40], "src": [224,128], "f": 0, "t": 540, "d": [286], "a": 1 }, + { "px": [376,40], "src": [232,128], "f": 0, "t": 541, "d": [287], "a": 1 }, + { "px": [0,48], "src": [216,128], "f": 0, "t": 539, "d": [288], "a": 1 }, + { "px": [8,48], "src": [224,128], "f": 0, "t": 540, "d": [289], "a": 1 }, + { "px": [16,48], "src": [224,128], "f": 0, "t": 540, "d": [290], "a": 1 }, + { "px": [24,48], "src": [224,128], "f": 0, "t": 540, "d": [291], "a": 1 }, + { "px": [32,48], "src": [224,128], "f": 0, "t": 540, "d": [292], "a": 1 }, + { "px": [40,48], "src": [224,128], "f": 0, "t": 540, "d": [293], "a": 1 }, + { "px": [48,48], "src": [224,128], "f": 0, "t": 540, "d": [294], "a": 1 }, + { "px": [56,48], "src": [224,128], "f": 0, "t": 540, "d": [295], "a": 1 }, + { "px": [64,48], "src": [224,128], "f": 0, "t": 540, "d": [296], "a": 1 }, + { "px": [72,48], "src": [224,128], "f": 0, "t": 540, "d": [297], "a": 1 }, + { "px": [80,48], "src": [224,128], "f": 0, "t": 540, "d": [298], "a": 1 }, + { "px": [88,48], "src": [224,128], "f": 0, "t": 540, "d": [299], "a": 1 }, + { "px": [96,48], "src": [224,128], "f": 0, "t": 540, "d": [300], "a": 1 }, + { "px": [104,48], "src": [224,128], "f": 0, "t": 540, "d": [301], "a": 1 }, + { "px": [112,48], "src": [224,128], "f": 0, "t": 540, "d": [302], "a": 1 }, + { "px": [120,48], "src": [224,128], "f": 0, "t": 540, "d": [303], "a": 1 }, + { "px": [128,48], "src": [224,128], "f": 0, "t": 540, "d": [304], "a": 1 }, + { "px": [136,48], "src": [224,128], "f": 0, "t": 540, "d": [305], "a": 1 }, + { "px": [144,48], "src": [224,128], "f": 0, "t": 540, "d": [306], "a": 1 }, + { "px": [152,48], "src": [224,128], "f": 0, "t": 540, "d": [307], "a": 1 }, + { "px": [160,48], "src": [224,128], "f": 0, "t": 540, "d": [308], "a": 1 }, + { "px": [168,48], "src": [224,128], "f": 0, "t": 540, "d": [309], "a": 1 }, + { "px": [176,48], "src": [224,128], "f": 0, "t": 540, "d": [310], "a": 1 }, + { "px": [184,48], "src": [224,128], "f": 0, "t": 540, "d": [311], "a": 1 }, + { "px": [192,48], "src": [224,128], "f": 0, "t": 540, "d": [312], "a": 1 }, + { "px": [200,48], "src": [224,128], "f": 0, "t": 540, "d": [313], "a": 1 }, + { "px": [208,48], "src": [224,128], "f": 0, "t": 540, "d": [314], "a": 1 }, + { "px": [216,48], "src": [224,128], "f": 0, "t": 540, "d": [315], "a": 1 }, + { "px": [224,48], "src": [224,128], "f": 0, "t": 540, "d": [316], "a": 1 }, + { "px": [232,48], "src": [224,128], "f": 0, "t": 540, "d": [317], "a": 1 }, + { "px": [240,48], "src": [224,128], "f": 0, "t": 540, "d": [318], "a": 1 }, + { "px": [248,48], "src": [224,128], "f": 0, "t": 540, "d": [319], "a": 1 }, + { "px": [256,48], "src": [224,128], "f": 0, "t": 540, "d": [320], "a": 1 }, + { "px": [264,48], "src": [224,128], "f": 0, "t": 540, "d": [321], "a": 1 }, + { "px": [272,48], "src": [224,128], "f": 0, "t": 540, "d": [322], "a": 1 }, + { "px": [280,48], "src": [224,128], "f": 0, "t": 540, "d": [323], "a": 1 }, + { "px": [288,48], "src": [224,128], "f": 0, "t": 540, "d": [324], "a": 1 }, + { "px": [296,48], "src": [224,128], "f": 0, "t": 540, "d": [325], "a": 1 }, + { "px": [304,48], "src": [224,128], "f": 0, "t": 540, "d": [326], "a": 1 }, + { "px": [312,48], "src": [224,128], "f": 0, "t": 540, "d": [327], "a": 1 }, + { "px": [320,48], "src": [224,128], "f": 0, "t": 540, "d": [328], "a": 1 }, + { "px": [328,48], "src": [224,128], "f": 0, "t": 540, "d": [329], "a": 1 }, + { "px": [336,48], "src": [224,128], "f": 0, "t": 540, "d": [330], "a": 1 }, + { "px": [344,48], "src": [224,128], "f": 0, "t": 540, "d": [331], "a": 1 }, + { "px": [352,48], "src": [224,128], "f": 0, "t": 540, "d": [332], "a": 1 }, + { "px": [360,48], "src": [224,128], "f": 0, "t": 540, "d": [333], "a": 1 }, + { "px": [368,48], "src": [224,128], "f": 0, "t": 540, "d": [334], "a": 1 }, + { "px": [376,48], "src": [232,128], "f": 0, "t": 541, "d": [335], "a": 1 }, + { "px": [0,56], "src": [216,128], "f": 0, "t": 539, "d": [336], "a": 1 }, + { "px": [8,56], "src": [224,128], "f": 0, "t": 540, "d": [337], "a": 1 }, + { "px": [16,56], "src": [224,128], "f": 0, "t": 540, "d": [338], "a": 1 }, + { "px": [24,56], "src": [224,128], "f": 0, "t": 540, "d": [339], "a": 1 }, + { "px": [32,56], "src": [224,128], "f": 0, "t": 540, "d": [340], "a": 1 }, + { "px": [40,56], "src": [224,128], "f": 0, "t": 540, "d": [341], "a": 1 }, + { "px": [48,56], "src": [224,128], "f": 0, "t": 540, "d": [342], "a": 1 }, + { "px": [56,56], "src": [224,128], "f": 0, "t": 540, "d": [343], "a": 1 }, + { "px": [64,56], "src": [224,128], "f": 0, "t": 540, "d": [344], "a": 1 }, + { "px": [72,56], "src": [224,128], "f": 0, "t": 540, "d": [345], "a": 1 }, + { "px": [80,56], "src": [224,128], "f": 0, "t": 540, "d": [346], "a": 1 }, + { "px": [88,56], "src": [224,128], "f": 0, "t": 540, "d": [347], "a": 1 }, + { "px": [96,56], "src": [224,128], "f": 0, "t": 540, "d": [348], "a": 1 }, + { "px": [104,56], "src": [224,128], "f": 0, "t": 540, "d": [349], "a": 1 }, + { "px": [112,56], "src": [224,128], "f": 0, "t": 540, "d": [350], "a": 1 }, + { "px": [120,56], "src": [224,128], "f": 0, "t": 540, "d": [351], "a": 1 }, + { "px": [128,56], "src": [224,128], "f": 0, "t": 540, "d": [352], "a": 1 }, + { "px": [136,56], "src": [224,128], "f": 0, "t": 540, "d": [353], "a": 1 }, + { "px": [144,56], "src": [224,128], "f": 0, "t": 540, "d": [354], "a": 1 }, + { "px": [152,56], "src": [224,128], "f": 0, "t": 540, "d": [355], "a": 1 }, + { "px": [160,56], "src": [224,128], "f": 0, "t": 540, "d": [356], "a": 1 }, + { "px": [168,56], "src": [224,128], "f": 0, "t": 540, "d": [357], "a": 1 }, + { "px": [176,56], "src": [224,128], "f": 0, "t": 540, "d": [358], "a": 1 }, + { "px": [184,56], "src": [224,128], "f": 0, "t": 540, "d": [359], "a": 1 }, + { "px": [192,56], "src": [224,128], "f": 0, "t": 540, "d": [360], "a": 1 }, + { "px": [200,56], "src": [224,128], "f": 0, "t": 540, "d": [361], "a": 1 }, + { "px": [208,56], "src": [224,128], "f": 0, "t": 540, "d": [362], "a": 1 }, + { "px": [216,56], "src": [224,128], "f": 0, "t": 540, "d": [363], "a": 1 }, + { "px": [224,56], "src": [224,128], "f": 0, "t": 540, "d": [364], "a": 1 }, + { "px": [232,56], "src": [224,128], "f": 0, "t": 540, "d": [365], "a": 1 }, + { "px": [240,56], "src": [224,128], "f": 0, "t": 540, "d": [366], "a": 1 }, + { "px": [248,56], "src": [224,128], "f": 0, "t": 540, "d": [367], "a": 1 }, + { "px": [256,56], "src": [224,128], "f": 0, "t": 540, "d": [368], "a": 1 }, + { "px": [264,56], "src": [224,128], "f": 0, "t": 540, "d": [369], "a": 1 }, + { "px": [272,56], "src": [224,128], "f": 0, "t": 540, "d": [370], "a": 1 }, + { "px": [280,56], "src": [224,128], "f": 0, "t": 540, "d": [371], "a": 1 }, + { "px": [288,56], "src": [224,128], "f": 0, "t": 540, "d": [372], "a": 1 }, + { "px": [296,56], "src": [224,128], "f": 0, "t": 540, "d": [373], "a": 1 }, + { "px": [304,56], "src": [224,128], "f": 0, "t": 540, "d": [374], "a": 1 }, + { "px": [312,56], "src": [224,128], "f": 0, "t": 540, "d": [375], "a": 1 }, + { "px": [320,56], "src": [224,128], "f": 0, "t": 540, "d": [376], "a": 1 }, + { "px": [328,56], "src": [224,128], "f": 0, "t": 540, "d": [377], "a": 1 }, + { "px": [336,56], "src": [224,128], "f": 0, "t": 540, "d": [378], "a": 1 }, + { "px": [344,56], "src": [224,128], "f": 0, "t": 540, "d": [379], "a": 1 }, + { "px": [352,56], "src": [224,128], "f": 0, "t": 540, "d": [380], "a": 1 }, + { "px": [360,56], "src": [224,128], "f": 0, "t": 540, "d": [381], "a": 1 }, + { "px": [368,56], "src": [224,128], "f": 0, "t": 540, "d": [382], "a": 1 }, + { "px": [376,56], "src": [232,128], "f": 0, "t": 541, "d": [383], "a": 1 }, + { "px": [0,64], "src": [216,128], "f": 0, "t": 539, "d": [384], "a": 1 }, + { "px": [8,64], "src": [224,128], "f": 0, "t": 540, "d": [385], "a": 1 }, + { "px": [16,64], "src": [224,128], "f": 0, "t": 540, "d": [386], "a": 1 }, + { "px": [24,64], "src": [224,128], "f": 0, "t": 540, "d": [387], "a": 1 }, + { "px": [32,64], "src": [224,128], "f": 0, "t": 540, "d": [388], "a": 1 }, + { "px": [40,64], "src": [224,128], "f": 0, "t": 540, "d": [389], "a": 1 }, + { "px": [48,64], "src": [224,128], "f": 0, "t": 540, "d": [390], "a": 1 }, + { "px": [56,64], "src": [224,128], "f": 0, "t": 540, "d": [391], "a": 1 }, + { "px": [64,64], "src": [224,128], "f": 0, "t": 540, "d": [392], "a": 1 }, + { "px": [72,64], "src": [224,128], "f": 0, "t": 540, "d": [393], "a": 1 }, + { "px": [80,64], "src": [224,128], "f": 0, "t": 540, "d": [394], "a": 1 }, + { "px": [88,64], "src": [224,128], "f": 0, "t": 540, "d": [395], "a": 1 }, + { "px": [96,64], "src": [224,128], "f": 0, "t": 540, "d": [396], "a": 1 }, + { "px": [104,64], "src": [224,128], "f": 0, "t": 540, "d": [397], "a": 1 }, + { "px": [112,64], "src": [224,128], "f": 0, "t": 540, "d": [398], "a": 1 }, + { "px": [120,64], "src": [224,128], "f": 0, "t": 540, "d": [399], "a": 1 }, + { "px": [128,64], "src": [224,128], "f": 0, "t": 540, "d": [400], "a": 1 }, + { "px": [136,64], "src": [224,128], "f": 0, "t": 540, "d": [401], "a": 1 }, + { "px": [144,64], "src": [224,128], "f": 0, "t": 540, "d": [402], "a": 1 }, + { "px": [152,64], "src": [224,128], "f": 0, "t": 540, "d": [403], "a": 1 }, + { "px": [160,64], "src": [224,128], "f": 0, "t": 540, "d": [404], "a": 1 }, + { "px": [168,64], "src": [224,128], "f": 0, "t": 540, "d": [405], "a": 1 }, + { "px": [176,64], "src": [224,128], "f": 0, "t": 540, "d": [406], "a": 1 }, + { "px": [184,64], "src": [224,128], "f": 0, "t": 540, "d": [407], "a": 1 }, + { "px": [192,64], "src": [224,128], "f": 0, "t": 540, "d": [408], "a": 1 }, + { "px": [200,64], "src": [224,128], "f": 0, "t": 540, "d": [409], "a": 1 }, + { "px": [208,64], "src": [224,128], "f": 0, "t": 540, "d": [410], "a": 1 }, + { "px": [216,64], "src": [224,128], "f": 0, "t": 540, "d": [411], "a": 1 }, + { "px": [224,64], "src": [224,128], "f": 0, "t": 540, "d": [412], "a": 1 }, + { "px": [232,64], "src": [224,128], "f": 0, "t": 540, "d": [413], "a": 1 }, + { "px": [240,64], "src": [224,128], "f": 0, "t": 540, "d": [414], "a": 1 }, + { "px": [248,64], "src": [224,128], "f": 0, "t": 540, "d": [415], "a": 1 }, + { "px": [256,64], "src": [224,128], "f": 0, "t": 540, "d": [416], "a": 1 }, + { "px": [264,64], "src": [224,128], "f": 0, "t": 540, "d": [417], "a": 1 }, + { "px": [272,64], "src": [224,128], "f": 0, "t": 540, "d": [418], "a": 1 }, + { "px": [280,64], "src": [224,128], "f": 0, "t": 540, "d": [419], "a": 1 }, + { "px": [288,64], "src": [224,128], "f": 0, "t": 540, "d": [420], "a": 1 }, + { "px": [296,64], "src": [224,128], "f": 0, "t": 540, "d": [421], "a": 1 }, + { "px": [304,64], "src": [224,128], "f": 0, "t": 540, "d": [422], "a": 1 }, + { "px": [312,64], "src": [224,128], "f": 0, "t": 540, "d": [423], "a": 1 }, + { "px": [320,64], "src": [224,128], "f": 0, "t": 540, "d": [424], "a": 1 }, + { "px": [328,64], "src": [224,128], "f": 0, "t": 540, "d": [425], "a": 1 }, + { "px": [336,64], "src": [224,128], "f": 0, "t": 540, "d": [426], "a": 1 }, + { "px": [344,64], "src": [224,128], "f": 0, "t": 540, "d": [427], "a": 1 }, + { "px": [352,64], "src": [224,128], "f": 0, "t": 540, "d": [428], "a": 1 }, + { "px": [360,64], "src": [224,128], "f": 0, "t": 540, "d": [429], "a": 1 }, + { "px": [368,64], "src": [224,128], "f": 0, "t": 540, "d": [430], "a": 1 }, + { "px": [376,64], "src": [232,128], "f": 0, "t": 541, "d": [431], "a": 1 }, + { "px": [0,72], "src": [216,128], "f": 0, "t": 539, "d": [432], "a": 1 }, + { "px": [8,72], "src": [224,128], "f": 0, "t": 540, "d": [433], "a": 1 }, + { "px": [16,72], "src": [224,128], "f": 0, "t": 540, "d": [434], "a": 1 }, + { "px": [24,72], "src": [224,128], "f": 0, "t": 540, "d": [435], "a": 1 }, + { "px": [32,72], "src": [224,128], "f": 0, "t": 540, "d": [436], "a": 1 }, + { "px": [40,72], "src": [224,128], "f": 0, "t": 540, "d": [437], "a": 1 }, + { "px": [48,72], "src": [224,128], "f": 0, "t": 540, "d": [438], "a": 1 }, + { "px": [56,72], "src": [224,128], "f": 0, "t": 540, "d": [439], "a": 1 }, + { "px": [64,72], "src": [224,128], "f": 0, "t": 540, "d": [440], "a": 1 }, + { "px": [72,72], "src": [224,128], "f": 0, "t": 540, "d": [441], "a": 1 }, + { "px": [80,72], "src": [224,128], "f": 0, "t": 540, "d": [442], "a": 1 }, + { "px": [88,72], "src": [224,128], "f": 0, "t": 540, "d": [443], "a": 1 }, + { "px": [96,72], "src": [224,128], "f": 0, "t": 540, "d": [444], "a": 1 }, + { "px": [104,72], "src": [224,128], "f": 0, "t": 540, "d": [445], "a": 1 }, + { "px": [112,72], "src": [224,128], "f": 0, "t": 540, "d": [446], "a": 1 }, + { "px": [120,72], "src": [224,128], "f": 0, "t": 540, "d": [447], "a": 1 }, + { "px": [128,72], "src": [224,128], "f": 0, "t": 540, "d": [448], "a": 1 }, + { "px": [136,72], "src": [224,128], "f": 0, "t": 540, "d": [449], "a": 1 }, + { "px": [144,72], "src": [224,128], "f": 0, "t": 540, "d": [450], "a": 1 }, + { "px": [152,72], "src": [224,128], "f": 0, "t": 540, "d": [451], "a": 1 }, + { "px": [160,72], "src": [224,128], "f": 0, "t": 540, "d": [452], "a": 1 }, + { "px": [168,72], "src": [224,128], "f": 0, "t": 540, "d": [453], "a": 1 }, + { "px": [176,72], "src": [224,128], "f": 0, "t": 540, "d": [454], "a": 1 }, + { "px": [184,72], "src": [224,128], "f": 0, "t": 540, "d": [455], "a": 1 }, + { "px": [192,72], "src": [224,128], "f": 0, "t": 540, "d": [456], "a": 1 }, + { "px": [200,72], "src": [224,128], "f": 0, "t": 540, "d": [457], "a": 1 }, + { "px": [208,72], "src": [224,128], "f": 0, "t": 540, "d": [458], "a": 1 }, + { "px": [216,72], "src": [224,128], "f": 0, "t": 540, "d": [459], "a": 1 }, + { "px": [224,72], "src": [224,128], "f": 0, "t": 540, "d": [460], "a": 1 }, + { "px": [232,72], "src": [224,128], "f": 0, "t": 540, "d": [461], "a": 1 }, + { "px": [240,72], "src": [224,128], "f": 0, "t": 540, "d": [462], "a": 1 }, + { "px": [248,72], "src": [224,128], "f": 0, "t": 540, "d": [463], "a": 1 }, + { "px": [256,72], "src": [224,128], "f": 0, "t": 540, "d": [464], "a": 1 }, + { "px": [264,72], "src": [224,128], "f": 0, "t": 540, "d": [465], "a": 1 }, + { "px": [272,72], "src": [224,128], "f": 0, "t": 540, "d": [466], "a": 1 }, + { "px": [280,72], "src": [224,128], "f": 0, "t": 540, "d": [467], "a": 1 }, + { "px": [288,72], "src": [224,128], "f": 0, "t": 540, "d": [468], "a": 1 }, + { "px": [296,72], "src": [224,128], "f": 0, "t": 540, "d": [469], "a": 1 }, + { "px": [304,72], "src": [224,128], "f": 0, "t": 540, "d": [470], "a": 1 }, + { "px": [312,72], "src": [224,128], "f": 0, "t": 540, "d": [471], "a": 1 }, + { "px": [320,72], "src": [224,128], "f": 0, "t": 540, "d": [472], "a": 1 }, + { "px": [328,72], "src": [224,128], "f": 0, "t": 540, "d": [473], "a": 1 }, + { "px": [336,72], "src": [224,128], "f": 0, "t": 540, "d": [474], "a": 1 }, + { "px": [344,72], "src": [224,128], "f": 0, "t": 540, "d": [475], "a": 1 }, + { "px": [352,72], "src": [224,128], "f": 0, "t": 540, "d": [476], "a": 1 }, + { "px": [360,72], "src": [224,128], "f": 0, "t": 540, "d": [477], "a": 1 }, + { "px": [368,72], "src": [224,128], "f": 0, "t": 540, "d": [478], "a": 1 }, + { "px": [376,72], "src": [232,128], "f": 0, "t": 541, "d": [479], "a": 1 }, + { "px": [0,80], "src": [216,128], "f": 0, "t": 539, "d": [480], "a": 1 }, + { "px": [8,80], "src": [224,128], "f": 0, "t": 540, "d": [481], "a": 1 }, + { "px": [16,80], "src": [224,128], "f": 0, "t": 540, "d": [482], "a": 1 }, + { "px": [24,80], "src": [224,128], "f": 0, "t": 540, "d": [483], "a": 1 }, + { "px": [32,80], "src": [224,128], "f": 0, "t": 540, "d": [484], "a": 1 }, + { "px": [40,80], "src": [224,128], "f": 0, "t": 540, "d": [485], "a": 1 }, + { "px": [48,80], "src": [224,128], "f": 0, "t": 540, "d": [486], "a": 1 }, + { "px": [56,80], "src": [224,128], "f": 0, "t": 540, "d": [487], "a": 1 }, + { "px": [64,80], "src": [224,128], "f": 0, "t": 540, "d": [488], "a": 1 }, + { "px": [72,80], "src": [224,128], "f": 0, "t": 540, "d": [489], "a": 1 }, + { "px": [80,80], "src": [224,128], "f": 0, "t": 540, "d": [490], "a": 1 }, + { "px": [88,80], "src": [224,128], "f": 0, "t": 540, "d": [491], "a": 1 }, + { "px": [96,80], "src": [224,128], "f": 0, "t": 540, "d": [492], "a": 1 }, + { "px": [104,80], "src": [224,128], "f": 0, "t": 540, "d": [493], "a": 1 }, + { "px": [112,80], "src": [224,128], "f": 0, "t": 540, "d": [494], "a": 1 }, + { "px": [120,80], "src": [224,128], "f": 0, "t": 540, "d": [495], "a": 1 }, + { "px": [128,80], "src": [224,128], "f": 0, "t": 540, "d": [496], "a": 1 }, + { "px": [136,80], "src": [224,128], "f": 0, "t": 540, "d": [497], "a": 1 }, + { "px": [144,80], "src": [224,128], "f": 0, "t": 540, "d": [498], "a": 1 }, + { "px": [152,80], "src": [224,128], "f": 0, "t": 540, "d": [499], "a": 1 }, + { "px": [160,80], "src": [224,128], "f": 0, "t": 540, "d": [500], "a": 1 }, + { "px": [168,80], "src": [224,128], "f": 0, "t": 540, "d": [501], "a": 1 }, + { "px": [176,80], "src": [224,128], "f": 0, "t": 540, "d": [502], "a": 1 }, + { "px": [184,80], "src": [224,128], "f": 0, "t": 540, "d": [503], "a": 1 }, + { "px": [192,80], "src": [224,128], "f": 0, "t": 540, "d": [504], "a": 1 }, + { "px": [200,80], "src": [224,128], "f": 0, "t": 540, "d": [505], "a": 1 }, + { "px": [208,80], "src": [224,128], "f": 0, "t": 540, "d": [506], "a": 1 }, + { "px": [216,80], "src": [224,128], "f": 0, "t": 540, "d": [507], "a": 1 }, + { "px": [224,80], "src": [224,128], "f": 0, "t": 540, "d": [508], "a": 1 }, + { "px": [232,80], "src": [224,128], "f": 0, "t": 540, "d": [509], "a": 1 }, + { "px": [240,80], "src": [224,128], "f": 0, "t": 540, "d": [510], "a": 1 }, + { "px": [248,80], "src": [224,128], "f": 0, "t": 540, "d": [511], "a": 1 }, + { "px": [256,80], "src": [224,128], "f": 0, "t": 540, "d": [512], "a": 1 }, + { "px": [264,80], "src": [224,128], "f": 0, "t": 540, "d": [513], "a": 1 }, + { "px": [272,80], "src": [224,128], "f": 0, "t": 540, "d": [514], "a": 1 }, + { "px": [280,80], "src": [224,128], "f": 0, "t": 540, "d": [515], "a": 1 }, + { "px": [288,80], "src": [224,128], "f": 0, "t": 540, "d": [516], "a": 1 }, + { "px": [296,80], "src": [224,128], "f": 0, "t": 540, "d": [517], "a": 1 }, + { "px": [304,80], "src": [224,128], "f": 0, "t": 540, "d": [518], "a": 1 }, + { "px": [312,80], "src": [224,128], "f": 0, "t": 540, "d": [519], "a": 1 }, + { "px": [320,80], "src": [224,128], "f": 0, "t": 540, "d": [520], "a": 1 }, + { "px": [328,80], "src": [224,128], "f": 0, "t": 540, "d": [521], "a": 1 }, + { "px": [336,80], "src": [224,128], "f": 0, "t": 540, "d": [522], "a": 1 }, + { "px": [344,80], "src": [224,128], "f": 0, "t": 540, "d": [523], "a": 1 }, + { "px": [352,80], "src": [224,128], "f": 0, "t": 540, "d": [524], "a": 1 }, + { "px": [360,80], "src": [224,128], "f": 0, "t": 540, "d": [525], "a": 1 }, + { "px": [368,80], "src": [224,128], "f": 0, "t": 540, "d": [526], "a": 1 }, + { "px": [376,80], "src": [232,128], "f": 0, "t": 541, "d": [527], "a": 1 }, + { "px": [0,88], "src": [216,128], "f": 0, "t": 539, "d": [528], "a": 1 }, + { "px": [8,88], "src": [224,128], "f": 0, "t": 540, "d": [529], "a": 1 }, + { "px": [16,88], "src": [224,128], "f": 0, "t": 540, "d": [530], "a": 1 }, + { "px": [24,88], "src": [224,128], "f": 0, "t": 540, "d": [531], "a": 1 }, + { "px": [32,88], "src": [224,128], "f": 0, "t": 540, "d": [532], "a": 1 }, + { "px": [40,88], "src": [224,128], "f": 0, "t": 540, "d": [533], "a": 1 }, + { "px": [48,88], "src": [224,128], "f": 0, "t": 540, "d": [534], "a": 1 }, + { "px": [56,88], "src": [224,128], "f": 0, "t": 540, "d": [535], "a": 1 }, + { "px": [64,88], "src": [224,128], "f": 0, "t": 540, "d": [536], "a": 1 }, + { "px": [72,88], "src": [224,128], "f": 0, "t": 540, "d": [537], "a": 1 }, + { "px": [80,88], "src": [224,128], "f": 0, "t": 540, "d": [538], "a": 1 }, + { "px": [88,88], "src": [224,128], "f": 0, "t": 540, "d": [539], "a": 1 }, + { "px": [96,88], "src": [224,128], "f": 0, "t": 540, "d": [540], "a": 1 }, + { "px": [104,88], "src": [224,128], "f": 0, "t": 540, "d": [541], "a": 1 }, + { "px": [112,88], "src": [224,128], "f": 0, "t": 540, "d": [542], "a": 1 }, + { "px": [120,88], "src": [224,128], "f": 0, "t": 540, "d": [543], "a": 1 }, + { "px": [128,88], "src": [224,128], "f": 0, "t": 540, "d": [544], "a": 1 }, + { "px": [136,88], "src": [224,128], "f": 0, "t": 540, "d": [545], "a": 1 }, + { "px": [144,88], "src": [224,128], "f": 0, "t": 540, "d": [546], "a": 1 }, + { "px": [152,88], "src": [224,128], "f": 0, "t": 540, "d": [547], "a": 1 }, + { "px": [160,88], "src": [224,128], "f": 0, "t": 540, "d": [548], "a": 1 }, + { "px": [168,88], "src": [224,128], "f": 0, "t": 540, "d": [549], "a": 1 }, + { "px": [176,88], "src": [224,128], "f": 0, "t": 540, "d": [550], "a": 1 }, + { "px": [184,88], "src": [224,128], "f": 0, "t": 540, "d": [551], "a": 1 }, + { "px": [192,88], "src": [224,128], "f": 0, "t": 540, "d": [552], "a": 1 }, + { "px": [200,88], "src": [224,128], "f": 0, "t": 540, "d": [553], "a": 1 }, + { "px": [208,88], "src": [224,128], "f": 0, "t": 540, "d": [554], "a": 1 }, + { "px": [216,88], "src": [224,128], "f": 0, "t": 540, "d": [555], "a": 1 }, + { "px": [224,88], "src": [224,128], "f": 0, "t": 540, "d": [556], "a": 1 }, + { "px": [232,88], "src": [224,128], "f": 0, "t": 540, "d": [557], "a": 1 }, + { "px": [240,88], "src": [224,128], "f": 0, "t": 540, "d": [558], "a": 1 }, + { "px": [248,88], "src": [224,128], "f": 0, "t": 540, "d": [559], "a": 1 }, + { "px": [256,88], "src": [224,128], "f": 0, "t": 540, "d": [560], "a": 1 }, + { "px": [264,88], "src": [224,128], "f": 0, "t": 540, "d": [561], "a": 1 }, + { "px": [272,88], "src": [224,128], "f": 0, "t": 540, "d": [562], "a": 1 }, + { "px": [280,88], "src": [224,128], "f": 0, "t": 540, "d": [563], "a": 1 }, + { "px": [288,88], "src": [224,128], "f": 0, "t": 540, "d": [564], "a": 1 }, + { "px": [296,88], "src": [224,128], "f": 0, "t": 540, "d": [565], "a": 1 }, + { "px": [304,88], "src": [224,128], "f": 0, "t": 540, "d": [566], "a": 1 }, + { "px": [312,88], "src": [224,128], "f": 0, "t": 540, "d": [567], "a": 1 }, + { "px": [320,88], "src": [224,128], "f": 0, "t": 540, "d": [568], "a": 1 }, + { "px": [328,88], "src": [224,128], "f": 0, "t": 540, "d": [569], "a": 1 }, + { "px": [336,88], "src": [224,128], "f": 0, "t": 540, "d": [570], "a": 1 }, + { "px": [344,88], "src": [224,128], "f": 0, "t": 540, "d": [571], "a": 1 }, + { "px": [352,88], "src": [224,128], "f": 0, "t": 540, "d": [572], "a": 1 }, + { "px": [360,88], "src": [224,128], "f": 0, "t": 540, "d": [573], "a": 1 }, + { "px": [368,88], "src": [224,128], "f": 0, "t": 540, "d": [574], "a": 1 }, + { "px": [376,88], "src": [232,128], "f": 0, "t": 541, "d": [575], "a": 1 }, + { "px": [0,96], "src": [216,128], "f": 0, "t": 539, "d": [576], "a": 1 }, + { "px": [8,96], "src": [224,128], "f": 0, "t": 540, "d": [577], "a": 1 }, + { "px": [16,96], "src": [224,128], "f": 0, "t": 540, "d": [578], "a": 1 }, + { "px": [24,96], "src": [224,128], "f": 0, "t": 540, "d": [579], "a": 1 }, + { "px": [32,96], "src": [224,128], "f": 0, "t": 540, "d": [580], "a": 1 }, + { "px": [40,96], "src": [224,128], "f": 0, "t": 540, "d": [581], "a": 1 }, + { "px": [48,96], "src": [224,128], "f": 0, "t": 540, "d": [582], "a": 1 }, + { "px": [56,96], "src": [224,128], "f": 0, "t": 540, "d": [583], "a": 1 }, + { "px": [64,96], "src": [224,128], "f": 0, "t": 540, "d": [584], "a": 1 }, + { "px": [72,96], "src": [224,128], "f": 0, "t": 540, "d": [585], "a": 1 }, + { "px": [80,96], "src": [224,128], "f": 0, "t": 540, "d": [586], "a": 1 }, + { "px": [88,96], "src": [224,128], "f": 0, "t": 540, "d": [587], "a": 1 }, + { "px": [96,96], "src": [224,128], "f": 0, "t": 540, "d": [588], "a": 1 }, + { "px": [104,96], "src": [224,128], "f": 0, "t": 540, "d": [589], "a": 1 }, + { "px": [112,96], "src": [224,128], "f": 0, "t": 540, "d": [590], "a": 1 }, + { "px": [120,96], "src": [224,128], "f": 0, "t": 540, "d": [591], "a": 1 }, + { "px": [128,96], "src": [224,128], "f": 0, "t": 540, "d": [592], "a": 1 }, + { "px": [136,96], "src": [224,128], "f": 0, "t": 540, "d": [593], "a": 1 }, + { "px": [144,96], "src": [224,128], "f": 0, "t": 540, "d": [594], "a": 1 }, + { "px": [152,96], "src": [224,128], "f": 0, "t": 540, "d": [595], "a": 1 }, + { "px": [160,96], "src": [224,128], "f": 0, "t": 540, "d": [596], "a": 1 }, + { "px": [168,96], "src": [224,128], "f": 0, "t": 540, "d": [597], "a": 1 }, + { "px": [176,96], "src": [224,128], "f": 0, "t": 540, "d": [598], "a": 1 }, + { "px": [184,96], "src": [224,128], "f": 0, "t": 540, "d": [599], "a": 1 }, + { "px": [192,96], "src": [224,128], "f": 0, "t": 540, "d": [600], "a": 1 }, + { "px": [200,96], "src": [224,128], "f": 0, "t": 540, "d": [601], "a": 1 }, + { "px": [208,96], "src": [224,128], "f": 0, "t": 540, "d": [602], "a": 1 }, + { "px": [216,96], "src": [224,128], "f": 0, "t": 540, "d": [603], "a": 1 }, + { "px": [224,96], "src": [224,128], "f": 0, "t": 540, "d": [604], "a": 1 }, + { "px": [232,96], "src": [224,128], "f": 0, "t": 540, "d": [605], "a": 1 }, + { "px": [240,96], "src": [224,128], "f": 0, "t": 540, "d": [606], "a": 1 }, + { "px": [248,96], "src": [224,128], "f": 0, "t": 540, "d": [607], "a": 1 }, + { "px": [256,96], "src": [224,128], "f": 0, "t": 540, "d": [608], "a": 1 }, + { "px": [264,96], "src": [224,128], "f": 0, "t": 540, "d": [609], "a": 1 }, + { "px": [272,96], "src": [224,128], "f": 0, "t": 540, "d": [610], "a": 1 }, + { "px": [280,96], "src": [224,128], "f": 0, "t": 540, "d": [611], "a": 1 }, + { "px": [288,96], "src": [224,128], "f": 0, "t": 540, "d": [612], "a": 1 }, + { "px": [296,96], "src": [224,128], "f": 0, "t": 540, "d": [613], "a": 1 }, + { "px": [304,96], "src": [224,128], "f": 0, "t": 540, "d": [614], "a": 1 }, + { "px": [312,96], "src": [224,128], "f": 0, "t": 540, "d": [615], "a": 1 }, + { "px": [320,96], "src": [224,128], "f": 0, "t": 540, "d": [616], "a": 1 }, + { "px": [328,96], "src": [224,128], "f": 0, "t": 540, "d": [617], "a": 1 }, + { "px": [336,96], "src": [224,128], "f": 0, "t": 540, "d": [618], "a": 1 }, + { "px": [344,96], "src": [224,128], "f": 0, "t": 540, "d": [619], "a": 1 }, + { "px": [352,96], "src": [224,128], "f": 0, "t": 540, "d": [620], "a": 1 }, + { "px": [360,96], "src": [224,128], "f": 0, "t": 540, "d": [621], "a": 1 }, + { "px": [368,96], "src": [224,128], "f": 0, "t": 540, "d": [622], "a": 1 }, + { "px": [376,96], "src": [232,128], "f": 0, "t": 541, "d": [623], "a": 1 }, + { "px": [0,104], "src": [216,128], "f": 0, "t": 539, "d": [624], "a": 1 }, + { "px": [8,104], "src": [224,128], "f": 0, "t": 540, "d": [625], "a": 1 }, + { "px": [16,104], "src": [224,128], "f": 0, "t": 540, "d": [626], "a": 1 }, + { "px": [24,104], "src": [224,128], "f": 0, "t": 540, "d": [627], "a": 1 }, + { "px": [32,104], "src": [224,128], "f": 0, "t": 540, "d": [628], "a": 1 }, + { "px": [40,104], "src": [224,128], "f": 0, "t": 540, "d": [629], "a": 1 }, + { "px": [48,104], "src": [224,128], "f": 0, "t": 540, "d": [630], "a": 1 }, + { "px": [56,104], "src": [224,128], "f": 0, "t": 540, "d": [631], "a": 1 }, + { "px": [64,104], "src": [224,128], "f": 0, "t": 540, "d": [632], "a": 1 }, + { "px": [72,104], "src": [224,128], "f": 0, "t": 540, "d": [633], "a": 1 }, + { "px": [80,104], "src": [224,128], "f": 0, "t": 540, "d": [634], "a": 1 }, + { "px": [88,104], "src": [224,128], "f": 0, "t": 540, "d": [635], "a": 1 }, + { "px": [96,104], "src": [224,128], "f": 0, "t": 540, "d": [636], "a": 1 }, + { "px": [104,104], "src": [224,128], "f": 0, "t": 540, "d": [637], "a": 1 }, + { "px": [112,104], "src": [224,128], "f": 0, "t": 540, "d": [638], "a": 1 }, + { "px": [120,104], "src": [224,128], "f": 0, "t": 540, "d": [639], "a": 1 }, + { "px": [128,104], "src": [224,128], "f": 0, "t": 540, "d": [640], "a": 1 }, + { "px": [136,104], "src": [224,128], "f": 0, "t": 540, "d": [641], "a": 1 }, + { "px": [144,104], "src": [224,128], "f": 0, "t": 540, "d": [642], "a": 1 }, + { "px": [152,104], "src": [224,128], "f": 0, "t": 540, "d": [643], "a": 1 }, + { "px": [160,104], "src": [224,128], "f": 0, "t": 540, "d": [644], "a": 1 }, + { "px": [168,104], "src": [224,128], "f": 0, "t": 540, "d": [645], "a": 1 }, + { "px": [176,104], "src": [224,128], "f": 0, "t": 540, "d": [646], "a": 1 }, + { "px": [184,104], "src": [224,128], "f": 0, "t": 540, "d": [647], "a": 1 }, + { "px": [192,104], "src": [224,128], "f": 0, "t": 540, "d": [648], "a": 1 }, + { "px": [200,104], "src": [224,128], "f": 0, "t": 540, "d": [649], "a": 1 }, + { "px": [208,104], "src": [224,128], "f": 0, "t": 540, "d": [650], "a": 1 }, + { "px": [216,104], "src": [224,128], "f": 0, "t": 540, "d": [651], "a": 1 }, + { "px": [224,104], "src": [224,128], "f": 0, "t": 540, "d": [652], "a": 1 }, + { "px": [232,104], "src": [224,128], "f": 0, "t": 540, "d": [653], "a": 1 }, + { "px": [240,104], "src": [224,128], "f": 0, "t": 540, "d": [654], "a": 1 }, + { "px": [248,104], "src": [224,128], "f": 0, "t": 540, "d": [655], "a": 1 }, + { "px": [256,104], "src": [224,128], "f": 0, "t": 540, "d": [656], "a": 1 }, + { "px": [264,104], "src": [224,128], "f": 0, "t": 540, "d": [657], "a": 1 }, + { "px": [272,104], "src": [224,128], "f": 0, "t": 540, "d": [658], "a": 1 }, + { "px": [280,104], "src": [224,128], "f": 0, "t": 540, "d": [659], "a": 1 }, + { "px": [288,104], "src": [224,128], "f": 0, "t": 540, "d": [660], "a": 1 }, + { "px": [296,104], "src": [224,128], "f": 0, "t": 540, "d": [661], "a": 1 }, + { "px": [304,104], "src": [224,128], "f": 0, "t": 540, "d": [662], "a": 1 }, + { "px": [312,104], "src": [224,128], "f": 0, "t": 540, "d": [663], "a": 1 }, + { "px": [320,104], "src": [224,128], "f": 0, "t": 540, "d": [664], "a": 1 }, + { "px": [328,104], "src": [224,128], "f": 0, "t": 540, "d": [665], "a": 1 }, + { "px": [336,104], "src": [224,128], "f": 0, "t": 540, "d": [666], "a": 1 }, + { "px": [344,104], "src": [224,128], "f": 0, "t": 540, "d": [667], "a": 1 }, + { "px": [352,104], "src": [224,128], "f": 0, "t": 540, "d": [668], "a": 1 }, + { "px": [360,104], "src": [224,128], "f": 0, "t": 540, "d": [669], "a": 1 }, + { "px": [368,104], "src": [224,128], "f": 0, "t": 540, "d": [670], "a": 1 }, + { "px": [376,104], "src": [232,128], "f": 0, "t": 541, "d": [671], "a": 1 }, + { "px": [0,112], "src": [216,128], "f": 0, "t": 539, "d": [672], "a": 1 }, + { "px": [8,112], "src": [224,128], "f": 0, "t": 540, "d": [673], "a": 1 }, + { "px": [16,112], "src": [224,128], "f": 0, "t": 540, "d": [674], "a": 1 }, + { "px": [24,112], "src": [224,128], "f": 0, "t": 540, "d": [675], "a": 1 }, + { "px": [32,112], "src": [224,128], "f": 0, "t": 540, "d": [676], "a": 1 }, + { "px": [40,112], "src": [224,128], "f": 0, "t": 540, "d": [677], "a": 1 }, + { "px": [48,112], "src": [224,128], "f": 0, "t": 540, "d": [678], "a": 1 }, + { "px": [56,112], "src": [224,128], "f": 0, "t": 540, "d": [679], "a": 1 }, + { "px": [64,112], "src": [224,128], "f": 0, "t": 540, "d": [680], "a": 1 }, + { "px": [72,112], "src": [224,128], "f": 0, "t": 540, "d": [681], "a": 1 }, + { "px": [80,112], "src": [224,128], "f": 0, "t": 540, "d": [682], "a": 1 }, + { "px": [88,112], "src": [224,128], "f": 0, "t": 540, "d": [683], "a": 1 }, + { "px": [96,112], "src": [224,128], "f": 0, "t": 540, "d": [684], "a": 1 }, + { "px": [104,112], "src": [224,128], "f": 0, "t": 540, "d": [685], "a": 1 }, + { "px": [112,112], "src": [224,128], "f": 0, "t": 540, "d": [686], "a": 1 }, + { "px": [120,112], "src": [224,128], "f": 0, "t": 540, "d": [687], "a": 1 }, + { "px": [128,112], "src": [224,128], "f": 0, "t": 540, "d": [688], "a": 1 }, + { "px": [136,112], "src": [224,128], "f": 0, "t": 540, "d": [689], "a": 1 }, + { "px": [144,112], "src": [224,128], "f": 0, "t": 540, "d": [690], "a": 1 }, + { "px": [152,112], "src": [224,128], "f": 0, "t": 540, "d": [691], "a": 1 }, + { "px": [160,112], "src": [224,128], "f": 0, "t": 540, "d": [692], "a": 1 }, + { "px": [168,112], "src": [224,128], "f": 0, "t": 540, "d": [693], "a": 1 }, + { "px": [176,112], "src": [224,128], "f": 0, "t": 540, "d": [694], "a": 1 }, + { "px": [184,112], "src": [224,128], "f": 0, "t": 540, "d": [695], "a": 1 }, + { "px": [192,112], "src": [224,128], "f": 0, "t": 540, "d": [696], "a": 1 }, + { "px": [200,112], "src": [224,128], "f": 0, "t": 540, "d": [697], "a": 1 }, + { "px": [208,112], "src": [224,128], "f": 0, "t": 540, "d": [698], "a": 1 }, + { "px": [216,112], "src": [224,128], "f": 0, "t": 540, "d": [699], "a": 1 }, + { "px": [224,112], "src": [224,128], "f": 0, "t": 540, "d": [700], "a": 1 }, + { "px": [232,112], "src": [224,128], "f": 0, "t": 540, "d": [701], "a": 1 }, + { "px": [240,112], "src": [224,128], "f": 0, "t": 540, "d": [702], "a": 1 }, + { "px": [248,112], "src": [224,128], "f": 0, "t": 540, "d": [703], "a": 1 }, + { "px": [256,112], "src": [224,128], "f": 0, "t": 540, "d": [704], "a": 1 }, + { "px": [264,112], "src": [224,128], "f": 0, "t": 540, "d": [705], "a": 1 }, + { "px": [272,112], "src": [224,128], "f": 0, "t": 540, "d": [706], "a": 1 }, + { "px": [280,112], "src": [224,128], "f": 0, "t": 540, "d": [707], "a": 1 }, + { "px": [288,112], "src": [224,128], "f": 0, "t": 540, "d": [708], "a": 1 }, + { "px": [296,112], "src": [224,128], "f": 0, "t": 540, "d": [709], "a": 1 }, + { "px": [304,112], "src": [224,128], "f": 0, "t": 540, "d": [710], "a": 1 }, + { "px": [312,112], "src": [224,128], "f": 0, "t": 540, "d": [711], "a": 1 }, + { "px": [320,112], "src": [224,128], "f": 0, "t": 540, "d": [712], "a": 1 }, + { "px": [328,112], "src": [224,128], "f": 0, "t": 540, "d": [713], "a": 1 }, + { "px": [336,112], "src": [224,128], "f": 0, "t": 540, "d": [714], "a": 1 }, + { "px": [344,112], "src": [224,128], "f": 0, "t": 540, "d": [715], "a": 1 }, + { "px": [352,112], "src": [224,128], "f": 0, "t": 540, "d": [716], "a": 1 }, + { "px": [360,112], "src": [224,128], "f": 0, "t": 540, "d": [717], "a": 1 }, + { "px": [368,112], "src": [224,128], "f": 0, "t": 540, "d": [718], "a": 1 }, + { "px": [376,112], "src": [232,128], "f": 0, "t": 541, "d": [719], "a": 1 }, + { "px": [0,120], "src": [216,128], "f": 0, "t": 539, "d": [720], "a": 1 }, + { "px": [8,120], "src": [224,128], "f": 0, "t": 540, "d": [721], "a": 1 }, + { "px": [16,120], "src": [224,128], "f": 0, "t": 540, "d": [722], "a": 1 }, + { "px": [24,120], "src": [224,128], "f": 0, "t": 540, "d": [723], "a": 1 }, + { "px": [32,120], "src": [224,128], "f": 0, "t": 540, "d": [724], "a": 1 }, + { "px": [40,120], "src": [224,128], "f": 0, "t": 540, "d": [725], "a": 1 }, + { "px": [48,120], "src": [224,128], "f": 0, "t": 540, "d": [726], "a": 1 }, + { "px": [56,120], "src": [224,128], "f": 0, "t": 540, "d": [727], "a": 1 }, + { "px": [64,120], "src": [224,128], "f": 0, "t": 540, "d": [728], "a": 1 }, + { "px": [72,120], "src": [224,128], "f": 0, "t": 540, "d": [729], "a": 1 }, + { "px": [80,120], "src": [224,128], "f": 0, "t": 540, "d": [730], "a": 1 }, + { "px": [88,120], "src": [224,128], "f": 0, "t": 540, "d": [731], "a": 1 }, + { "px": [96,120], "src": [224,128], "f": 0, "t": 540, "d": [732], "a": 1 }, + { "px": [104,120], "src": [224,128], "f": 0, "t": 540, "d": [733], "a": 1 }, + { "px": [112,120], "src": [224,128], "f": 0, "t": 540, "d": [734], "a": 1 }, + { "px": [120,120], "src": [224,128], "f": 0, "t": 540, "d": [735], "a": 1 }, + { "px": [128,120], "src": [224,128], "f": 0, "t": 540, "d": [736], "a": 1 }, + { "px": [136,120], "src": [224,128], "f": 0, "t": 540, "d": [737], "a": 1 }, + { "px": [144,120], "src": [224,128], "f": 0, "t": 540, "d": [738], "a": 1 }, + { "px": [152,120], "src": [224,128], "f": 0, "t": 540, "d": [739], "a": 1 }, + { "px": [160,120], "src": [224,128], "f": 0, "t": 540, "d": [740], "a": 1 }, + { "px": [168,120], "src": [224,128], "f": 0, "t": 540, "d": [741], "a": 1 }, + { "px": [176,120], "src": [224,128], "f": 0, "t": 540, "d": [742], "a": 1 }, + { "px": [184,120], "src": [224,128], "f": 0, "t": 540, "d": [743], "a": 1 }, + { "px": [192,120], "src": [224,128], "f": 0, "t": 540, "d": [744], "a": 1 }, + { "px": [200,120], "src": [224,128], "f": 0, "t": 540, "d": [745], "a": 1 }, + { "px": [208,120], "src": [224,128], "f": 0, "t": 540, "d": [746], "a": 1 }, + { "px": [216,120], "src": [224,128], "f": 0, "t": 540, "d": [747], "a": 1 }, + { "px": [224,120], "src": [224,128], "f": 0, "t": 540, "d": [748], "a": 1 }, + { "px": [232,120], "src": [224,128], "f": 0, "t": 540, "d": [749], "a": 1 }, + { "px": [240,120], "src": [224,128], "f": 0, "t": 540, "d": [750], "a": 1 }, + { "px": [248,120], "src": [224,128], "f": 0, "t": 540, "d": [751], "a": 1 }, + { "px": [256,120], "src": [224,128], "f": 0, "t": 540, "d": [752], "a": 1 }, + { "px": [264,120], "src": [224,128], "f": 0, "t": 540, "d": [753], "a": 1 }, + { "px": [272,120], "src": [224,128], "f": 0, "t": 540, "d": [754], "a": 1 }, + { "px": [280,120], "src": [224,128], "f": 0, "t": 540, "d": [755], "a": 1 }, + { "px": [288,120], "src": [224,128], "f": 0, "t": 540, "d": [756], "a": 1 }, + { "px": [296,120], "src": [224,128], "f": 0, "t": 540, "d": [757], "a": 1 }, + { "px": [304,120], "src": [224,128], "f": 0, "t": 540, "d": [758], "a": 1 }, + { "px": [312,120], "src": [224,128], "f": 0, "t": 540, "d": [759], "a": 1 }, + { "px": [320,120], "src": [224,128], "f": 0, "t": 540, "d": [760], "a": 1 }, + { "px": [328,120], "src": [224,128], "f": 0, "t": 540, "d": [761], "a": 1 }, + { "px": [336,120], "src": [224,128], "f": 0, "t": 540, "d": [762], "a": 1 }, + { "px": [344,120], "src": [224,128], "f": 0, "t": 540, "d": [763], "a": 1 }, + { "px": [352,120], "src": [224,128], "f": 0, "t": 540, "d": [764], "a": 1 }, + { "px": [360,120], "src": [224,128], "f": 0, "t": 540, "d": [765], "a": 1 }, + { "px": [368,120], "src": [224,128], "f": 0, "t": 540, "d": [766], "a": 1 }, + { "px": [376,120], "src": [232,128], "f": 0, "t": 541, "d": [767], "a": 1 }, + { "px": [0,128], "src": [216,128], "f": 0, "t": 539, "d": [768], "a": 1 }, + { "px": [8,128], "src": [224,128], "f": 0, "t": 540, "d": [769], "a": 1 }, + { "px": [16,128], "src": [224,128], "f": 0, "t": 540, "d": [770], "a": 1 }, + { "px": [24,128], "src": [224,128], "f": 0, "t": 540, "d": [771], "a": 1 }, + { "px": [32,128], "src": [224,128], "f": 0, "t": 540, "d": [772], "a": 1 }, + { "px": [40,128], "src": [224,128], "f": 0, "t": 540, "d": [773], "a": 1 }, + { "px": [48,128], "src": [224,128], "f": 0, "t": 540, "d": [774], "a": 1 }, + { "px": [56,128], "src": [224,128], "f": 0, "t": 540, "d": [775], "a": 1 }, + { "px": [64,128], "src": [224,128], "f": 0, "t": 540, "d": [776], "a": 1 }, + { "px": [72,128], "src": [224,128], "f": 0, "t": 540, "d": [777], "a": 1 }, + { "px": [80,128], "src": [224,128], "f": 0, "t": 540, "d": [778], "a": 1 }, + { "px": [88,128], "src": [224,128], "f": 0, "t": 540, "d": [779], "a": 1 }, + { "px": [96,128], "src": [224,128], "f": 0, "t": 540, "d": [780], "a": 1 }, + { "px": [104,128], "src": [224,128], "f": 0, "t": 540, "d": [781], "a": 1 }, + { "px": [112,128], "src": [224,128], "f": 0, "t": 540, "d": [782], "a": 1 }, + { "px": [120,128], "src": [224,128], "f": 0, "t": 540, "d": [783], "a": 1 }, + { "px": [128,128], "src": [224,128], "f": 0, "t": 540, "d": [784], "a": 1 }, + { "px": [136,128], "src": [224,128], "f": 0, "t": 540, "d": [785], "a": 1 }, + { "px": [144,128], "src": [224,128], "f": 0, "t": 540, "d": [786], "a": 1 }, + { "px": [152,128], "src": [224,128], "f": 0, "t": 540, "d": [787], "a": 1 }, + { "px": [160,128], "src": [224,128], "f": 0, "t": 540, "d": [788], "a": 1 }, + { "px": [168,128], "src": [224,128], "f": 0, "t": 540, "d": [789], "a": 1 }, + { "px": [176,128], "src": [224,128], "f": 0, "t": 540, "d": [790], "a": 1 }, + { "px": [184,128], "src": [224,128], "f": 0, "t": 540, "d": [791], "a": 1 }, + { "px": [192,128], "src": [224,128], "f": 0, "t": 540, "d": [792], "a": 1 }, + { "px": [200,128], "src": [224,128], "f": 0, "t": 540, "d": [793], "a": 1 }, + { "px": [208,128], "src": [224,128], "f": 0, "t": 540, "d": [794], "a": 1 }, + { "px": [216,128], "src": [224,128], "f": 0, "t": 540, "d": [795], "a": 1 }, + { "px": [224,128], "src": [224,128], "f": 0, "t": 540, "d": [796], "a": 1 }, + { "px": [232,128], "src": [224,128], "f": 0, "t": 540, "d": [797], "a": 1 }, + { "px": [240,128], "src": [224,128], "f": 0, "t": 540, "d": [798], "a": 1 }, + { "px": [248,128], "src": [224,128], "f": 0, "t": 540, "d": [799], "a": 1 }, + { "px": [256,128], "src": [224,128], "f": 0, "t": 540, "d": [800], "a": 1 }, + { "px": [264,128], "src": [224,128], "f": 0, "t": 540, "d": [801], "a": 1 }, + { "px": [272,128], "src": [224,128], "f": 0, "t": 540, "d": [802], "a": 1 }, + { "px": [280,128], "src": [224,128], "f": 0, "t": 540, "d": [803], "a": 1 }, + { "px": [288,128], "src": [224,128], "f": 0, "t": 540, "d": [804], "a": 1 }, + { "px": [296,128], "src": [224,128], "f": 0, "t": 540, "d": [805], "a": 1 }, + { "px": [304,128], "src": [224,128], "f": 0, "t": 540, "d": [806], "a": 1 }, + { "px": [312,128], "src": [224,128], "f": 0, "t": 540, "d": [807], "a": 1 }, + { "px": [320,128], "src": [224,128], "f": 0, "t": 540, "d": [808], "a": 1 }, + { "px": [328,128], "src": [224,128], "f": 0, "t": 540, "d": [809], "a": 1 }, + { "px": [336,128], "src": [224,128], "f": 0, "t": 540, "d": [810], "a": 1 }, + { "px": [344,128], "src": [224,128], "f": 0, "t": 540, "d": [811], "a": 1 }, + { "px": [352,128], "src": [224,128], "f": 0, "t": 540, "d": [812], "a": 1 }, + { "px": [360,128], "src": [224,128], "f": 0, "t": 540, "d": [813], "a": 1 }, + { "px": [368,128], "src": [224,128], "f": 0, "t": 540, "d": [814], "a": 1 }, + { "px": [376,128], "src": [232,128], "f": 0, "t": 541, "d": [815], "a": 1 }, + { "px": [0,136], "src": [216,128], "f": 0, "t": 539, "d": [816], "a": 1 }, + { "px": [8,136], "src": [224,128], "f": 0, "t": 540, "d": [817], "a": 1 }, + { "px": [16,136], "src": [224,128], "f": 0, "t": 540, "d": [818], "a": 1 }, + { "px": [24,136], "src": [224,128], "f": 0, "t": 540, "d": [819], "a": 1 }, + { "px": [32,136], "src": [224,128], "f": 0, "t": 540, "d": [820], "a": 1 }, + { "px": [40,136], "src": [224,128], "f": 0, "t": 540, "d": [821], "a": 1 }, + { "px": [48,136], "src": [224,128], "f": 0, "t": 540, "d": [822], "a": 1 }, + { "px": [56,136], "src": [224,128], "f": 0, "t": 540, "d": [823], "a": 1 }, + { "px": [64,136], "src": [224,128], "f": 0, "t": 540, "d": [824], "a": 1 }, + { "px": [72,136], "src": [224,128], "f": 0, "t": 540, "d": [825], "a": 1 }, + { "px": [80,136], "src": [224,128], "f": 0, "t": 540, "d": [826], "a": 1 }, + { "px": [88,136], "src": [224,128], "f": 0, "t": 540, "d": [827], "a": 1 }, + { "px": [96,136], "src": [224,128], "f": 0, "t": 540, "d": [828], "a": 1 }, + { "px": [104,136], "src": [224,128], "f": 0, "t": 540, "d": [829], "a": 1 }, + { "px": [112,136], "src": [224,128], "f": 0, "t": 540, "d": [830], "a": 1 }, + { "px": [120,136], "src": [224,128], "f": 0, "t": 540, "d": [831], "a": 1 }, + { "px": [128,136], "src": [224,128], "f": 0, "t": 540, "d": [832], "a": 1 }, + { "px": [136,136], "src": [224,128], "f": 0, "t": 540, "d": [833], "a": 1 }, + { "px": [144,136], "src": [224,128], "f": 0, "t": 540, "d": [834], "a": 1 }, + { "px": [152,136], "src": [224,128], "f": 0, "t": 540, "d": [835], "a": 1 }, + { "px": [160,136], "src": [224,128], "f": 0, "t": 540, "d": [836], "a": 1 }, + { "px": [168,136], "src": [224,128], "f": 0, "t": 540, "d": [837], "a": 1 }, + { "px": [176,136], "src": [224,128], "f": 0, "t": 540, "d": [838], "a": 1 }, + { "px": [184,136], "src": [224,128], "f": 0, "t": 540, "d": [839], "a": 1 }, + { "px": [192,136], "src": [224,128], "f": 0, "t": 540, "d": [840], "a": 1 }, + { "px": [200,136], "src": [224,128], "f": 0, "t": 540, "d": [841], "a": 1 }, + { "px": [208,136], "src": [224,128], "f": 0, "t": 540, "d": [842], "a": 1 }, + { "px": [216,136], "src": [224,128], "f": 0, "t": 540, "d": [843], "a": 1 }, + { "px": [224,136], "src": [224,128], "f": 0, "t": 540, "d": [844], "a": 1 }, + { "px": [232,136], "src": [224,128], "f": 0, "t": 540, "d": [845], "a": 1 }, + { "px": [240,136], "src": [224,128], "f": 0, "t": 540, "d": [846], "a": 1 }, + { "px": [248,136], "src": [224,128], "f": 0, "t": 540, "d": [847], "a": 1 }, + { "px": [256,136], "src": [224,128], "f": 0, "t": 540, "d": [848], "a": 1 }, + { "px": [264,136], "src": [224,128], "f": 0, "t": 540, "d": [849], "a": 1 }, + { "px": [272,136], "src": [224,128], "f": 0, "t": 540, "d": [850], "a": 1 }, + { "px": [280,136], "src": [224,128], "f": 0, "t": 540, "d": [851], "a": 1 }, + { "px": [288,136], "src": [224,128], "f": 0, "t": 540, "d": [852], "a": 1 }, + { "px": [296,136], "src": [224,128], "f": 0, "t": 540, "d": [853], "a": 1 }, + { "px": [304,136], "src": [224,128], "f": 0, "t": 540, "d": [854], "a": 1 }, + { "px": [312,136], "src": [224,128], "f": 0, "t": 540, "d": [855], "a": 1 }, + { "px": [320,136], "src": [224,128], "f": 0, "t": 540, "d": [856], "a": 1 }, + { "px": [328,136], "src": [224,128], "f": 0, "t": 540, "d": [857], "a": 1 }, + { "px": [336,136], "src": [224,128], "f": 0, "t": 540, "d": [858], "a": 1 }, + { "px": [344,136], "src": [224,128], "f": 0, "t": 540, "d": [859], "a": 1 }, + { "px": [352,136], "src": [224,128], "f": 0, "t": 540, "d": [860], "a": 1 }, + { "px": [360,136], "src": [224,128], "f": 0, "t": 540, "d": [861], "a": 1 }, + { "px": [368,136], "src": [224,128], "f": 0, "t": 540, "d": [862], "a": 1 }, + { "px": [376,136], "src": [232,128], "f": 0, "t": 541, "d": [863], "a": 1 }, + { "px": [0,144], "src": [216,128], "f": 0, "t": 539, "d": [864], "a": 1 }, + { "px": [8,144], "src": [224,128], "f": 0, "t": 540, "d": [865], "a": 1 }, + { "px": [16,144], "src": [224,128], "f": 0, "t": 540, "d": [866], "a": 1 }, + { "px": [24,144], "src": [224,128], "f": 0, "t": 540, "d": [867], "a": 1 }, + { "px": [32,144], "src": [224,128], "f": 0, "t": 540, "d": [868], "a": 1 }, + { "px": [40,144], "src": [224,128], "f": 0, "t": 540, "d": [869], "a": 1 }, + { "px": [48,144], "src": [224,128], "f": 0, "t": 540, "d": [870], "a": 1 }, + { "px": [56,144], "src": [224,128], "f": 0, "t": 540, "d": [871], "a": 1 }, + { "px": [64,144], "src": [224,128], "f": 0, "t": 540, "d": [872], "a": 1 }, + { "px": [72,144], "src": [224,128], "f": 0, "t": 540, "d": [873], "a": 1 }, + { "px": [80,144], "src": [224,128], "f": 0, "t": 540, "d": [874], "a": 1 }, + { "px": [88,144], "src": [224,128], "f": 0, "t": 540, "d": [875], "a": 1 }, + { "px": [96,144], "src": [224,128], "f": 0, "t": 540, "d": [876], "a": 1 }, + { "px": [104,144], "src": [224,128], "f": 0, "t": 540, "d": [877], "a": 1 }, + { "px": [112,144], "src": [224,128], "f": 0, "t": 540, "d": [878], "a": 1 }, + { "px": [120,144], "src": [224,128], "f": 0, "t": 540, "d": [879], "a": 1 }, + { "px": [128,144], "src": [224,128], "f": 0, "t": 540, "d": [880], "a": 1 }, + { "px": [136,144], "src": [224,128], "f": 0, "t": 540, "d": [881], "a": 1 }, + { "px": [144,144], "src": [224,128], "f": 0, "t": 540, "d": [882], "a": 1 }, + { "px": [152,144], "src": [224,128], "f": 0, "t": 540, "d": [883], "a": 1 }, + { "px": [160,144], "src": [224,128], "f": 0, "t": 540, "d": [884], "a": 1 }, + { "px": [168,144], "src": [224,128], "f": 0, "t": 540, "d": [885], "a": 1 }, + { "px": [176,144], "src": [224,128], "f": 0, "t": 540, "d": [886], "a": 1 }, + { "px": [184,144], "src": [224,128], "f": 0, "t": 540, "d": [887], "a": 1 }, + { "px": [192,144], "src": [224,128], "f": 0, "t": 540, "d": [888], "a": 1 }, + { "px": [200,144], "src": [224,128], "f": 0, "t": 540, "d": [889], "a": 1 }, + { "px": [208,144], "src": [224,128], "f": 0, "t": 540, "d": [890], "a": 1 }, + { "px": [216,144], "src": [224,128], "f": 0, "t": 540, "d": [891], "a": 1 }, + { "px": [224,144], "src": [224,128], "f": 0, "t": 540, "d": [892], "a": 1 }, + { "px": [232,144], "src": [224,128], "f": 0, "t": 540, "d": [893], "a": 1 }, + { "px": [240,144], "src": [224,128], "f": 0, "t": 540, "d": [894], "a": 1 }, + { "px": [248,144], "src": [224,128], "f": 0, "t": 540, "d": [895], "a": 1 }, + { "px": [256,144], "src": [224,128], "f": 0, "t": 540, "d": [896], "a": 1 }, + { "px": [264,144], "src": [224,128], "f": 0, "t": 540, "d": [897], "a": 1 }, + { "px": [272,144], "src": [224,128], "f": 0, "t": 540, "d": [898], "a": 1 }, + { "px": [280,144], "src": [224,128], "f": 0, "t": 540, "d": [899], "a": 1 }, + { "px": [288,144], "src": [224,128], "f": 0, "t": 540, "d": [900], "a": 1 }, + { "px": [296,144], "src": [224,128], "f": 0, "t": 540, "d": [901], "a": 1 }, + { "px": [304,144], "src": [224,128], "f": 0, "t": 540, "d": [902], "a": 1 }, + { "px": [312,144], "src": [224,128], "f": 0, "t": 540, "d": [903], "a": 1 }, + { "px": [320,144], "src": [224,128], "f": 0, "t": 540, "d": [904], "a": 1 }, + { "px": [328,144], "src": [224,128], "f": 0, "t": 540, "d": [905], "a": 1 }, + { "px": [336,144], "src": [224,128], "f": 0, "t": 540, "d": [906], "a": 1 }, + { "px": [344,144], "src": [224,128], "f": 0, "t": 540, "d": [907], "a": 1 }, + { "px": [352,144], "src": [224,128], "f": 0, "t": 540, "d": [908], "a": 1 }, + { "px": [360,144], "src": [224,128], "f": 0, "t": 540, "d": [909], "a": 1 }, + { "px": [368,144], "src": [224,128], "f": 0, "t": 540, "d": [910], "a": 1 }, + { "px": [376,144], "src": [232,128], "f": 0, "t": 541, "d": [911], "a": 1 }, + { "px": [0,152], "src": [216,128], "f": 0, "t": 539, "d": [912], "a": 1 }, + { "px": [8,152], "src": [224,128], "f": 0, "t": 540, "d": [913], "a": 1 }, + { "px": [16,152], "src": [224,128], "f": 0, "t": 540, "d": [914], "a": 1 }, + { "px": [24,152], "src": [224,128], "f": 0, "t": 540, "d": [915], "a": 1 }, + { "px": [32,152], "src": [224,128], "f": 0, "t": 540, "d": [916], "a": 1 }, + { "px": [40,152], "src": [224,128], "f": 0, "t": 540, "d": [917], "a": 1 }, + { "px": [48,152], "src": [224,128], "f": 0, "t": 540, "d": [918], "a": 1 }, + { "px": [56,152], "src": [224,128], "f": 0, "t": 540, "d": [919], "a": 1 }, + { "px": [64,152], "src": [224,128], "f": 0, "t": 540, "d": [920], "a": 1 }, + { "px": [72,152], "src": [224,128], "f": 0, "t": 540, "d": [921], "a": 1 }, + { "px": [80,152], "src": [224,128], "f": 0, "t": 540, "d": [922], "a": 1 }, + { "px": [88,152], "src": [224,128], "f": 0, "t": 540, "d": [923], "a": 1 }, + { "px": [96,152], "src": [224,128], "f": 0, "t": 540, "d": [924], "a": 1 }, + { "px": [104,152], "src": [224,128], "f": 0, "t": 540, "d": [925], "a": 1 }, + { "px": [112,152], "src": [224,128], "f": 0, "t": 540, "d": [926], "a": 1 }, + { "px": [120,152], "src": [224,128], "f": 0, "t": 540, "d": [927], "a": 1 }, + { "px": [128,152], "src": [224,128], "f": 0, "t": 540, "d": [928], "a": 1 }, + { "px": [136,152], "src": [224,128], "f": 0, "t": 540, "d": [929], "a": 1 }, + { "px": [144,152], "src": [224,128], "f": 0, "t": 540, "d": [930], "a": 1 }, + { "px": [152,152], "src": [224,128], "f": 0, "t": 540, "d": [931], "a": 1 }, + { "px": [160,152], "src": [224,128], "f": 0, "t": 540, "d": [932], "a": 1 }, + { "px": [168,152], "src": [224,128], "f": 0, "t": 540, "d": [933], "a": 1 }, + { "px": [176,152], "src": [224,128], "f": 0, "t": 540, "d": [934], "a": 1 }, + { "px": [184,152], "src": [224,128], "f": 0, "t": 540, "d": [935], "a": 1 }, + { "px": [192,152], "src": [224,128], "f": 0, "t": 540, "d": [936], "a": 1 }, + { "px": [200,152], "src": [224,128], "f": 0, "t": 540, "d": [937], "a": 1 }, + { "px": [208,152], "src": [224,128], "f": 0, "t": 540, "d": [938], "a": 1 }, + { "px": [216,152], "src": [224,128], "f": 0, "t": 540, "d": [939], "a": 1 }, + { "px": [224,152], "src": [224,128], "f": 0, "t": 540, "d": [940], "a": 1 }, + { "px": [232,152], "src": [224,128], "f": 0, "t": 540, "d": [941], "a": 1 }, + { "px": [240,152], "src": [224,128], "f": 0, "t": 540, "d": [942], "a": 1 }, + { "px": [248,152], "src": [224,128], "f": 0, "t": 540, "d": [943], "a": 1 }, + { "px": [256,152], "src": [224,128], "f": 0, "t": 540, "d": [944], "a": 1 }, + { "px": [264,152], "src": [224,128], "f": 0, "t": 540, "d": [945], "a": 1 }, + { "px": [272,152], "src": [224,128], "f": 0, "t": 540, "d": [946], "a": 1 }, + { "px": [280,152], "src": [224,128], "f": 0, "t": 540, "d": [947], "a": 1 }, + { "px": [288,152], "src": [224,128], "f": 0, "t": 540, "d": [948], "a": 1 }, + { "px": [296,152], "src": [224,128], "f": 0, "t": 540, "d": [949], "a": 1 }, + { "px": [304,152], "src": [224,128], "f": 0, "t": 540, "d": [950], "a": 1 }, + { "px": [312,152], "src": [224,128], "f": 0, "t": 540, "d": [951], "a": 1 }, + { "px": [320,152], "src": [224,128], "f": 0, "t": 540, "d": [952], "a": 1 }, + { "px": [328,152], "src": [224,128], "f": 0, "t": 540, "d": [953], "a": 1 }, + { "px": [336,152], "src": [224,128], "f": 0, "t": 540, "d": [954], "a": 1 }, + { "px": [344,152], "src": [224,128], "f": 0, "t": 540, "d": [955], "a": 1 }, + { "px": [352,152], "src": [224,128], "f": 0, "t": 540, "d": [956], "a": 1 }, + { "px": [360,152], "src": [224,128], "f": 0, "t": 540, "d": [957], "a": 1 }, + { "px": [368,152], "src": [224,128], "f": 0, "t": 540, "d": [958], "a": 1 }, + { "px": [376,152], "src": [232,128], "f": 0, "t": 541, "d": [959], "a": 1 }, + { "px": [0,160], "src": [216,128], "f": 0, "t": 539, "d": [960], "a": 1 }, + { "px": [8,160], "src": [224,128], "f": 0, "t": 540, "d": [961], "a": 1 }, + { "px": [16,160], "src": [224,128], "f": 0, "t": 540, "d": [962], "a": 1 }, + { "px": [24,160], "src": [224,128], "f": 0, "t": 540, "d": [963], "a": 1 }, + { "px": [32,160], "src": [224,128], "f": 0, "t": 540, "d": [964], "a": 1 }, + { "px": [40,160], "src": [224,128], "f": 0, "t": 540, "d": [965], "a": 1 }, + { "px": [48,160], "src": [224,128], "f": 0, "t": 540, "d": [966], "a": 1 }, + { "px": [56,160], "src": [224,128], "f": 0, "t": 540, "d": [967], "a": 1 }, + { "px": [64,160], "src": [224,128], "f": 0, "t": 540, "d": [968], "a": 1 }, + { "px": [72,160], "src": [224,128], "f": 0, "t": 540, "d": [969], "a": 1 }, + { "px": [80,160], "src": [224,128], "f": 0, "t": 540, "d": [970], "a": 1 }, + { "px": [88,160], "src": [224,128], "f": 0, "t": 540, "d": [971], "a": 1 }, + { "px": [96,160], "src": [224,128], "f": 0, "t": 540, "d": [972], "a": 1 }, + { "px": [104,160], "src": [224,128], "f": 0, "t": 540, "d": [973], "a": 1 }, + { "px": [112,160], "src": [224,128], "f": 0, "t": 540, "d": [974], "a": 1 }, + { "px": [120,160], "src": [224,128], "f": 0, "t": 540, "d": [975], "a": 1 }, + { "px": [128,160], "src": [224,128], "f": 0, "t": 540, "d": [976], "a": 1 }, + { "px": [136,160], "src": [224,128], "f": 0, "t": 540, "d": [977], "a": 1 }, + { "px": [144,160], "src": [224,128], "f": 0, "t": 540, "d": [978], "a": 1 }, + { "px": [152,160], "src": [224,128], "f": 0, "t": 540, "d": [979], "a": 1 }, + { "px": [160,160], "src": [224,128], "f": 0, "t": 540, "d": [980], "a": 1 }, + { "px": [168,160], "src": [224,128], "f": 0, "t": 540, "d": [981], "a": 1 }, + { "px": [176,160], "src": [224,128], "f": 0, "t": 540, "d": [982], "a": 1 }, + { "px": [184,160], "src": [224,128], "f": 0, "t": 540, "d": [983], "a": 1 }, + { "px": [192,160], "src": [224,128], "f": 0, "t": 540, "d": [984], "a": 1 }, + { "px": [200,160], "src": [224,128], "f": 0, "t": 540, "d": [985], "a": 1 }, + { "px": [208,160], "src": [224,128], "f": 0, "t": 540, "d": [986], "a": 1 }, + { "px": [216,160], "src": [224,128], "f": 0, "t": 540, "d": [987], "a": 1 }, + { "px": [224,160], "src": [224,128], "f": 0, "t": 540, "d": [988], "a": 1 }, + { "px": [232,160], "src": [224,128], "f": 0, "t": 540, "d": [989], "a": 1 }, + { "px": [240,160], "src": [224,128], "f": 0, "t": 540, "d": [990], "a": 1 }, + { "px": [248,160], "src": [224,128], "f": 0, "t": 540, "d": [991], "a": 1 }, + { "px": [256,160], "src": [224,128], "f": 0, "t": 540, "d": [992], "a": 1 }, + { "px": [264,160], "src": [224,128], "f": 0, "t": 540, "d": [993], "a": 1 }, + { "px": [272,160], "src": [224,128], "f": 0, "t": 540, "d": [994], "a": 1 }, + { "px": [280,160], "src": [224,128], "f": 0, "t": 540, "d": [995], "a": 1 }, + { "px": [288,160], "src": [224,128], "f": 0, "t": 540, "d": [996], "a": 1 }, + { "px": [296,160], "src": [224,128], "f": 0, "t": 540, "d": [997], "a": 1 }, + { "px": [304,160], "src": [224,128], "f": 0, "t": 540, "d": [998], "a": 1 }, + { "px": [312,160], "src": [224,128], "f": 0, "t": 540, "d": [999], "a": 1 }, + { "px": [320,160], "src": [224,128], "f": 0, "t": 540, "d": [1000], "a": 1 }, + { "px": [328,160], "src": [224,128], "f": 0, "t": 540, "d": [1001], "a": 1 }, + { "px": [336,160], "src": [224,128], "f": 0, "t": 540, "d": [1002], "a": 1 }, + { "px": [344,160], "src": [224,128], "f": 0, "t": 540, "d": [1003], "a": 1 }, + { "px": [352,160], "src": [224,128], "f": 0, "t": 540, "d": [1004], "a": 1 }, + { "px": [360,160], "src": [224,128], "f": 0, "t": 540, "d": [1005], "a": 1 }, + { "px": [368,160], "src": [224,128], "f": 0, "t": 540, "d": [1006], "a": 1 }, + { "px": [376,160], "src": [232,128], "f": 0, "t": 541, "d": [1007], "a": 1 }, + { "px": [0,168], "src": [216,128], "f": 0, "t": 539, "d": [1008], "a": 1 }, + { "px": [8,168], "src": [224,128], "f": 0, "t": 540, "d": [1009], "a": 1 }, + { "px": [16,168], "src": [224,128], "f": 0, "t": 540, "d": [1010], "a": 1 }, + { "px": [24,168], "src": [224,128], "f": 0, "t": 540, "d": [1011], "a": 1 }, + { "px": [32,168], "src": [224,128], "f": 0, "t": 540, "d": [1012], "a": 1 }, + { "px": [40,168], "src": [224,128], "f": 0, "t": 540, "d": [1013], "a": 1 }, + { "px": [48,168], "src": [224,128], "f": 0, "t": 540, "d": [1014], "a": 1 }, + { "px": [56,168], "src": [224,128], "f": 0, "t": 540, "d": [1015], "a": 1 }, + { "px": [64,168], "src": [224,128], "f": 0, "t": 540, "d": [1016], "a": 1 }, + { "px": [72,168], "src": [224,128], "f": 0, "t": 540, "d": [1017], "a": 1 }, + { "px": [80,168], "src": [224,128], "f": 0, "t": 540, "d": [1018], "a": 1 }, + { "px": [88,168], "src": [224,128], "f": 0, "t": 540, "d": [1019], "a": 1 }, + { "px": [96,168], "src": [224,128], "f": 0, "t": 540, "d": [1020], "a": 1 }, + { "px": [104,168], "src": [224,128], "f": 0, "t": 540, "d": [1021], "a": 1 }, + { "px": [112,168], "src": [224,128], "f": 0, "t": 540, "d": [1022], "a": 1 }, + { "px": [120,168], "src": [224,128], "f": 0, "t": 540, "d": [1023], "a": 1 }, + { "px": [128,168], "src": [224,128], "f": 0, "t": 540, "d": [1024], "a": 1 }, + { "px": [136,168], "src": [224,128], "f": 0, "t": 540, "d": [1025], "a": 1 }, + { "px": [144,168], "src": [224,128], "f": 0, "t": 540, "d": [1026], "a": 1 }, + { "px": [152,168], "src": [224,128], "f": 0, "t": 540, "d": [1027], "a": 1 }, + { "px": [160,168], "src": [224,128], "f": 0, "t": 540, "d": [1028], "a": 1 }, + { "px": [168,168], "src": [224,128], "f": 0, "t": 540, "d": [1029], "a": 1 }, + { "px": [176,168], "src": [224,128], "f": 0, "t": 540, "d": [1030], "a": 1 }, + { "px": [184,168], "src": [224,128], "f": 0, "t": 540, "d": [1031], "a": 1 }, + { "px": [192,168], "src": [224,128], "f": 0, "t": 540, "d": [1032], "a": 1 }, + { "px": [200,168], "src": [224,128], "f": 0, "t": 540, "d": [1033], "a": 1 }, + { "px": [208,168], "src": [224,128], "f": 0, "t": 540, "d": [1034], "a": 1 }, + { "px": [216,168], "src": [224,128], "f": 0, "t": 540, "d": [1035], "a": 1 }, + { "px": [224,168], "src": [224,128], "f": 0, "t": 540, "d": [1036], "a": 1 }, + { "px": [232,168], "src": [224,128], "f": 0, "t": 540, "d": [1037], "a": 1 }, + { "px": [240,168], "src": [224,128], "f": 0, "t": 540, "d": [1038], "a": 1 }, + { "px": [248,168], "src": [224,128], "f": 0, "t": 540, "d": [1039], "a": 1 }, + { "px": [256,168], "src": [224,128], "f": 0, "t": 540, "d": [1040], "a": 1 }, + { "px": [264,168], "src": [224,128], "f": 0, "t": 540, "d": [1041], "a": 1 }, + { "px": [272,168], "src": [224,128], "f": 0, "t": 540, "d": [1042], "a": 1 }, + { "px": [280,168], "src": [224,128], "f": 0, "t": 540, "d": [1043], "a": 1 }, + { "px": [288,168], "src": [224,128], "f": 0, "t": 540, "d": [1044], "a": 1 }, + { "px": [296,168], "src": [224,128], "f": 0, "t": 540, "d": [1045], "a": 1 }, + { "px": [304,168], "src": [224,128], "f": 0, "t": 540, "d": [1046], "a": 1 }, + { "px": [312,168], "src": [224,128], "f": 0, "t": 540, "d": [1047], "a": 1 }, + { "px": [320,168], "src": [224,128], "f": 0, "t": 540, "d": [1048], "a": 1 }, + { "px": [328,168], "src": [224,128], "f": 0, "t": 540, "d": [1049], "a": 1 }, + { "px": [336,168], "src": [224,128], "f": 0, "t": 540, "d": [1050], "a": 1 }, + { "px": [344,168], "src": [224,128], "f": 0, "t": 540, "d": [1051], "a": 1 }, + { "px": [352,168], "src": [224,128], "f": 0, "t": 540, "d": [1052], "a": 1 }, + { "px": [360,168], "src": [224,128], "f": 0, "t": 540, "d": [1053], "a": 1 }, + { "px": [368,168], "src": [224,128], "f": 0, "t": 540, "d": [1054], "a": 1 }, + { "px": [376,168], "src": [232,128], "f": 0, "t": 541, "d": [1055], "a": 1 }, + { "px": [0,176], "src": [216,128], "f": 0, "t": 539, "d": [1056], "a": 1 }, + { "px": [8,176], "src": [224,128], "f": 0, "t": 540, "d": [1057], "a": 1 }, + { "px": [16,176], "src": [224,128], "f": 0, "t": 540, "d": [1058], "a": 1 }, + { "px": [24,176], "src": [224,128], "f": 0, "t": 540, "d": [1059], "a": 1 }, + { "px": [32,176], "src": [224,128], "f": 0, "t": 540, "d": [1060], "a": 1 }, + { "px": [40,176], "src": [224,128], "f": 0, "t": 540, "d": [1061], "a": 1 }, + { "px": [48,176], "src": [224,128], "f": 0, "t": 540, "d": [1062], "a": 1 }, + { "px": [56,176], "src": [224,128], "f": 0, "t": 540, "d": [1063], "a": 1 }, + { "px": [64,176], "src": [224,128], "f": 0, "t": 540, "d": [1064], "a": 1 }, + { "px": [72,176], "src": [224,128], "f": 0, "t": 540, "d": [1065], "a": 1 }, + { "px": [80,176], "src": [224,128], "f": 0, "t": 540, "d": [1066], "a": 1 }, + { "px": [88,176], "src": [224,128], "f": 0, "t": 540, "d": [1067], "a": 1 }, + { "px": [96,176], "src": [224,128], "f": 0, "t": 540, "d": [1068], "a": 1 }, + { "px": [104,176], "src": [224,128], "f": 0, "t": 540, "d": [1069], "a": 1 }, + { "px": [112,176], "src": [224,128], "f": 0, "t": 540, "d": [1070], "a": 1 }, + { "px": [120,176], "src": [224,128], "f": 0, "t": 540, "d": [1071], "a": 1 }, + { "px": [128,176], "src": [224,128], "f": 0, "t": 540, "d": [1072], "a": 1 }, + { "px": [136,176], "src": [224,128], "f": 0, "t": 540, "d": [1073], "a": 1 }, + { "px": [144,176], "src": [224,128], "f": 0, "t": 540, "d": [1074], "a": 1 }, + { "px": [152,176], "src": [224,128], "f": 0, "t": 540, "d": [1075], "a": 1 }, + { "px": [160,176], "src": [224,128], "f": 0, "t": 540, "d": [1076], "a": 1 }, + { "px": [168,176], "src": [224,128], "f": 0, "t": 540, "d": [1077], "a": 1 }, + { "px": [176,176], "src": [224,128], "f": 0, "t": 540, "d": [1078], "a": 1 }, + { "px": [184,176], "src": [224,128], "f": 0, "t": 540, "d": [1079], "a": 1 }, + { "px": [192,176], "src": [224,128], "f": 0, "t": 540, "d": [1080], "a": 1 }, + { "px": [200,176], "src": [224,128], "f": 0, "t": 540, "d": [1081], "a": 1 }, + { "px": [208,176], "src": [224,128], "f": 0, "t": 540, "d": [1082], "a": 1 }, + { "px": [216,176], "src": [224,128], "f": 0, "t": 540, "d": [1083], "a": 1 }, + { "px": [224,176], "src": [224,128], "f": 0, "t": 540, "d": [1084], "a": 1 }, + { "px": [232,176], "src": [224,128], "f": 0, "t": 540, "d": [1085], "a": 1 }, + { "px": [240,176], "src": [224,128], "f": 0, "t": 540, "d": [1086], "a": 1 }, + { "px": [248,176], "src": [224,128], "f": 0, "t": 540, "d": [1087], "a": 1 }, + { "px": [256,176], "src": [224,128], "f": 0, "t": 540, "d": [1088], "a": 1 }, + { "px": [264,176], "src": [224,128], "f": 0, "t": 540, "d": [1089], "a": 1 }, + { "px": [272,176], "src": [224,128], "f": 0, "t": 540, "d": [1090], "a": 1 }, + { "px": [280,176], "src": [224,128], "f": 0, "t": 540, "d": [1091], "a": 1 }, + { "px": [288,176], "src": [224,128], "f": 0, "t": 540, "d": [1092], "a": 1 }, + { "px": [296,176], "src": [224,128], "f": 0, "t": 540, "d": [1093], "a": 1 }, + { "px": [304,176], "src": [224,128], "f": 0, "t": 540, "d": [1094], "a": 1 }, + { "px": [312,176], "src": [224,128], "f": 0, "t": 540, "d": [1095], "a": 1 }, + { "px": [320,176], "src": [224,128], "f": 0, "t": 540, "d": [1096], "a": 1 }, + { "px": [328,176], "src": [224,128], "f": 0, "t": 540, "d": [1097], "a": 1 }, + { "px": [336,176], "src": [224,128], "f": 0, "t": 540, "d": [1098], "a": 1 }, + { "px": [344,176], "src": [224,128], "f": 0, "t": 540, "d": [1099], "a": 1 }, + { "px": [352,176], "src": [224,128], "f": 0, "t": 540, "d": [1100], "a": 1 }, + { "px": [360,176], "src": [224,128], "f": 0, "t": 540, "d": [1101], "a": 1 }, + { "px": [368,176], "src": [224,128], "f": 0, "t": 540, "d": [1102], "a": 1 }, + { "px": [376,176], "src": [232,128], "f": 0, "t": 541, "d": [1103], "a": 1 }, + { "px": [0,184], "src": [216,128], "f": 0, "t": 539, "d": [1104], "a": 1 }, + { "px": [8,184], "src": [224,128], "f": 0, "t": 540, "d": [1105], "a": 1 }, + { "px": [16,184], "src": [224,128], "f": 0, "t": 540, "d": [1106], "a": 1 }, + { "px": [24,184], "src": [224,128], "f": 0, "t": 540, "d": [1107], "a": 1 }, + { "px": [32,184], "src": [224,128], "f": 0, "t": 540, "d": [1108], "a": 1 }, + { "px": [40,184], "src": [224,128], "f": 0, "t": 540, "d": [1109], "a": 1 }, + { "px": [48,184], "src": [224,128], "f": 0, "t": 540, "d": [1110], "a": 1 }, + { "px": [56,184], "src": [224,128], "f": 0, "t": 540, "d": [1111], "a": 1 }, + { "px": [64,184], "src": [224,128], "f": 0, "t": 540, "d": [1112], "a": 1 }, + { "px": [72,184], "src": [224,128], "f": 0, "t": 540, "d": [1113], "a": 1 }, + { "px": [80,184], "src": [224,128], "f": 0, "t": 540, "d": [1114], "a": 1 }, + { "px": [88,184], "src": [224,128], "f": 0, "t": 540, "d": [1115], "a": 1 }, + { "px": [96,184], "src": [224,128], "f": 0, "t": 540, "d": [1116], "a": 1 }, + { "px": [104,184], "src": [224,128], "f": 0, "t": 540, "d": [1117], "a": 1 }, + { "px": [112,184], "src": [224,128], "f": 0, "t": 540, "d": [1118], "a": 1 }, + { "px": [120,184], "src": [224,128], "f": 0, "t": 540, "d": [1119], "a": 1 }, + { "px": [128,184], "src": [224,128], "f": 0, "t": 540, "d": [1120], "a": 1 }, + { "px": [136,184], "src": [224,128], "f": 0, "t": 540, "d": [1121], "a": 1 }, + { "px": [144,184], "src": [224,128], "f": 0, "t": 540, "d": [1122], "a": 1 }, + { "px": [152,184], "src": [224,128], "f": 0, "t": 540, "d": [1123], "a": 1 }, + { "px": [160,184], "src": [224,128], "f": 0, "t": 540, "d": [1124], "a": 1 }, + { "px": [168,184], "src": [224,128], "f": 0, "t": 540, "d": [1125], "a": 1 }, + { "px": [176,184], "src": [224,128], "f": 0, "t": 540, "d": [1126], "a": 1 }, + { "px": [184,184], "src": [224,128], "f": 0, "t": 540, "d": [1127], "a": 1 }, + { "px": [192,184], "src": [224,128], "f": 0, "t": 540, "d": [1128], "a": 1 }, + { "px": [200,184], "src": [224,128], "f": 0, "t": 540, "d": [1129], "a": 1 }, + { "px": [208,184], "src": [224,128], "f": 0, "t": 540, "d": [1130], "a": 1 }, + { "px": [216,184], "src": [224,128], "f": 0, "t": 540, "d": [1131], "a": 1 }, + { "px": [224,184], "src": [224,128], "f": 0, "t": 540, "d": [1132], "a": 1 }, + { "px": [232,184], "src": [224,128], "f": 0, "t": 540, "d": [1133], "a": 1 }, + { "px": [240,184], "src": [224,128], "f": 0, "t": 540, "d": [1134], "a": 1 }, + { "px": [248,184], "src": [224,128], "f": 0, "t": 540, "d": [1135], "a": 1 }, + { "px": [256,184], "src": [224,128], "f": 0, "t": 540, "d": [1136], "a": 1 }, + { "px": [264,184], "src": [224,128], "f": 0, "t": 540, "d": [1137], "a": 1 }, + { "px": [272,184], "src": [224,128], "f": 0, "t": 540, "d": [1138], "a": 1 }, + { "px": [280,184], "src": [224,128], "f": 0, "t": 540, "d": [1139], "a": 1 }, + { "px": [288,184], "src": [224,128], "f": 0, "t": 540, "d": [1140], "a": 1 }, + { "px": [296,184], "src": [224,128], "f": 0, "t": 540, "d": [1141], "a": 1 }, + { "px": [304,184], "src": [224,128], "f": 0, "t": 540, "d": [1142], "a": 1 }, + { "px": [312,184], "src": [224,128], "f": 0, "t": 540, "d": [1143], "a": 1 }, + { "px": [320,184], "src": [224,128], "f": 0, "t": 540, "d": [1144], "a": 1 }, + { "px": [328,184], "src": [224,128], "f": 0, "t": 540, "d": [1145], "a": 1 }, + { "px": [336,184], "src": [224,128], "f": 0, "t": 540, "d": [1146], "a": 1 }, + { "px": [344,184], "src": [224,128], "f": 0, "t": 540, "d": [1147], "a": 1 }, + { "px": [352,184], "src": [224,128], "f": 0, "t": 540, "d": [1148], "a": 1 }, + { "px": [360,184], "src": [224,128], "f": 0, "t": 540, "d": [1149], "a": 1 }, + { "px": [368,184], "src": [224,128], "f": 0, "t": 540, "d": [1150], "a": 1 }, + { "px": [376,184], "src": [232,128], "f": 0, "t": 541, "d": [1151], "a": 1 }, + { "px": [0,192], "src": [216,128], "f": 0, "t": 539, "d": [1152], "a": 1 }, + { "px": [8,192], "src": [224,128], "f": 0, "t": 540, "d": [1153], "a": 1 }, + { "px": [16,192], "src": [224,128], "f": 0, "t": 540, "d": [1154], "a": 1 }, + { "px": [24,192], "src": [224,128], "f": 0, "t": 540, "d": [1155], "a": 1 }, + { "px": [32,192], "src": [224,128], "f": 0, "t": 540, "d": [1156], "a": 1 }, + { "px": [40,192], "src": [224,128], "f": 0, "t": 540, "d": [1157], "a": 1 }, + { "px": [48,192], "src": [224,128], "f": 0, "t": 540, "d": [1158], "a": 1 }, + { "px": [56,192], "src": [224,128], "f": 0, "t": 540, "d": [1159], "a": 1 }, + { "px": [64,192], "src": [224,128], "f": 0, "t": 540, "d": [1160], "a": 1 }, + { "px": [72,192], "src": [224,128], "f": 0, "t": 540, "d": [1161], "a": 1 }, + { "px": [80,192], "src": [224,128], "f": 0, "t": 540, "d": [1162], "a": 1 }, + { "px": [88,192], "src": [224,128], "f": 0, "t": 540, "d": [1163], "a": 1 }, + { "px": [96,192], "src": [224,128], "f": 0, "t": 540, "d": [1164], "a": 1 }, + { "px": [104,192], "src": [224,128], "f": 0, "t": 540, "d": [1165], "a": 1 }, + { "px": [112,192], "src": [224,128], "f": 0, "t": 540, "d": [1166], "a": 1 }, + { "px": [120,192], "src": [224,128], "f": 0, "t": 540, "d": [1167], "a": 1 }, + { "px": [128,192], "src": [224,128], "f": 0, "t": 540, "d": [1168], "a": 1 }, + { "px": [136,192], "src": [224,128], "f": 0, "t": 540, "d": [1169], "a": 1 }, + { "px": [144,192], "src": [224,128], "f": 0, "t": 540, "d": [1170], "a": 1 }, + { "px": [152,192], "src": [224,128], "f": 0, "t": 540, "d": [1171], "a": 1 }, + { "px": [160,192], "src": [224,128], "f": 0, "t": 540, "d": [1172], "a": 1 }, + { "px": [168,192], "src": [224,128], "f": 0, "t": 540, "d": [1173], "a": 1 }, + { "px": [176,192], "src": [224,128], "f": 0, "t": 540, "d": [1174], "a": 1 }, + { "px": [184,192], "src": [224,128], "f": 0, "t": 540, "d": [1175], "a": 1 }, + { "px": [192,192], "src": [224,128], "f": 0, "t": 540, "d": [1176], "a": 1 }, + { "px": [200,192], "src": [224,128], "f": 0, "t": 540, "d": [1177], "a": 1 }, + { "px": [208,192], "src": [224,128], "f": 0, "t": 540, "d": [1178], "a": 1 }, + { "px": [216,192], "src": [224,128], "f": 0, "t": 540, "d": [1179], "a": 1 }, + { "px": [224,192], "src": [224,128], "f": 0, "t": 540, "d": [1180], "a": 1 }, + { "px": [232,192], "src": [224,128], "f": 0, "t": 540, "d": [1181], "a": 1 }, + { "px": [240,192], "src": [224,128], "f": 0, "t": 540, "d": [1182], "a": 1 }, + { "px": [248,192], "src": [224,128], "f": 0, "t": 540, "d": [1183], "a": 1 }, + { "px": [256,192], "src": [224,128], "f": 0, "t": 540, "d": [1184], "a": 1 }, + { "px": [264,192], "src": [224,128], "f": 0, "t": 540, "d": [1185], "a": 1 }, + { "px": [272,192], "src": [224,128], "f": 0, "t": 540, "d": [1186], "a": 1 }, + { "px": [280,192], "src": [224,128], "f": 0, "t": 540, "d": [1187], "a": 1 }, + { "px": [288,192], "src": [224,128], "f": 0, "t": 540, "d": [1188], "a": 1 }, + { "px": [296,192], "src": [224,128], "f": 0, "t": 540, "d": [1189], "a": 1 }, + { "px": [304,192], "src": [224,128], "f": 0, "t": 540, "d": [1190], "a": 1 }, + { "px": [312,192], "src": [224,128], "f": 0, "t": 540, "d": [1191], "a": 1 }, + { "px": [320,192], "src": [224,128], "f": 0, "t": 540, "d": [1192], "a": 1 }, + { "px": [328,192], "src": [224,128], "f": 0, "t": 540, "d": [1193], "a": 1 }, + { "px": [336,192], "src": [224,128], "f": 0, "t": 540, "d": [1194], "a": 1 }, + { "px": [344,192], "src": [224,128], "f": 0, "t": 540, "d": [1195], "a": 1 }, + { "px": [352,192], "src": [224,128], "f": 0, "t": 540, "d": [1196], "a": 1 }, + { "px": [360,192], "src": [224,128], "f": 0, "t": 540, "d": [1197], "a": 1 }, + { "px": [368,192], "src": [224,128], "f": 0, "t": 540, "d": [1198], "a": 1 }, + { "px": [376,192], "src": [232,128], "f": 0, "t": 541, "d": [1199], "a": 1 }, + { "px": [0,200], "src": [216,128], "f": 0, "t": 539, "d": [1200], "a": 1 }, + { "px": [8,200], "src": [224,128], "f": 0, "t": 540, "d": [1201], "a": 1 }, + { "px": [16,200], "src": [224,128], "f": 0, "t": 540, "d": [1202], "a": 1 }, + { "px": [24,200], "src": [224,128], "f": 0, "t": 540, "d": [1203], "a": 1 }, + { "px": [32,200], "src": [224,128], "f": 0, "t": 540, "d": [1204], "a": 1 }, + { "px": [40,200], "src": [224,128], "f": 0, "t": 540, "d": [1205], "a": 1 }, + { "px": [48,200], "src": [224,128], "f": 0, "t": 540, "d": [1206], "a": 1 }, + { "px": [56,200], "src": [224,128], "f": 0, "t": 540, "d": [1207], "a": 1 }, + { "px": [64,200], "src": [224,128], "f": 0, "t": 540, "d": [1208], "a": 1 }, + { "px": [72,200], "src": [224,128], "f": 0, "t": 540, "d": [1209], "a": 1 }, + { "px": [80,200], "src": [224,128], "f": 0, "t": 540, "d": [1210], "a": 1 }, + { "px": [88,200], "src": [224,128], "f": 0, "t": 540, "d": [1211], "a": 1 }, + { "px": [96,200], "src": [224,128], "f": 0, "t": 540, "d": [1212], "a": 1 }, + { "px": [104,200], "src": [224,128], "f": 0, "t": 540, "d": [1213], "a": 1 }, + { "px": [112,200], "src": [224,128], "f": 0, "t": 540, "d": [1214], "a": 1 }, + { "px": [120,200], "src": [224,128], "f": 0, "t": 540, "d": [1215], "a": 1 }, + { "px": [128,200], "src": [224,128], "f": 0, "t": 540, "d": [1216], "a": 1 }, + { "px": [136,200], "src": [224,128], "f": 0, "t": 540, "d": [1217], "a": 1 }, + { "px": [144,200], "src": [224,128], "f": 0, "t": 540, "d": [1218], "a": 1 }, + { "px": [152,200], "src": [224,128], "f": 0, "t": 540, "d": [1219], "a": 1 }, + { "px": [160,200], "src": [224,128], "f": 0, "t": 540, "d": [1220], "a": 1 }, + { "px": [168,200], "src": [224,128], "f": 0, "t": 540, "d": [1221], "a": 1 }, + { "px": [176,200], "src": [224,128], "f": 0, "t": 540, "d": [1222], "a": 1 }, + { "px": [184,200], "src": [224,128], "f": 0, "t": 540, "d": [1223], "a": 1 }, + { "px": [192,200], "src": [224,128], "f": 0, "t": 540, "d": [1224], "a": 1 }, + { "px": [200,200], "src": [224,128], "f": 0, "t": 540, "d": [1225], "a": 1 }, + { "px": [208,200], "src": [224,128], "f": 0, "t": 540, "d": [1226], "a": 1 }, + { "px": [216,200], "src": [224,128], "f": 0, "t": 540, "d": [1227], "a": 1 }, + { "px": [224,200], "src": [224,128], "f": 0, "t": 540, "d": [1228], "a": 1 }, + { "px": [232,200], "src": [224,128], "f": 0, "t": 540, "d": [1229], "a": 1 }, + { "px": [240,200], "src": [224,128], "f": 0, "t": 540, "d": [1230], "a": 1 }, + { "px": [248,200], "src": [224,128], "f": 0, "t": 540, "d": [1231], "a": 1 }, + { "px": [256,200], "src": [224,128], "f": 0, "t": 540, "d": [1232], "a": 1 }, + { "px": [264,200], "src": [224,128], "f": 0, "t": 540, "d": [1233], "a": 1 }, + { "px": [272,200], "src": [224,128], "f": 0, "t": 540, "d": [1234], "a": 1 }, + { "px": [280,200], "src": [224,128], "f": 0, "t": 540, "d": [1235], "a": 1 }, + { "px": [288,200], "src": [224,128], "f": 0, "t": 540, "d": [1236], "a": 1 }, + { "px": [296,200], "src": [224,128], "f": 0, "t": 540, "d": [1237], "a": 1 }, + { "px": [304,200], "src": [224,128], "f": 0, "t": 540, "d": [1238], "a": 1 }, + { "px": [312,200], "src": [224,128], "f": 0, "t": 540, "d": [1239], "a": 1 }, + { "px": [320,200], "src": [224,128], "f": 0, "t": 540, "d": [1240], "a": 1 }, + { "px": [328,200], "src": [224,128], "f": 0, "t": 540, "d": [1241], "a": 1 }, + { "px": [336,200], "src": [224,128], "f": 0, "t": 540, "d": [1242], "a": 1 }, + { "px": [344,200], "src": [224,128], "f": 0, "t": 540, "d": [1243], "a": 1 }, + { "px": [352,200], "src": [224,128], "f": 0, "t": 540, "d": [1244], "a": 1 }, + { "px": [360,200], "src": [224,128], "f": 0, "t": 540, "d": [1245], "a": 1 }, + { "px": [368,200], "src": [224,128], "f": 0, "t": 540, "d": [1246], "a": 1 }, + { "px": [376,200], "src": [232,128], "f": 0, "t": 541, "d": [1247], "a": 1 }, + { "px": [0,208], "src": [216,128], "f": 0, "t": 539, "d": [1248], "a": 1 }, + { "px": [8,208], "src": [224,128], "f": 0, "t": 540, "d": [1249], "a": 1 }, + { "px": [16,208], "src": [224,128], "f": 0, "t": 540, "d": [1250], "a": 1 }, + { "px": [24,208], "src": [224,128], "f": 0, "t": 540, "d": [1251], "a": 1 }, + { "px": [32,208], "src": [224,128], "f": 0, "t": 540, "d": [1252], "a": 1 }, + { "px": [40,208], "src": [224,128], "f": 0, "t": 540, "d": [1253], "a": 1 }, + { "px": [48,208], "src": [224,128], "f": 0, "t": 540, "d": [1254], "a": 1 }, + { "px": [56,208], "src": [224,128], "f": 0, "t": 540, "d": [1255], "a": 1 }, + { "px": [64,208], "src": [224,128], "f": 0, "t": 540, "d": [1256], "a": 1 }, + { "px": [72,208], "src": [224,128], "f": 0, "t": 540, "d": [1257], "a": 1 }, + { "px": [80,208], "src": [224,128], "f": 0, "t": 540, "d": [1258], "a": 1 }, + { "px": [88,208], "src": [224,128], "f": 0, "t": 540, "d": [1259], "a": 1 }, + { "px": [96,208], "src": [224,128], "f": 0, "t": 540, "d": [1260], "a": 1 }, + { "px": [104,208], "src": [224,128], "f": 0, "t": 540, "d": [1261], "a": 1 }, + { "px": [112,208], "src": [224,128], "f": 0, "t": 540, "d": [1262], "a": 1 }, + { "px": [120,208], "src": [224,128], "f": 0, "t": 540, "d": [1263], "a": 1 }, + { "px": [128,208], "src": [224,128], "f": 0, "t": 540, "d": [1264], "a": 1 }, + { "px": [136,208], "src": [224,128], "f": 0, "t": 540, "d": [1265], "a": 1 }, + { "px": [144,208], "src": [224,128], "f": 0, "t": 540, "d": [1266], "a": 1 }, + { "px": [152,208], "src": [224,128], "f": 0, "t": 540, "d": [1267], "a": 1 }, + { "px": [160,208], "src": [224,128], "f": 0, "t": 540, "d": [1268], "a": 1 }, + { "px": [168,208], "src": [224,128], "f": 0, "t": 540, "d": [1269], "a": 1 }, + { "px": [176,208], "src": [224,128], "f": 0, "t": 540, "d": [1270], "a": 1 }, + { "px": [184,208], "src": [224,128], "f": 0, "t": 540, "d": [1271], "a": 1 }, + { "px": [192,208], "src": [224,128], "f": 0, "t": 540, "d": [1272], "a": 1 }, + { "px": [200,208], "src": [224,128], "f": 0, "t": 540, "d": [1273], "a": 1 }, + { "px": [208,208], "src": [224,128], "f": 0, "t": 540, "d": [1274], "a": 1 }, + { "px": [216,208], "src": [224,128], "f": 0, "t": 540, "d": [1275], "a": 1 }, + { "px": [224,208], "src": [224,128], "f": 0, "t": 540, "d": [1276], "a": 1 }, + { "px": [232,208], "src": [224,128], "f": 0, "t": 540, "d": [1277], "a": 1 }, + { "px": [240,208], "src": [224,128], "f": 0, "t": 540, "d": [1278], "a": 1 }, + { "px": [248,208], "src": [224,128], "f": 0, "t": 540, "d": [1279], "a": 1 }, + { "px": [256,208], "src": [224,128], "f": 0, "t": 540, "d": [1280], "a": 1 }, + { "px": [264,208], "src": [224,128], "f": 0, "t": 540, "d": [1281], "a": 1 }, + { "px": [272,208], "src": [224,128], "f": 0, "t": 540, "d": [1282], "a": 1 }, + { "px": [280,208], "src": [224,128], "f": 0, "t": 540, "d": [1283], "a": 1 }, + { "px": [288,208], "src": [224,128], "f": 0, "t": 540, "d": [1284], "a": 1 }, + { "px": [296,208], "src": [224,128], "f": 0, "t": 540, "d": [1285], "a": 1 }, + { "px": [304,208], "src": [224,128], "f": 0, "t": 540, "d": [1286], "a": 1 }, + { "px": [312,208], "src": [224,128], "f": 0, "t": 540, "d": [1287], "a": 1 }, + { "px": [320,208], "src": [224,128], "f": 0, "t": 540, "d": [1288], "a": 1 }, + { "px": [328,208], "src": [224,128], "f": 0, "t": 540, "d": [1289], "a": 1 }, + { "px": [336,208], "src": [224,128], "f": 0, "t": 540, "d": [1290], "a": 1 }, + { "px": [344,208], "src": [224,128], "f": 0, "t": 540, "d": [1291], "a": 1 }, + { "px": [352,208], "src": [224,128], "f": 0, "t": 540, "d": [1292], "a": 1 }, + { "px": [360,208], "src": [224,128], "f": 0, "t": 540, "d": [1293], "a": 1 }, + { "px": [368,208], "src": [224,128], "f": 0, "t": 540, "d": [1294], "a": 1 }, + { "px": [376,208], "src": [232,128], "f": 0, "t": 541, "d": [1295], "a": 1 }, + { "px": [0,216], "src": [216,128], "f": 0, "t": 539, "d": [1296], "a": 1 }, + { "px": [8,216], "src": [224,128], "f": 0, "t": 540, "d": [1297], "a": 1 }, + { "px": [16,216], "src": [224,128], "f": 0, "t": 540, "d": [1298], "a": 1 }, + { "px": [24,216], "src": [224,128], "f": 0, "t": 540, "d": [1299], "a": 1 }, + { "px": [32,216], "src": [224,128], "f": 0, "t": 540, "d": [1300], "a": 1 }, + { "px": [40,216], "src": [224,128], "f": 0, "t": 540, "d": [1301], "a": 1 }, + { "px": [48,216], "src": [224,128], "f": 0, "t": 540, "d": [1302], "a": 1 }, + { "px": [56,216], "src": [224,128], "f": 0, "t": 540, "d": [1303], "a": 1 }, + { "px": [64,216], "src": [224,128], "f": 0, "t": 540, "d": [1304], "a": 1 }, + { "px": [72,216], "src": [224,128], "f": 0, "t": 540, "d": [1305], "a": 1 }, + { "px": [80,216], "src": [224,128], "f": 0, "t": 540, "d": [1306], "a": 1 }, + { "px": [88,216], "src": [224,128], "f": 0, "t": 540, "d": [1307], "a": 1 }, + { "px": [96,216], "src": [224,128], "f": 0, "t": 540, "d": [1308], "a": 1 }, + { "px": [104,216], "src": [224,128], "f": 0, "t": 540, "d": [1309], "a": 1 }, + { "px": [112,216], "src": [224,128], "f": 0, "t": 540, "d": [1310], "a": 1 }, + { "px": [120,216], "src": [224,128], "f": 0, "t": 540, "d": [1311], "a": 1 }, + { "px": [128,216], "src": [224,128], "f": 0, "t": 540, "d": [1312], "a": 1 }, + { "px": [136,216], "src": [224,128], "f": 0, "t": 540, "d": [1313], "a": 1 }, + { "px": [144,216], "src": [224,128], "f": 0, "t": 540, "d": [1314], "a": 1 }, + { "px": [152,216], "src": [224,128], "f": 0, "t": 540, "d": [1315], "a": 1 }, + { "px": [160,216], "src": [224,128], "f": 0, "t": 540, "d": [1316], "a": 1 }, + { "px": [168,216], "src": [224,128], "f": 0, "t": 540, "d": [1317], "a": 1 }, + { "px": [176,216], "src": [224,128], "f": 0, "t": 540, "d": [1318], "a": 1 }, + { "px": [184,216], "src": [224,128], "f": 0, "t": 540, "d": [1319], "a": 1 }, + { "px": [192,216], "src": [224,128], "f": 0, "t": 540, "d": [1320], "a": 1 }, + { "px": [200,216], "src": [224,128], "f": 0, "t": 540, "d": [1321], "a": 1 }, + { "px": [208,216], "src": [224,128], "f": 0, "t": 540, "d": [1322], "a": 1 }, + { "px": [216,216], "src": [224,128], "f": 0, "t": 540, "d": [1323], "a": 1 }, + { "px": [224,216], "src": [224,128], "f": 0, "t": 540, "d": [1324], "a": 1 }, + { "px": [232,216], "src": [224,128], "f": 0, "t": 540, "d": [1325], "a": 1 }, + { "px": [240,216], "src": [224,128], "f": 0, "t": 540, "d": [1326], "a": 1 }, + { "px": [248,216], "src": [224,128], "f": 0, "t": 540, "d": [1327], "a": 1 }, + { "px": [256,216], "src": [224,128], "f": 0, "t": 540, "d": [1328], "a": 1 }, + { "px": [264,216], "src": [224,128], "f": 0, "t": 540, "d": [1329], "a": 1 }, + { "px": [272,216], "src": [224,128], "f": 0, "t": 540, "d": [1330], "a": 1 }, + { "px": [280,216], "src": [224,128], "f": 0, "t": 540, "d": [1331], "a": 1 }, + { "px": [288,216], "src": [224,128], "f": 0, "t": 540, "d": [1332], "a": 1 }, + { "px": [296,216], "src": [224,128], "f": 0, "t": 540, "d": [1333], "a": 1 }, + { "px": [304,216], "src": [224,128], "f": 0, "t": 540, "d": [1334], "a": 1 }, + { "px": [312,216], "src": [224,128], "f": 0, "t": 540, "d": [1335], "a": 1 }, + { "px": [320,216], "src": [224,128], "f": 0, "t": 540, "d": [1336], "a": 1 }, + { "px": [328,216], "src": [224,128], "f": 0, "t": 540, "d": [1337], "a": 1 }, + { "px": [336,216], "src": [224,128], "f": 0, "t": 540, "d": [1338], "a": 1 }, + { "px": [344,216], "src": [224,128], "f": 0, "t": 540, "d": [1339], "a": 1 }, + { "px": [352,216], "src": [224,128], "f": 0, "t": 540, "d": [1340], "a": 1 }, + { "px": [360,216], "src": [224,128], "f": 0, "t": 540, "d": [1341], "a": 1 }, + { "px": [368,216], "src": [224,128], "f": 0, "t": 540, "d": [1342], "a": 1 }, + { "px": [376,216], "src": [232,128], "f": 0, "t": 541, "d": [1343], "a": 1 }, + { "px": [0,224], "src": [216,128], "f": 0, "t": 539, "d": [1344], "a": 1 }, + { "px": [8,224], "src": [224,128], "f": 0, "t": 540, "d": [1345], "a": 1 }, + { "px": [16,224], "src": [224,128], "f": 0, "t": 540, "d": [1346], "a": 1 }, + { "px": [24,224], "src": [224,128], "f": 0, "t": 540, "d": [1347], "a": 1 }, + { "px": [32,224], "src": [224,128], "f": 0, "t": 540, "d": [1348], "a": 1 }, + { "px": [40,224], "src": [224,128], "f": 0, "t": 540, "d": [1349], "a": 1 }, + { "px": [48,224], "src": [224,128], "f": 0, "t": 540, "d": [1350], "a": 1 }, + { "px": [56,224], "src": [224,128], "f": 0, "t": 540, "d": [1351], "a": 1 }, + { "px": [64,224], "src": [224,128], "f": 0, "t": 540, "d": [1352], "a": 1 }, + { "px": [72,224], "src": [224,128], "f": 0, "t": 540, "d": [1353], "a": 1 }, + { "px": [80,224], "src": [224,128], "f": 0, "t": 540, "d": [1354], "a": 1 }, + { "px": [88,224], "src": [224,128], "f": 0, "t": 540, "d": [1355], "a": 1 }, + { "px": [96,224], "src": [224,128], "f": 0, "t": 540, "d": [1356], "a": 1 }, + { "px": [104,224], "src": [224,128], "f": 0, "t": 540, "d": [1357], "a": 1 }, + { "px": [112,224], "src": [224,128], "f": 0, "t": 540, "d": [1358], "a": 1 }, + { "px": [120,224], "src": [224,128], "f": 0, "t": 540, "d": [1359], "a": 1 }, + { "px": [128,224], "src": [224,128], "f": 0, "t": 540, "d": [1360], "a": 1 }, + { "px": [136,224], "src": [224,128], "f": 0, "t": 540, "d": [1361], "a": 1 }, + { "px": [144,224], "src": [224,128], "f": 0, "t": 540, "d": [1362], "a": 1 }, + { "px": [152,224], "src": [224,128], "f": 0, "t": 540, "d": [1363], "a": 1 }, + { "px": [160,224], "src": [224,128], "f": 0, "t": 540, "d": [1364], "a": 1 }, + { "px": [168,224], "src": [224,128], "f": 0, "t": 540, "d": [1365], "a": 1 }, + { "px": [176,224], "src": [224,128], "f": 0, "t": 540, "d": [1366], "a": 1 }, + { "px": [184,224], "src": [224,128], "f": 0, "t": 540, "d": [1367], "a": 1 }, + { "px": [192,224], "src": [224,128], "f": 0, "t": 540, "d": [1368], "a": 1 }, + { "px": [200,224], "src": [224,128], "f": 0, "t": 540, "d": [1369], "a": 1 }, + { "px": [208,224], "src": [224,128], "f": 0, "t": 540, "d": [1370], "a": 1 }, + { "px": [216,224], "src": [224,128], "f": 0, "t": 540, "d": [1371], "a": 1 }, + { "px": [224,224], "src": [224,128], "f": 0, "t": 540, "d": [1372], "a": 1 }, + { "px": [232,224], "src": [224,128], "f": 0, "t": 540, "d": [1373], "a": 1 }, + { "px": [240,224], "src": [224,128], "f": 0, "t": 540, "d": [1374], "a": 1 }, + { "px": [248,224], "src": [224,128], "f": 0, "t": 540, "d": [1375], "a": 1 }, + { "px": [256,224], "src": [224,128], "f": 0, "t": 540, "d": [1376], "a": 1 }, + { "px": [264,224], "src": [224,128], "f": 0, "t": 540, "d": [1377], "a": 1 }, + { "px": [272,224], "src": [224,128], "f": 0, "t": 540, "d": [1378], "a": 1 }, + { "px": [280,224], "src": [224,128], "f": 0, "t": 540, "d": [1379], "a": 1 }, + { "px": [288,224], "src": [224,128], "f": 0, "t": 540, "d": [1380], "a": 1 }, + { "px": [296,224], "src": [224,128], "f": 0, "t": 540, "d": [1381], "a": 1 }, + { "px": [304,224], "src": [224,128], "f": 0, "t": 540, "d": [1382], "a": 1 }, + { "px": [312,224], "src": [224,128], "f": 0, "t": 540, "d": [1383], "a": 1 }, + { "px": [320,224], "src": [224,128], "f": 0, "t": 540, "d": [1384], "a": 1 }, + { "px": [328,224], "src": [224,128], "f": 0, "t": 540, "d": [1385], "a": 1 }, + { "px": [336,224], "src": [224,128], "f": 0, "t": 540, "d": [1386], "a": 1 }, + { "px": [344,224], "src": [224,128], "f": 0, "t": 540, "d": [1387], "a": 1 }, + { "px": [352,224], "src": [224,128], "f": 0, "t": 540, "d": [1388], "a": 1 }, + { "px": [360,224], "src": [224,128], "f": 0, "t": 540, "d": [1389], "a": 1 }, + { "px": [368,224], "src": [224,128], "f": 0, "t": 540, "d": [1390], "a": 1 }, + { "px": [376,224], "src": [232,128], "f": 0, "t": 541, "d": [1391], "a": 1 }, + { "px": [0,232], "src": [216,128], "f": 0, "t": 539, "d": [1392], "a": 1 }, + { "px": [8,232], "src": [224,128], "f": 0, "t": 540, "d": [1393], "a": 1 }, + { "px": [16,232], "src": [224,128], "f": 0, "t": 540, "d": [1394], "a": 1 }, + { "px": [24,232], "src": [224,128], "f": 0, "t": 540, "d": [1395], "a": 1 }, + { "px": [32,232], "src": [224,128], "f": 0, "t": 540, "d": [1396], "a": 1 }, + { "px": [40,232], "src": [224,128], "f": 0, "t": 540, "d": [1397], "a": 1 }, + { "px": [48,232], "src": [224,128], "f": 0, "t": 540, "d": [1398], "a": 1 }, + { "px": [56,232], "src": [224,128], "f": 0, "t": 540, "d": [1399], "a": 1 }, + { "px": [64,232], "src": [224,128], "f": 0, "t": 540, "d": [1400], "a": 1 }, + { "px": [72,232], "src": [224,128], "f": 0, "t": 540, "d": [1401], "a": 1 }, + { "px": [80,232], "src": [224,128], "f": 0, "t": 540, "d": [1402], "a": 1 }, + { "px": [88,232], "src": [224,128], "f": 0, "t": 540, "d": [1403], "a": 1 }, + { "px": [96,232], "src": [224,128], "f": 0, "t": 540, "d": [1404], "a": 1 }, + { "px": [104,232], "src": [224,128], "f": 0, "t": 540, "d": [1405], "a": 1 }, + { "px": [112,232], "src": [224,128], "f": 0, "t": 540, "d": [1406], "a": 1 }, + { "px": [120,232], "src": [224,128], "f": 0, "t": 540, "d": [1407], "a": 1 }, + { "px": [128,232], "src": [224,128], "f": 0, "t": 540, "d": [1408], "a": 1 }, + { "px": [136,232], "src": [224,128], "f": 0, "t": 540, "d": [1409], "a": 1 }, + { "px": [144,232], "src": [224,128], "f": 0, "t": 540, "d": [1410], "a": 1 }, + { "px": [152,232], "src": [224,128], "f": 0, "t": 540, "d": [1411], "a": 1 }, + { "px": [160,232], "src": [224,128], "f": 0, "t": 540, "d": [1412], "a": 1 }, + { "px": [168,232], "src": [224,128], "f": 0, "t": 540, "d": [1413], "a": 1 }, + { "px": [176,232], "src": [224,128], "f": 0, "t": 540, "d": [1414], "a": 1 }, + { "px": [184,232], "src": [224,128], "f": 0, "t": 540, "d": [1415], "a": 1 }, + { "px": [192,232], "src": [224,128], "f": 0, "t": 540, "d": [1416], "a": 1 }, + { "px": [200,232], "src": [224,128], "f": 0, "t": 540, "d": [1417], "a": 1 }, + { "px": [208,232], "src": [224,128], "f": 0, "t": 540, "d": [1418], "a": 1 }, + { "px": [216,232], "src": [224,128], "f": 0, "t": 540, "d": [1419], "a": 1 }, + { "px": [224,232], "src": [224,128], "f": 0, "t": 540, "d": [1420], "a": 1 }, + { "px": [232,232], "src": [224,128], "f": 0, "t": 540, "d": [1421], "a": 1 }, + { "px": [240,232], "src": [224,128], "f": 0, "t": 540, "d": [1422], "a": 1 }, + { "px": [248,232], "src": [224,128], "f": 0, "t": 540, "d": [1423], "a": 1 }, + { "px": [256,232], "src": [224,128], "f": 0, "t": 540, "d": [1424], "a": 1 }, + { "px": [264,232], "src": [224,128], "f": 0, "t": 540, "d": [1425], "a": 1 }, + { "px": [272,232], "src": [224,128], "f": 0, "t": 540, "d": [1426], "a": 1 }, + { "px": [280,232], "src": [224,128], "f": 0, "t": 540, "d": [1427], "a": 1 }, + { "px": [288,232], "src": [224,128], "f": 0, "t": 540, "d": [1428], "a": 1 }, + { "px": [296,232], "src": [224,128], "f": 0, "t": 540, "d": [1429], "a": 1 }, + { "px": [304,232], "src": [224,128], "f": 0, "t": 540, "d": [1430], "a": 1 }, + { "px": [312,232], "src": [224,128], "f": 0, "t": 540, "d": [1431], "a": 1 }, + { "px": [320,232], "src": [224,128], "f": 0, "t": 540, "d": [1432], "a": 1 }, + { "px": [328,232], "src": [224,128], "f": 0, "t": 540, "d": [1433], "a": 1 }, + { "px": [336,232], "src": [224,128], "f": 0, "t": 540, "d": [1434], "a": 1 }, + { "px": [344,232], "src": [224,128], "f": 0, "t": 540, "d": [1435], "a": 1 }, + { "px": [352,232], "src": [224,128], "f": 0, "t": 540, "d": [1436], "a": 1 }, + { "px": [360,232], "src": [224,128], "f": 0, "t": 540, "d": [1437], "a": 1 }, + { "px": [368,232], "src": [224,128], "f": 0, "t": 540, "d": [1438], "a": 1 }, + { "px": [376,232], "src": [232,128], "f": 0, "t": 541, "d": [1439], "a": 1 }, + { "px": [0,240], "src": [216,128], "f": 0, "t": 539, "d": [1440], "a": 1 }, + { "px": [8,240], "src": [224,128], "f": 0, "t": 540, "d": [1441], "a": 1 }, + { "px": [16,240], "src": [224,128], "f": 0, "t": 540, "d": [1442], "a": 1 }, + { "px": [24,240], "src": [224,128], "f": 0, "t": 540, "d": [1443], "a": 1 }, + { "px": [32,240], "src": [224,128], "f": 0, "t": 540, "d": [1444], "a": 1 }, + { "px": [40,240], "src": [224,128], "f": 0, "t": 540, "d": [1445], "a": 1 }, + { "px": [48,240], "src": [224,128], "f": 0, "t": 540, "d": [1446], "a": 1 }, + { "px": [56,240], "src": [224,128], "f": 0, "t": 540, "d": [1447], "a": 1 }, + { "px": [64,240], "src": [224,128], "f": 0, "t": 540, "d": [1448], "a": 1 }, + { "px": [72,240], "src": [224,128], "f": 0, "t": 540, "d": [1449], "a": 1 }, + { "px": [80,240], "src": [224,128], "f": 0, "t": 540, "d": [1450], "a": 1 }, + { "px": [88,240], "src": [224,128], "f": 0, "t": 540, "d": [1451], "a": 1 }, + { "px": [96,240], "src": [224,128], "f": 0, "t": 540, "d": [1452], "a": 1 }, + { "px": [104,240], "src": [224,128], "f": 0, "t": 540, "d": [1453], "a": 1 }, + { "px": [112,240], "src": [224,128], "f": 0, "t": 540, "d": [1454], "a": 1 }, + { "px": [120,240], "src": [224,128], "f": 0, "t": 540, "d": [1455], "a": 1 }, + { "px": [128,240], "src": [224,128], "f": 0, "t": 540, "d": [1456], "a": 1 }, + { "px": [136,240], "src": [224,128], "f": 0, "t": 540, "d": [1457], "a": 1 }, + { "px": [144,240], "src": [224,128], "f": 0, "t": 540, "d": [1458], "a": 1 }, + { "px": [152,240], "src": [224,128], "f": 0, "t": 540, "d": [1459], "a": 1 }, + { "px": [160,240], "src": [224,128], "f": 0, "t": 540, "d": [1460], "a": 1 }, + { "px": [168,240], "src": [224,128], "f": 0, "t": 540, "d": [1461], "a": 1 }, + { "px": [176,240], "src": [224,128], "f": 0, "t": 540, "d": [1462], "a": 1 }, + { "px": [184,240], "src": [224,128], "f": 0, "t": 540, "d": [1463], "a": 1 }, + { "px": [192,240], "src": [224,128], "f": 0, "t": 540, "d": [1464], "a": 1 }, + { "px": [200,240], "src": [224,128], "f": 0, "t": 540, "d": [1465], "a": 1 }, + { "px": [208,240], "src": [224,128], "f": 0, "t": 540, "d": [1466], "a": 1 }, + { "px": [216,240], "src": [224,128], "f": 0, "t": 540, "d": [1467], "a": 1 }, + { "px": [224,240], "src": [224,128], "f": 0, "t": 540, "d": [1468], "a": 1 }, + { "px": [232,240], "src": [224,128], "f": 0, "t": 540, "d": [1469], "a": 1 }, + { "px": [240,240], "src": [224,128], "f": 0, "t": 540, "d": [1470], "a": 1 }, + { "px": [248,240], "src": [224,128], "f": 0, "t": 540, "d": [1471], "a": 1 }, + { "px": [256,240], "src": [224,128], "f": 0, "t": 540, "d": [1472], "a": 1 }, + { "px": [264,240], "src": [224,128], "f": 0, "t": 540, "d": [1473], "a": 1 }, + { "px": [272,240], "src": [224,128], "f": 0, "t": 540, "d": [1474], "a": 1 }, + { "px": [280,240], "src": [224,128], "f": 0, "t": 540, "d": [1475], "a": 1 }, + { "px": [288,240], "src": [224,128], "f": 0, "t": 540, "d": [1476], "a": 1 }, + { "px": [296,240], "src": [224,128], "f": 0, "t": 540, "d": [1477], "a": 1 }, + { "px": [304,240], "src": [224,128], "f": 0, "t": 540, "d": [1478], "a": 1 }, + { "px": [312,240], "src": [224,128], "f": 0, "t": 540, "d": [1479], "a": 1 }, + { "px": [320,240], "src": [224,128], "f": 0, "t": 540, "d": [1480], "a": 1 }, + { "px": [328,240], "src": [224,128], "f": 0, "t": 540, "d": [1481], "a": 1 }, + { "px": [336,240], "src": [224,128], "f": 0, "t": 540, "d": [1482], "a": 1 }, + { "px": [344,240], "src": [224,128], "f": 0, "t": 540, "d": [1483], "a": 1 }, + { "px": [352,240], "src": [224,128], "f": 0, "t": 540, "d": [1484], "a": 1 }, + { "px": [360,240], "src": [224,128], "f": 0, "t": 540, "d": [1485], "a": 1 }, + { "px": [368,240], "src": [224,128], "f": 0, "t": 540, "d": [1486], "a": 1 }, + { "px": [376,240], "src": [232,128], "f": 0, "t": 541, "d": [1487], "a": 1 }, + { "px": [0,248], "src": [216,136], "f": 0, "t": 571, "d": [1488], "a": 1 }, + { "px": [8,248], "src": [224,136], "f": 0, "t": 572, "d": [1489], "a": 1 }, + { "px": [16,248], "src": [224,136], "f": 0, "t": 572, "d": [1490], "a": 1 }, + { "px": [24,248], "src": [224,136], "f": 0, "t": 572, "d": [1491], "a": 1 }, + { "px": [32,248], "src": [224,136], "f": 0, "t": 572, "d": [1492], "a": 1 }, + { "px": [40,248], "src": [224,136], "f": 0, "t": 572, "d": [1493], "a": 1 }, + { "px": [48,248], "src": [224,136], "f": 0, "t": 572, "d": [1494], "a": 1 }, + { "px": [56,248], "src": [224,136], "f": 0, "t": 572, "d": [1495], "a": 1 }, + { "px": [64,248], "src": [224,136], "f": 0, "t": 572, "d": [1496], "a": 1 }, + { "px": [72,248], "src": [224,136], "f": 0, "t": 572, "d": [1497], "a": 1 }, + { "px": [80,248], "src": [224,136], "f": 0, "t": 572, "d": [1498], "a": 1 }, + { "px": [88,248], "src": [224,136], "f": 0, "t": 572, "d": [1499], "a": 1 }, + { "px": [96,248], "src": [224,136], "f": 0, "t": 572, "d": [1500], "a": 1 }, + { "px": [104,248], "src": [224,136], "f": 0, "t": 572, "d": [1501], "a": 1 }, + { "px": [112,248], "src": [224,136], "f": 0, "t": 572, "d": [1502], "a": 1 }, + { "px": [120,248], "src": [224,136], "f": 0, "t": 572, "d": [1503], "a": 1 }, + { "px": [128,248], "src": [224,136], "f": 0, "t": 572, "d": [1504], "a": 1 }, + { "px": [136,248], "src": [224,136], "f": 0, "t": 572, "d": [1505], "a": 1 }, + { "px": [144,248], "src": [224,136], "f": 0, "t": 572, "d": [1506], "a": 1 }, + { "px": [152,248], "src": [224,136], "f": 0, "t": 572, "d": [1507], "a": 1 }, + { "px": [160,248], "src": [224,136], "f": 0, "t": 572, "d": [1508], "a": 1 }, + { "px": [168,248], "src": [224,136], "f": 0, "t": 572, "d": [1509], "a": 1 }, + { "px": [176,248], "src": [224,136], "f": 0, "t": 572, "d": [1510], "a": 1 }, + { "px": [184,248], "src": [224,136], "f": 0, "t": 572, "d": [1511], "a": 1 }, + { "px": [192,248], "src": [224,136], "f": 0, "t": 572, "d": [1512], "a": 1 }, + { "px": [200,248], "src": [224,136], "f": 0, "t": 572, "d": [1513], "a": 1 }, + { "px": [208,248], "src": [224,136], "f": 0, "t": 572, "d": [1514], "a": 1 }, + { "px": [216,248], "src": [224,136], "f": 0, "t": 572, "d": [1515], "a": 1 }, + { "px": [224,248], "src": [224,136], "f": 0, "t": 572, "d": [1516], "a": 1 }, + { "px": [232,248], "src": [224,136], "f": 0, "t": 572, "d": [1517], "a": 1 }, + { "px": [240,248], "src": [224,136], "f": 0, "t": 572, "d": [1518], "a": 1 }, + { "px": [248,248], "src": [224,136], "f": 0, "t": 572, "d": [1519], "a": 1 }, + { "px": [256,248], "src": [224,136], "f": 0, "t": 572, "d": [1520], "a": 1 }, + { "px": [264,248], "src": [224,136], "f": 0, "t": 572, "d": [1521], "a": 1 }, + { "px": [272,248], "src": [224,136], "f": 0, "t": 572, "d": [1522], "a": 1 }, + { "px": [280,248], "src": [224,136], "f": 0, "t": 572, "d": [1523], "a": 1 }, + { "px": [288,248], "src": [224,136], "f": 0, "t": 572, "d": [1524], "a": 1 }, + { "px": [296,248], "src": [224,136], "f": 0, "t": 572, "d": [1525], "a": 1 }, + { "px": [304,248], "src": [224,136], "f": 0, "t": 572, "d": [1526], "a": 1 }, + { "px": [312,248], "src": [224,136], "f": 0, "t": 572, "d": [1527], "a": 1 }, + { "px": [320,248], "src": [224,136], "f": 0, "t": 572, "d": [1528], "a": 1 }, + { "px": [328,248], "src": [224,136], "f": 0, "t": 572, "d": [1529], "a": 1 }, + { "px": [336,248], "src": [224,136], "f": 0, "t": 572, "d": [1530], "a": 1 }, + { "px": [344,248], "src": [224,136], "f": 0, "t": 572, "d": [1531], "a": 1 }, + { "px": [352,248], "src": [224,136], "f": 0, "t": 572, "d": [1532], "a": 1 }, + { "px": [360,248], "src": [224,136], "f": 0, "t": 572, "d": [1533], "a": 1 }, + { "px": [368,248], "src": [224,136], "f": 0, "t": 572, "d": [1534], "a": 1 }, + { "px": [376,248], "src": [232,136], "f": 0, "t": 573, "d": [1535], "a": 1 } + ], + "entityInstances": [] + } + ], + "__neighbours": [] + }, + { + "identifier": "NameModal", + "iid": "8acb9730-fa90-11f0-ad09-2f4bba102db8", + "uid": 11, + "worldX": 96, + "worldY": -200, + "worldDepth": 0, + "pxWid": 192, + "pxHei": 128, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 24, + "__cHei": 16, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "8acbbe40-fa90-11f0-ad09-65bd858241d9", + "levelId": 11, + "layerDefUid": 6, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 1654665, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "TextInput", + "__grid": [2,6], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": null, + "__smartColor": "#EAD4AA", + "iid": "137c9520-fa90-11f0-8348-818c79b196af", + "width": 160, + "height": 20, + "defUid": 3, + "px": [16,48], + "fieldInstances": [], + "__worldX": 112, + "__worldY": -152 + }, + { + "__identifier": "TextButton", + "__grid": [3,13], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "28908c50-fa90-11f0-8348-6bf2a348c46c", + "width": 136, + "height": 16, + "defUid": 78, + "px": [24,104], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "Update the name", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["Update the name"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ false ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Green", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Green"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": "Validate", "__tile": null, "defUid": 84, "realEditorValues": [{ + "id": "V_String", + "params": ["Validate"] + }] } + ], + "__worldX": 120, + "__worldY": -96 + } + ] + }, + { + "__identifier": "Panels", + "__type": "Entities", + "__cWid": 24, + "__cHei": 16, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "812bd5d0-fa90-11f0-ade1-5bcb18c16f90", + "levelId": 11, + "layerDefUid": 75, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 9080514, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 24, + "__cHei": 16, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 72, + "__tilesetRelPath": "sprite-sheet.png", + "iid": "8acbbe41-fa90-11f0-ad09-ab3f47e184ef", + "levelId": 11, + "layerDefUid": 10, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 8572808, + "overrideTilesetUid": null, + "gridTiles": [ + { "px": [0,0], "src": [216,104], "f": 0, "t": 443, "d": [0], "a": 1 }, + { "px": [8,0], "src": [224,104], "f": 0, "t": 444, "d": [1], "a": 1 }, + { "px": [16,0], "src": [224,104], "f": 0, "t": 444, "d": [2], "a": 1 }, + { "px": [24,0], "src": [224,104], "f": 0, "t": 444, "d": [3], "a": 1 }, + { "px": [32,0], "src": [224,104], "f": 0, "t": 444, "d": [4], "a": 1 }, + { "px": [40,0], "src": [224,104], "f": 0, "t": 444, "d": [5], "a": 1 }, + { "px": [48,0], "src": [224,104], "f": 0, "t": 444, "d": [6], "a": 1 }, + { "px": [56,0], "src": [224,104], "f": 0, "t": 444, "d": [7], "a": 1 }, + { "px": [64,0], "src": [224,104], "f": 0, "t": 444, "d": [8], "a": 1 }, + { "px": [72,0], "src": [224,104], "f": 0, "t": 444, "d": [9], "a": 1 }, + { "px": [80,0], "src": [224,104], "f": 0, "t": 444, "d": [10], "a": 1 }, + { "px": [88,0], "src": [224,104], "f": 0, "t": 444, "d": [11], "a": 1 }, + { "px": [96,0], "src": [224,104], "f": 0, "t": 444, "d": [12], "a": 1 }, + { "px": [104,0], "src": [224,104], "f": 0, "t": 444, "d": [13], "a": 1 }, + { "px": [112,0], "src": [224,104], "f": 0, "t": 444, "d": [14], "a": 1 }, + { "px": [120,0], "src": [224,104], "f": 0, "t": 444, "d": [15], "a": 1 }, + { "px": [128,0], "src": [224,104], "f": 0, "t": 444, "d": [16], "a": 1 }, + { "px": [136,0], "src": [224,104], "f": 0, "t": 444, "d": [17], "a": 1 }, + { "px": [144,0], "src": [224,104], "f": 0, "t": 444, "d": [18], "a": 1 }, + { "px": [152,0], "src": [224,104], "f": 0, "t": 444, "d": [19], "a": 1 }, + { "px": [160,0], "src": [224,104], "f": 0, "t": 444, "d": [20], "a": 1 }, + { "px": [168,0], "src": [224,104], "f": 0, "t": 444, "d": [21], "a": 1 }, + { "px": [176,0], "src": [224,104], "f": 0, "t": 444, "d": [22], "a": 1 }, + { "px": [184,0], "src": [216,104], "f": 1, "t": 443, "d": [23], "a": 1 }, + { "px": [0,8], "src": [216,112], "f": 0, "t": 475, "d": [24], "a": 1 }, + { "px": [8,8], "src": [224,112], "f": 0, "t": 476, "d": [25], "a": 1 }, + { "px": [16,8], "src": [224,112], "f": 0, "t": 476, "d": [26], "a": 1 }, + { "px": [24,8], "src": [224,112], "f": 0, "t": 476, "d": [27], "a": 1 }, + { "px": [32,8], "src": [224,112], "f": 0, "t": 476, "d": [28], "a": 1 }, + { "px": [40,8], "src": [224,112], "f": 0, "t": 476, "d": [29], "a": 1 }, + { "px": [48,8], "src": [224,112], "f": 0, "t": 476, "d": [30], "a": 1 }, + { "px": [56,8], "src": [224,112], "f": 0, "t": 476, "d": [31], "a": 1 }, + { "px": [64,8], "src": [224,112], "f": 0, "t": 476, "d": [32], "a": 1 }, + { "px": [72,8], "src": [224,112], "f": 0, "t": 476, "d": [33], "a": 1 }, + { "px": [80,8], "src": [224,112], "f": 0, "t": 476, "d": [34], "a": 1 }, + { "px": [88,8], "src": [224,112], "f": 0, "t": 476, "d": [35], "a": 1 }, + { "px": [96,8], "src": [224,112], "f": 0, "t": 476, "d": [36], "a": 1 }, + { "px": [104,8], "src": [224,112], "f": 0, "t": 476, "d": [37], "a": 1 }, + { "px": [112,8], "src": [224,112], "f": 0, "t": 476, "d": [38], "a": 1 }, + { "px": [120,8], "src": [224,112], "f": 0, "t": 476, "d": [39], "a": 1 }, + { "px": [128,8], "src": [224,112], "f": 0, "t": 476, "d": [40], "a": 1 }, + { "px": [136,8], "src": [224,112], "f": 0, "t": 476, "d": [41], "a": 1 }, + { "px": [144,8], "src": [224,112], "f": 0, "t": 476, "d": [42], "a": 1 }, + { "px": [152,8], "src": [224,112], "f": 0, "t": 476, "d": [43], "a": 1 }, + { "px": [160,8], "src": [224,112], "f": 0, "t": 476, "d": [44], "a": 1 }, + { "px": [168,8], "src": [224,112], "f": 0, "t": 476, "d": [45], "a": 1 }, + { "px": [176,8], "src": [224,112], "f": 0, "t": 476, "d": [46], "a": 1 }, + { "px": [184,8], "src": [232,112], "f": 0, "t": 477, "d": [47], "a": 1 }, + { "px": [0,16], "src": [216,112], "f": 0, "t": 475, "d": [48], "a": 1 }, + { "px": [8,16], "src": [224,112], "f": 0, "t": 476, "d": [49], "a": 1 }, + { "px": [16,16], "src": [224,112], "f": 0, "t": 476, "d": [50], "a": 1 }, + { "px": [24,16], "src": [224,112], "f": 0, "t": 476, "d": [51], "a": 1 }, + { "px": [32,16], "src": [224,112], "f": 0, "t": 476, "d": [52], "a": 1 }, + { "px": [40,16], "src": [224,112], "f": 0, "t": 476, "d": [53], "a": 1 }, + { "px": [48,16], "src": [224,112], "f": 0, "t": 476, "d": [54], "a": 1 }, + { "px": [56,16], "src": [224,112], "f": 0, "t": 476, "d": [55], "a": 1 }, + { "px": [64,16], "src": [224,112], "f": 0, "t": 476, "d": [56], "a": 1 }, + { "px": [72,16], "src": [224,112], "f": 0, "t": 476, "d": [57], "a": 1 }, + { "px": [80,16], "src": [224,112], "f": 0, "t": 476, "d": [58], "a": 1 }, + { "px": [88,16], "src": [224,112], "f": 0, "t": 476, "d": [59], "a": 1 }, + { "px": [96,16], "src": [224,112], "f": 0, "t": 476, "d": [60], "a": 1 }, + { "px": [104,16], "src": [224,112], "f": 0, "t": 476, "d": [61], "a": 1 }, + { "px": [112,16], "src": [224,112], "f": 0, "t": 476, "d": [62], "a": 1 }, + { "px": [120,16], "src": [224,112], "f": 0, "t": 476, "d": [63], "a": 1 }, + { "px": [128,16], "src": [224,112], "f": 0, "t": 476, "d": [64], "a": 1 }, + { "px": [136,16], "src": [224,112], "f": 0, "t": 476, "d": [65], "a": 1 }, + { "px": [144,16], "src": [224,112], "f": 0, "t": 476, "d": [66], "a": 1 }, + { "px": [152,16], "src": [224,112], "f": 0, "t": 476, "d": [67], "a": 1 }, + { "px": [160,16], "src": [224,112], "f": 0, "t": 476, "d": [68], "a": 1 }, + { "px": [168,16], "src": [224,112], "f": 0, "t": 476, "d": [69], "a": 1 }, + { "px": [176,16], "src": [224,112], "f": 0, "t": 476, "d": [70], "a": 1 }, + { "px": [184,16], "src": [232,112], "f": 0, "t": 477, "d": [71], "a": 1 }, + { "px": [0,24], "src": [216,112], "f": 0, "t": 475, "d": [72], "a": 1 }, + { "px": [8,24], "src": [224,112], "f": 0, "t": 476, "d": [73], "a": 1 }, + { "px": [16,24], "src": [224,112], "f": 0, "t": 476, "d": [74], "a": 1 }, + { "px": [24,24], "src": [224,112], "f": 0, "t": 476, "d": [75], "a": 1 }, + { "px": [32,24], "src": [224,112], "f": 0, "t": 476, "d": [76], "a": 1 }, + { "px": [40,24], "src": [224,112], "f": 0, "t": 476, "d": [77], "a": 1 }, + { "px": [48,24], "src": [224,112], "f": 0, "t": 476, "d": [78], "a": 1 }, + { "px": [56,24], "src": [224,112], "f": 0, "t": 476, "d": [79], "a": 1 }, + { "px": [64,24], "src": [224,112], "f": 0, "t": 476, "d": [80], "a": 1 }, + { "px": [72,24], "src": [224,112], "f": 0, "t": 476, "d": [81], "a": 1 }, + { "px": [80,24], "src": [224,112], "f": 0, "t": 476, "d": [82], "a": 1 }, + { "px": [88,24], "src": [224,112], "f": 0, "t": 476, "d": [83], "a": 1 }, + { "px": [96,24], "src": [224,112], "f": 0, "t": 476, "d": [84], "a": 1 }, + { "px": [104,24], "src": [224,112], "f": 0, "t": 476, "d": [85], "a": 1 }, + { "px": [112,24], "src": [224,112], "f": 0, "t": 476, "d": [86], "a": 1 }, + { "px": [120,24], "src": [224,112], "f": 0, "t": 476, "d": [87], "a": 1 }, + { "px": [128,24], "src": [224,112], "f": 0, "t": 476, "d": [88], "a": 1 }, + { "px": [136,24], "src": [224,112], "f": 0, "t": 476, "d": [89], "a": 1 }, + { "px": [144,24], "src": [224,112], "f": 0, "t": 476, "d": [90], "a": 1 }, + { "px": [152,24], "src": [224,112], "f": 0, "t": 476, "d": [91], "a": 1 }, + { "px": [160,24], "src": [224,112], "f": 0, "t": 476, "d": [92], "a": 1 }, + { "px": [168,24], "src": [224,112], "f": 0, "t": 476, "d": [93], "a": 1 }, + { "px": [176,24], "src": [224,112], "f": 0, "t": 476, "d": [94], "a": 1 }, + { "px": [184,24], "src": [232,112], "f": 0, "t": 477, "d": [95], "a": 1 }, + { "px": [0,32], "src": [216,112], "f": 0, "t": 475, "d": [96], "a": 1 }, + { "px": [8,32], "src": [224,112], "f": 0, "t": 476, "d": [97], "a": 1 }, + { "px": [16,32], "src": [224,112], "f": 0, "t": 476, "d": [98], "a": 1 }, + { "px": [24,32], "src": [224,112], "f": 0, "t": 476, "d": [99], "a": 1 }, + { "px": [32,32], "src": [224,112], "f": 0, "t": 476, "d": [100], "a": 1 }, + { "px": [40,32], "src": [224,112], "f": 0, "t": 476, "d": [101], "a": 1 }, + { "px": [48,32], "src": [224,112], "f": 0, "t": 476, "d": [102], "a": 1 }, + { "px": [56,32], "src": [224,112], "f": 0, "t": 476, "d": [103], "a": 1 }, + { "px": [64,32], "src": [224,112], "f": 0, "t": 476, "d": [104], "a": 1 }, + { "px": [72,32], "src": [224,112], "f": 0, "t": 476, "d": [105], "a": 1 }, + { "px": [80,32], "src": [224,112], "f": 0, "t": 476, "d": [106], "a": 1 }, + { "px": [88,32], "src": [224,112], "f": 0, "t": 476, "d": [107], "a": 1 }, + { "px": [96,32], "src": [224,112], "f": 0, "t": 476, "d": [108], "a": 1 }, + { "px": [104,32], "src": [224,112], "f": 0, "t": 476, "d": [109], "a": 1 }, + { "px": [112,32], "src": [224,112], "f": 0, "t": 476, "d": [110], "a": 1 }, + { "px": [120,32], "src": [224,112], "f": 0, "t": 476, "d": [111], "a": 1 }, + { "px": [128,32], "src": [224,112], "f": 0, "t": 476, "d": [112], "a": 1 }, + { "px": [136,32], "src": [224,112], "f": 0, "t": 476, "d": [113], "a": 1 }, + { "px": [144,32], "src": [224,112], "f": 0, "t": 476, "d": [114], "a": 1 }, + { "px": [152,32], "src": [224,112], "f": 0, "t": 476, "d": [115], "a": 1 }, + { "px": [160,32], "src": [224,112], "f": 0, "t": 476, "d": [116], "a": 1 }, + { "px": [168,32], "src": [224,112], "f": 0, "t": 476, "d": [117], "a": 1 }, + { "px": [176,32], "src": [224,112], "f": 0, "t": 476, "d": [118], "a": 1 }, + { "px": [184,32], "src": [232,112], "f": 0, "t": 477, "d": [119], "a": 1 }, + { "px": [0,40], "src": [216,112], "f": 0, "t": 475, "d": [120], "a": 1 }, + { "px": [8,40], "src": [224,112], "f": 0, "t": 476, "d": [121], "a": 1 }, + { "px": [16,40], "src": [224,112], "f": 0, "t": 476, "d": [122], "a": 1 }, + { "px": [24,40], "src": [224,112], "f": 0, "t": 476, "d": [123], "a": 1 }, + { "px": [32,40], "src": [224,112], "f": 0, "t": 476, "d": [124], "a": 1 }, + { "px": [40,40], "src": [224,112], "f": 0, "t": 476, "d": [125], "a": 1 }, + { "px": [48,40], "src": [224,112], "f": 0, "t": 476, "d": [126], "a": 1 }, + { "px": [56,40], "src": [224,112], "f": 0, "t": 476, "d": [127], "a": 1 }, + { "px": [64,40], "src": [224,112], "f": 0, "t": 476, "d": [128], "a": 1 }, + { "px": [72,40], "src": [224,112], "f": 0, "t": 476, "d": [129], "a": 1 }, + { "px": [80,40], "src": [224,112], "f": 0, "t": 476, "d": [130], "a": 1 }, + { "px": [88,40], "src": [224,112], "f": 0, "t": 476, "d": [131], "a": 1 }, + { "px": [96,40], "src": [224,112], "f": 0, "t": 476, "d": [132], "a": 1 }, + { "px": [104,40], "src": [224,112], "f": 0, "t": 476, "d": [133], "a": 1 }, + { "px": [112,40], "src": [224,112], "f": 0, "t": 476, "d": [134], "a": 1 }, + { "px": [120,40], "src": [224,112], "f": 0, "t": 476, "d": [135], "a": 1 }, + { "px": [128,40], "src": [224,112], "f": 0, "t": 476, "d": [136], "a": 1 }, + { "px": [136,40], "src": [224,112], "f": 0, "t": 476, "d": [137], "a": 1 }, + { "px": [144,40], "src": [224,112], "f": 0, "t": 476, "d": [138], "a": 1 }, + { "px": [152,40], "src": [224,112], "f": 0, "t": 476, "d": [139], "a": 1 }, + { "px": [160,40], "src": [224,112], "f": 0, "t": 476, "d": [140], "a": 1 }, + { "px": [168,40], "src": [224,112], "f": 0, "t": 476, "d": [141], "a": 1 }, + { "px": [176,40], "src": [224,112], "f": 0, "t": 476, "d": [142], "a": 1 }, + { "px": [184,40], "src": [232,112], "f": 0, "t": 477, "d": [143], "a": 1 }, + { "px": [0,48], "src": [216,112], "f": 0, "t": 475, "d": [144], "a": 1 }, + { "px": [8,48], "src": [224,112], "f": 0, "t": 476, "d": [145], "a": 1 }, + { "px": [16,48], "src": [224,112], "f": 0, "t": 476, "d": [146], "a": 1 }, + { "px": [24,48], "src": [224,112], "f": 0, "t": 476, "d": [147], "a": 1 }, + { "px": [32,48], "src": [224,112], "f": 0, "t": 476, "d": [148], "a": 1 }, + { "px": [40,48], "src": [224,112], "f": 0, "t": 476, "d": [149], "a": 1 }, + { "px": [48,48], "src": [224,112], "f": 0, "t": 476, "d": [150], "a": 1 }, + { "px": [56,48], "src": [224,112], "f": 0, "t": 476, "d": [151], "a": 1 }, + { "px": [64,48], "src": [224,112], "f": 0, "t": 476, "d": [152], "a": 1 }, + { "px": [72,48], "src": [224,112], "f": 0, "t": 476, "d": [153], "a": 1 }, + { "px": [80,48], "src": [224,112], "f": 0, "t": 476, "d": [154], "a": 1 }, + { "px": [88,48], "src": [224,112], "f": 0, "t": 476, "d": [155], "a": 1 }, + { "px": [96,48], "src": [224,112], "f": 0, "t": 476, "d": [156], "a": 1 }, + { "px": [104,48], "src": [224,112], "f": 0, "t": 476, "d": [157], "a": 1 }, + { "px": [112,48], "src": [224,112], "f": 0, "t": 476, "d": [158], "a": 1 }, + { "px": [120,48], "src": [224,112], "f": 0, "t": 476, "d": [159], "a": 1 }, + { "px": [128,48], "src": [224,112], "f": 0, "t": 476, "d": [160], "a": 1 }, + { "px": [136,48], "src": [224,112], "f": 0, "t": 476, "d": [161], "a": 1 }, + { "px": [144,48], "src": [224,112], "f": 0, "t": 476, "d": [162], "a": 1 }, + { "px": [152,48], "src": [224,112], "f": 0, "t": 476, "d": [163], "a": 1 }, + { "px": [160,48], "src": [224,112], "f": 0, "t": 476, "d": [164], "a": 1 }, + { "px": [168,48], "src": [224,112], "f": 0, "t": 476, "d": [165], "a": 1 }, + { "px": [176,48], "src": [224,112], "f": 0, "t": 476, "d": [166], "a": 1 }, + { "px": [184,48], "src": [232,112], "f": 0, "t": 477, "d": [167], "a": 1 }, + { "px": [0,56], "src": [216,112], "f": 0, "t": 475, "d": [168], "a": 1 }, + { "px": [8,56], "src": [224,112], "f": 0, "t": 476, "d": [169], "a": 1 }, + { "px": [16,56], "src": [224,112], "f": 0, "t": 476, "d": [170], "a": 1 }, + { "px": [24,56], "src": [224,112], "f": 0, "t": 476, "d": [171], "a": 1 }, + { "px": [32,56], "src": [224,112], "f": 0, "t": 476, "d": [172], "a": 1 }, + { "px": [40,56], "src": [224,112], "f": 0, "t": 476, "d": [173], "a": 1 }, + { "px": [48,56], "src": [224,112], "f": 0, "t": 476, "d": [174], "a": 1 }, + { "px": [56,56], "src": [224,112], "f": 0, "t": 476, "d": [175], "a": 1 }, + { "px": [64,56], "src": [224,112], "f": 0, "t": 476, "d": [176], "a": 1 }, + { "px": [72,56], "src": [224,112], "f": 0, "t": 476, "d": [177], "a": 1 }, + { "px": [80,56], "src": [224,112], "f": 0, "t": 476, "d": [178], "a": 1 }, + { "px": [88,56], "src": [224,112], "f": 0, "t": 476, "d": [179], "a": 1 }, + { "px": [96,56], "src": [224,112], "f": 0, "t": 476, "d": [180], "a": 1 }, + { "px": [104,56], "src": [224,112], "f": 0, "t": 476, "d": [181], "a": 1 }, + { "px": [112,56], "src": [224,112], "f": 0, "t": 476, "d": [182], "a": 1 }, + { "px": [120,56], "src": [224,112], "f": 0, "t": 476, "d": [183], "a": 1 }, + { "px": [128,56], "src": [224,112], "f": 0, "t": 476, "d": [184], "a": 1 }, + { "px": [136,56], "src": [224,112], "f": 0, "t": 476, "d": [185], "a": 1 }, + { "px": [144,56], "src": [224,112], "f": 0, "t": 476, "d": [186], "a": 1 }, + { "px": [152,56], "src": [224,112], "f": 0, "t": 476, "d": [187], "a": 1 }, + { "px": [160,56], "src": [224,112], "f": 0, "t": 476, "d": [188], "a": 1 }, + { "px": [168,56], "src": [224,112], "f": 0, "t": 476, "d": [189], "a": 1 }, + { "px": [176,56], "src": [224,112], "f": 0, "t": 476, "d": [190], "a": 1 }, + { "px": [184,56], "src": [232,112], "f": 0, "t": 477, "d": [191], "a": 1 }, + { "px": [0,64], "src": [216,112], "f": 0, "t": 475, "d": [192], "a": 1 }, + { "px": [8,64], "src": [224,112], "f": 0, "t": 476, "d": [193], "a": 1 }, + { "px": [16,64], "src": [224,112], "f": 0, "t": 476, "d": [194], "a": 1 }, + { "px": [24,64], "src": [224,112], "f": 0, "t": 476, "d": [195], "a": 1 }, + { "px": [32,64], "src": [224,112], "f": 0, "t": 476, "d": [196], "a": 1 }, + { "px": [40,64], "src": [224,112], "f": 0, "t": 476, "d": [197], "a": 1 }, + { "px": [48,64], "src": [224,112], "f": 0, "t": 476, "d": [198], "a": 1 }, + { "px": [56,64], "src": [224,112], "f": 0, "t": 476, "d": [199], "a": 1 }, + { "px": [64,64], "src": [224,112], "f": 0, "t": 476, "d": [200], "a": 1 }, + { "px": [72,64], "src": [224,112], "f": 0, "t": 476, "d": [201], "a": 1 }, + { "px": [80,64], "src": [224,112], "f": 0, "t": 476, "d": [202], "a": 1 }, + { "px": [88,64], "src": [224,112], "f": 0, "t": 476, "d": [203], "a": 1 }, + { "px": [96,64], "src": [224,112], "f": 0, "t": 476, "d": [204], "a": 1 }, + { "px": [104,64], "src": [224,112], "f": 0, "t": 476, "d": [205], "a": 1 }, + { "px": [112,64], "src": [224,112], "f": 0, "t": 476, "d": [206], "a": 1 }, + { "px": [120,64], "src": [224,112], "f": 0, "t": 476, "d": [207], "a": 1 }, + { "px": [128,64], "src": [224,112], "f": 0, "t": 476, "d": [208], "a": 1 }, + { "px": [136,64], "src": [224,112], "f": 0, "t": 476, "d": [209], "a": 1 }, + { "px": [144,64], "src": [224,112], "f": 0, "t": 476, "d": [210], "a": 1 }, + { "px": [152,64], "src": [224,112], "f": 0, "t": 476, "d": [211], "a": 1 }, + { "px": [160,64], "src": [224,112], "f": 0, "t": 476, "d": [212], "a": 1 }, + { "px": [168,64], "src": [224,112], "f": 0, "t": 476, "d": [213], "a": 1 }, + { "px": [176,64], "src": [224,112], "f": 0, "t": 476, "d": [214], "a": 1 }, + { "px": [184,64], "src": [232,112], "f": 0, "t": 477, "d": [215], "a": 1 }, + { "px": [0,72], "src": [216,112], "f": 0, "t": 475, "d": [216], "a": 1 }, + { "px": [8,72], "src": [224,112], "f": 0, "t": 476, "d": [217], "a": 1 }, + { "px": [16,72], "src": [224,112], "f": 0, "t": 476, "d": [218], "a": 1 }, + { "px": [24,72], "src": [224,112], "f": 0, "t": 476, "d": [219], "a": 1 }, + { "px": [32,72], "src": [224,112], "f": 0, "t": 476, "d": [220], "a": 1 }, + { "px": [40,72], "src": [224,112], "f": 0, "t": 476, "d": [221], "a": 1 }, + { "px": [48,72], "src": [224,112], "f": 0, "t": 476, "d": [222], "a": 1 }, + { "px": [56,72], "src": [224,112], "f": 0, "t": 476, "d": [223], "a": 1 }, + { "px": [64,72], "src": [224,112], "f": 0, "t": 476, "d": [224], "a": 1 }, + { "px": [72,72], "src": [224,112], "f": 0, "t": 476, "d": [225], "a": 1 }, + { "px": [80,72], "src": [224,112], "f": 0, "t": 476, "d": [226], "a": 1 }, + { "px": [88,72], "src": [224,112], "f": 0, "t": 476, "d": [227], "a": 1 }, + { "px": [96,72], "src": [224,112], "f": 0, "t": 476, "d": [228], "a": 1 }, + { "px": [104,72], "src": [224,112], "f": 0, "t": 476, "d": [229], "a": 1 }, + { "px": [112,72], "src": [224,112], "f": 0, "t": 476, "d": [230], "a": 1 }, + { "px": [120,72], "src": [224,112], "f": 0, "t": 476, "d": [231], "a": 1 }, + { "px": [128,72], "src": [224,112], "f": 0, "t": 476, "d": [232], "a": 1 }, + { "px": [136,72], "src": [224,112], "f": 0, "t": 476, "d": [233], "a": 1 }, + { "px": [144,72], "src": [224,112], "f": 0, "t": 476, "d": [234], "a": 1 }, + { "px": [152,72], "src": [224,112], "f": 0, "t": 476, "d": [235], "a": 1 }, + { "px": [160,72], "src": [224,112], "f": 0, "t": 476, "d": [236], "a": 1 }, + { "px": [168,72], "src": [224,112], "f": 0, "t": 476, "d": [237], "a": 1 }, + { "px": [176,72], "src": [224,112], "f": 0, "t": 476, "d": [238], "a": 1 }, + { "px": [184,72], "src": [232,112], "f": 0, "t": 477, "d": [239], "a": 1 }, + { "px": [0,80], "src": [216,112], "f": 0, "t": 475, "d": [240], "a": 1 }, + { "px": [8,80], "src": [224,112], "f": 0, "t": 476, "d": [241], "a": 1 }, + { "px": [16,80], "src": [224,112], "f": 0, "t": 476, "d": [242], "a": 1 }, + { "px": [24,80], "src": [224,112], "f": 0, "t": 476, "d": [243], "a": 1 }, + { "px": [32,80], "src": [224,112], "f": 0, "t": 476, "d": [244], "a": 1 }, + { "px": [40,80], "src": [224,112], "f": 0, "t": 476, "d": [245], "a": 1 }, + { "px": [48,80], "src": [224,112], "f": 0, "t": 476, "d": [246], "a": 1 }, + { "px": [56,80], "src": [224,112], "f": 0, "t": 476, "d": [247], "a": 1 }, + { "px": [64,80], "src": [224,112], "f": 0, "t": 476, "d": [248], "a": 1 }, + { "px": [72,80], "src": [224,112], "f": 0, "t": 476, "d": [249], "a": 1 }, + { "px": [80,80], "src": [224,112], "f": 0, "t": 476, "d": [250], "a": 1 }, + { "px": [88,80], "src": [224,112], "f": 0, "t": 476, "d": [251], "a": 1 }, + { "px": [96,80], "src": [224,112], "f": 0, "t": 476, "d": [252], "a": 1 }, + { "px": [104,80], "src": [224,112], "f": 0, "t": 476, "d": [253], "a": 1 }, + { "px": [112,80], "src": [224,112], "f": 0, "t": 476, "d": [254], "a": 1 }, + { "px": [120,80], "src": [224,112], "f": 0, "t": 476, "d": [255], "a": 1 }, + { "px": [128,80], "src": [224,112], "f": 0, "t": 476, "d": [256], "a": 1 }, + { "px": [136,80], "src": [224,112], "f": 0, "t": 476, "d": [257], "a": 1 }, + { "px": [144,80], "src": [224,112], "f": 0, "t": 476, "d": [258], "a": 1 }, + { "px": [152,80], "src": [224,112], "f": 0, "t": 476, "d": [259], "a": 1 }, + { "px": [160,80], "src": [224,112], "f": 0, "t": 476, "d": [260], "a": 1 }, + { "px": [168,80], "src": [224,112], "f": 0, "t": 476, "d": [261], "a": 1 }, + { "px": [176,80], "src": [224,112], "f": 0, "t": 476, "d": [262], "a": 1 }, + { "px": [184,80], "src": [232,112], "f": 0, "t": 477, "d": [263], "a": 1 }, + { "px": [0,88], "src": [216,112], "f": 0, "t": 475, "d": [264], "a": 1 }, + { "px": [8,88], "src": [224,112], "f": 0, "t": 476, "d": [265], "a": 1 }, + { "px": [16,88], "src": [224,112], "f": 0, "t": 476, "d": [266], "a": 1 }, + { "px": [24,88], "src": [224,112], "f": 0, "t": 476, "d": [267], "a": 1 }, + { "px": [32,88], "src": [224,112], "f": 0, "t": 476, "d": [268], "a": 1 }, + { "px": [40,88], "src": [224,112], "f": 0, "t": 476, "d": [269], "a": 1 }, + { "px": [48,88], "src": [224,112], "f": 0, "t": 476, "d": [270], "a": 1 }, + { "px": [56,88], "src": [224,112], "f": 0, "t": 476, "d": [271], "a": 1 }, + { "px": [64,88], "src": [224,112], "f": 0, "t": 476, "d": [272], "a": 1 }, + { "px": [72,88], "src": [224,112], "f": 0, "t": 476, "d": [273], "a": 1 }, + { "px": [80,88], "src": [224,112], "f": 0, "t": 476, "d": [274], "a": 1 }, + { "px": [88,88], "src": [224,112], "f": 0, "t": 476, "d": [275], "a": 1 }, + { "px": [96,88], "src": [224,112], "f": 0, "t": 476, "d": [276], "a": 1 }, + { "px": [104,88], "src": [224,112], "f": 0, "t": 476, "d": [277], "a": 1 }, + { "px": [112,88], "src": [224,112], "f": 0, "t": 476, "d": [278], "a": 1 }, + { "px": [120,88], "src": [224,112], "f": 0, "t": 476, "d": [279], "a": 1 }, + { "px": [128,88], "src": [224,112], "f": 0, "t": 476, "d": [280], "a": 1 }, + { "px": [136,88], "src": [224,112], "f": 0, "t": 476, "d": [281], "a": 1 }, + { "px": [144,88], "src": [224,112], "f": 0, "t": 476, "d": [282], "a": 1 }, + { "px": [152,88], "src": [224,112], "f": 0, "t": 476, "d": [283], "a": 1 }, + { "px": [160,88], "src": [224,112], "f": 0, "t": 476, "d": [284], "a": 1 }, + { "px": [168,88], "src": [224,112], "f": 0, "t": 476, "d": [285], "a": 1 }, + { "px": [176,88], "src": [224,112], "f": 0, "t": 476, "d": [286], "a": 1 }, + { "px": [184,88], "src": [232,112], "f": 0, "t": 477, "d": [287], "a": 1 }, + { "px": [0,96], "src": [216,112], "f": 0, "t": 475, "d": [288], "a": 1 }, + { "px": [8,96], "src": [224,112], "f": 0, "t": 476, "d": [289], "a": 1 }, + { "px": [16,96], "src": [224,112], "f": 0, "t": 476, "d": [290], "a": 1 }, + { "px": [24,96], "src": [224,112], "f": 0, "t": 476, "d": [291], "a": 1 }, + { "px": [32,96], "src": [224,112], "f": 0, "t": 476, "d": [292], "a": 1 }, + { "px": [40,96], "src": [224,112], "f": 0, "t": 476, "d": [293], "a": 1 }, + { "px": [48,96], "src": [224,112], "f": 0, "t": 476, "d": [294], "a": 1 }, + { "px": [56,96], "src": [224,112], "f": 0, "t": 476, "d": [295], "a": 1 }, + { "px": [64,96], "src": [224,112], "f": 0, "t": 476, "d": [296], "a": 1 }, + { "px": [72,96], "src": [224,112], "f": 0, "t": 476, "d": [297], "a": 1 }, + { "px": [80,96], "src": [224,112], "f": 0, "t": 476, "d": [298], "a": 1 }, + { "px": [88,96], "src": [224,112], "f": 0, "t": 476, "d": [299], "a": 1 }, + { "px": [96,96], "src": [224,112], "f": 0, "t": 476, "d": [300], "a": 1 }, + { "px": [104,96], "src": [224,112], "f": 0, "t": 476, "d": [301], "a": 1 }, + { "px": [112,96], "src": [224,112], "f": 0, "t": 476, "d": [302], "a": 1 }, + { "px": [120,96], "src": [224,112], "f": 0, "t": 476, "d": [303], "a": 1 }, + { "px": [128,96], "src": [224,112], "f": 0, "t": 476, "d": [304], "a": 1 }, + { "px": [136,96], "src": [224,112], "f": 0, "t": 476, "d": [305], "a": 1 }, + { "px": [144,96], "src": [224,112], "f": 0, "t": 476, "d": [306], "a": 1 }, + { "px": [152,96], "src": [224,112], "f": 0, "t": 476, "d": [307], "a": 1 }, + { "px": [160,96], "src": [224,112], "f": 0, "t": 476, "d": [308], "a": 1 }, + { "px": [168,96], "src": [224,112], "f": 0, "t": 476, "d": [309], "a": 1 }, + { "px": [176,96], "src": [224,112], "f": 0, "t": 476, "d": [310], "a": 1 }, + { "px": [184,96], "src": [232,112], "f": 0, "t": 477, "d": [311], "a": 1 }, + { "px": [0,104], "src": [216,112], "f": 0, "t": 475, "d": [312], "a": 1 }, + { "px": [8,104], "src": [224,112], "f": 0, "t": 476, "d": [313], "a": 1 }, + { "px": [16,104], "src": [224,112], "f": 0, "t": 476, "d": [314], "a": 1 }, + { "px": [24,104], "src": [224,112], "f": 0, "t": 476, "d": [315], "a": 1 }, + { "px": [32,104], "src": [224,112], "f": 0, "t": 476, "d": [316], "a": 1 }, + { "px": [40,104], "src": [224,112], "f": 0, "t": 476, "d": [317], "a": 1 }, + { "px": [48,104], "src": [224,112], "f": 0, "t": 476, "d": [318], "a": 1 }, + { "px": [56,104], "src": [224,112], "f": 0, "t": 476, "d": [319], "a": 1 }, + { "px": [64,104], "src": [224,112], "f": 0, "t": 476, "d": [320], "a": 1 }, + { "px": [72,104], "src": [224,112], "f": 0, "t": 476, "d": [321], "a": 1 }, + { "px": [80,104], "src": [224,112], "f": 0, "t": 476, "d": [322], "a": 1 }, + { "px": [88,104], "src": [224,112], "f": 0, "t": 476, "d": [323], "a": 1 }, + { "px": [96,104], "src": [224,112], "f": 0, "t": 476, "d": [324], "a": 1 }, + { "px": [104,104], "src": [224,112], "f": 0, "t": 476, "d": [325], "a": 1 }, + { "px": [112,104], "src": [224,112], "f": 0, "t": 476, "d": [326], "a": 1 }, + { "px": [120,104], "src": [224,112], "f": 0, "t": 476, "d": [327], "a": 1 }, + { "px": [128,104], "src": [224,112], "f": 0, "t": 476, "d": [328], "a": 1 }, + { "px": [136,104], "src": [224,112], "f": 0, "t": 476, "d": [329], "a": 1 }, + { "px": [144,104], "src": [224,112], "f": 0, "t": 476, "d": [330], "a": 1 }, + { "px": [152,104], "src": [224,112], "f": 0, "t": 476, "d": [331], "a": 1 }, + { "px": [160,104], "src": [224,112], "f": 0, "t": 476, "d": [332], "a": 1 }, + { "px": [168,104], "src": [224,112], "f": 0, "t": 476, "d": [333], "a": 1 }, + { "px": [176,104], "src": [224,112], "f": 0, "t": 476, "d": [334], "a": 1 }, + { "px": [184,104], "src": [232,112], "f": 0, "t": 477, "d": [335], "a": 1 }, + { "px": [0,112], "src": [216,112], "f": 0, "t": 475, "d": [336], "a": 1 }, + { "px": [8,112], "src": [224,112], "f": 0, "t": 476, "d": [337], "a": 1 }, + { "px": [16,112], "src": [224,112], "f": 0, "t": 476, "d": [338], "a": 1 }, + { "px": [24,112], "src": [224,112], "f": 0, "t": 476, "d": [339], "a": 1 }, + { "px": [32,112], "src": [224,112], "f": 0, "t": 476, "d": [340], "a": 1 }, + { "px": [40,112], "src": [224,112], "f": 0, "t": 476, "d": [341], "a": 1 }, + { "px": [48,112], "src": [224,112], "f": 0, "t": 476, "d": [342], "a": 1 }, + { "px": [56,112], "src": [224,112], "f": 0, "t": 476, "d": [343], "a": 1 }, + { "px": [64,112], "src": [224,112], "f": 0, "t": 476, "d": [344], "a": 1 }, + { "px": [72,112], "src": [224,112], "f": 0, "t": 476, "d": [345], "a": 1 }, + { "px": [80,112], "src": [224,112], "f": 0, "t": 476, "d": [346], "a": 1 }, + { "px": [88,112], "src": [224,112], "f": 0, "t": 476, "d": [347], "a": 1 }, + { "px": [96,112], "src": [224,112], "f": 0, "t": 476, "d": [348], "a": 1 }, + { "px": [104,112], "src": [224,112], "f": 0, "t": 476, "d": [349], "a": 1 }, + { "px": [112,112], "src": [224,112], "f": 0, "t": 476, "d": [350], "a": 1 }, + { "px": [120,112], "src": [224,112], "f": 0, "t": 476, "d": [351], "a": 1 }, + { "px": [128,112], "src": [224,112], "f": 0, "t": 476, "d": [352], "a": 1 }, + { "px": [136,112], "src": [224,112], "f": 0, "t": 476, "d": [353], "a": 1 }, + { "px": [144,112], "src": [224,112], "f": 0, "t": 476, "d": [354], "a": 1 }, + { "px": [152,112], "src": [224,112], "f": 0, "t": 476, "d": [355], "a": 1 }, + { "px": [160,112], "src": [224,112], "f": 0, "t": 476, "d": [356], "a": 1 }, + { "px": [168,112], "src": [224,112], "f": 0, "t": 476, "d": [357], "a": 1 }, + { "px": [176,112], "src": [224,112], "f": 0, "t": 476, "d": [358], "a": 1 }, + { "px": [184,112], "src": [232,112], "f": 0, "t": 477, "d": [359], "a": 1 }, + { "px": [0,120], "src": [216,104], "f": 2, "t": 443, "d": [360], "a": 1 }, + { "px": [8,120], "src": [224,120], "f": 0, "t": 508, "d": [361], "a": 1 }, + { "px": [16,120], "src": [224,120], "f": 0, "t": 508, "d": [362], "a": 1 }, + { "px": [24,120], "src": [224,120], "f": 0, "t": 508, "d": [363], "a": 1 }, + { "px": [32,120], "src": [224,120], "f": 0, "t": 508, "d": [364], "a": 1 }, + { "px": [40,120], "src": [224,120], "f": 0, "t": 508, "d": [365], "a": 1 }, + { "px": [48,120], "src": [224,120], "f": 0, "t": 508, "d": [366], "a": 1 }, + { "px": [56,120], "src": [224,120], "f": 0, "t": 508, "d": [367], "a": 1 }, + { "px": [64,120], "src": [224,120], "f": 0, "t": 508, "d": [368], "a": 1 }, + { "px": [72,120], "src": [224,120], "f": 0, "t": 508, "d": [369], "a": 1 }, + { "px": [80,120], "src": [224,120], "f": 0, "t": 508, "d": [370], "a": 1 }, + { "px": [88,120], "src": [224,120], "f": 0, "t": 508, "d": [371], "a": 1 }, + { "px": [96,120], "src": [224,120], "f": 0, "t": 508, "d": [372], "a": 1 }, + { "px": [104,120], "src": [224,120], "f": 0, "t": 508, "d": [373], "a": 1 }, + { "px": [112,120], "src": [224,120], "f": 0, "t": 508, "d": [374], "a": 1 }, + { "px": [120,120], "src": [224,120], "f": 0, "t": 508, "d": [375], "a": 1 }, + { "px": [128,120], "src": [224,120], "f": 0, "t": 508, "d": [376], "a": 1 }, + { "px": [136,120], "src": [224,120], "f": 0, "t": 508, "d": [377], "a": 1 }, + { "px": [144,120], "src": [224,120], "f": 0, "t": 508, "d": [378], "a": 1 }, + { "px": [152,120], "src": [224,120], "f": 0, "t": 508, "d": [379], "a": 1 }, + { "px": [160,120], "src": [224,120], "f": 0, "t": 508, "d": [380], "a": 1 }, + { "px": [168,120], "src": [224,120], "f": 0, "t": 508, "d": [381], "a": 1 }, + { "px": [176,120], "src": [224,120], "f": 0, "t": 508, "d": [382], "a": 1 }, + { "px": [184,120], "src": [216,104], "f": 3, "t": 443, "d": [383], "a": 1 } + ], + "entityInstances": [] + } + ], + "__neighbours": [] + }, + { + "identifier": "RandomSfxModal", + "iid": "9f8a7d00-fa90-11f0-ba84-51a28bab3076", + "uid": 70, + "worldX": 472, + "worldY": -192, + "worldDepth": 0, + "pxWid": 240, + "pxHei": 120, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 30, + "__cHei": 15, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "9f8a7d08-fa90-11f0-ba84-6930c192c86b", + "levelId": 70, + "layerDefUid": 6, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 8560234, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Dropdown", + "__grid": [4,2], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "b1c2d3e4-fa90-11f0-ba84-a1b2c3d4e5f6", + "width": 176, + "height": 16, + "defUid": 2, + "px": [32,16], + "fieldInstances": [], + "__worldX": 504, + "__worldY": -176 + }, + { + "__identifier": "TextButton", + "__grid": [6,11], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "3be275a0-fa90-11f0-8348-15034fa3d601", + "width": 136, + "height": 16, + "defUid": 78, + "px": [48,88], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "Use the sound template", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["Use the sound template"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "HardBlue", "__tile": null, "defUid": 82, "realEditorValues": [] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 520, + "__worldY": -104 + } + ] + }, + { + "__identifier": "Panels", + "__type": "Entities", + "__cWid": 30, + "__cHei": 15, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "812bd5d1-fa90-11f0-ade1-f1f625992215", + "levelId": 70, + "layerDefUid": 75, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 8886607, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 30, + "__cHei": 15, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 72, + "__tilesetRelPath": "sprite-sheet.png", + "iid": "9f8aa410-fa90-11f0-ba84-c19a938c4658", + "levelId": 70, + "layerDefUid": 10, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 5509726, + "overrideTilesetUid": null, + "gridTiles": [ + { "px": [0,0], "src": [216,104], "f": 0, "t": 443, "d": [0], "a": 1 }, + { "px": [8,0], "src": [224,104], "f": 0, "t": 444, "d": [1], "a": 1 }, + { "px": [16,0], "src": [224,104], "f": 0, "t": 444, "d": [2], "a": 1 }, + { "px": [24,0], "src": [224,104], "f": 0, "t": 444, "d": [3], "a": 1 }, + { "px": [32,0], "src": [224,104], "f": 0, "t": 444, "d": [4], "a": 1 }, + { "px": [40,0], "src": [224,104], "f": 0, "t": 444, "d": [5], "a": 1 }, + { "px": [48,0], "src": [224,104], "f": 0, "t": 444, "d": [6], "a": 1 }, + { "px": [56,0], "src": [224,104], "f": 0, "t": 444, "d": [7], "a": 1 }, + { "px": [64,0], "src": [224,104], "f": 0, "t": 444, "d": [8], "a": 1 }, + { "px": [72,0], "src": [224,104], "f": 0, "t": 444, "d": [9], "a": 1 }, + { "px": [80,0], "src": [224,104], "f": 0, "t": 444, "d": [10], "a": 1 }, + { "px": [88,0], "src": [224,104], "f": 0, "t": 444, "d": [11], "a": 1 }, + { "px": [96,0], "src": [224,104], "f": 0, "t": 444, "d": [12], "a": 1 }, + { "px": [104,0], "src": [224,104], "f": 0, "t": 444, "d": [13], "a": 1 }, + { "px": [112,0], "src": [224,104], "f": 0, "t": 444, "d": [14], "a": 1 }, + { "px": [120,0], "src": [224,104], "f": 0, "t": 444, "d": [15], "a": 1 }, + { "px": [128,0], "src": [224,104], "f": 0, "t": 444, "d": [16], "a": 1 }, + { "px": [136,0], "src": [224,104], "f": 0, "t": 444, "d": [17], "a": 1 }, + { "px": [144,0], "src": [224,104], "f": 0, "t": 444, "d": [18], "a": 1 }, + { "px": [152,0], "src": [224,104], "f": 0, "t": 444, "d": [19], "a": 1 }, + { "px": [160,0], "src": [224,104], "f": 0, "t": 444, "d": [20], "a": 1 }, + { "px": [168,0], "src": [224,104], "f": 0, "t": 444, "d": [21], "a": 1 }, + { "px": [176,0], "src": [224,104], "f": 0, "t": 444, "d": [22], "a": 1 }, + { "px": [184,0], "src": [224,104], "f": 0, "t": 444, "d": [23], "a": 1 }, + { "px": [192,0], "src": [224,104], "f": 0, "t": 444, "d": [24], "a": 1 }, + { "px": [200,0], "src": [224,104], "f": 0, "t": 444, "d": [25], "a": 1 }, + { "px": [208,0], "src": [224,104], "f": 0, "t": 444, "d": [26], "a": 1 }, + { "px": [216,0], "src": [224,104], "f": 0, "t": 444, "d": [27], "a": 1 }, + { "px": [224,0], "src": [224,104], "f": 0, "t": 444, "d": [28], "a": 1 }, + { "px": [232,0], "src": [232,104], "f": 0, "t": 445, "d": [29], "a": 1 }, + { "px": [0,8], "src": [216,112], "f": 0, "t": 475, "d": [30], "a": 1 }, + { "px": [8,8], "src": [224,112], "f": 0, "t": 476, "d": [31], "a": 1 }, + { "px": [16,8], "src": [224,112], "f": 0, "t": 476, "d": [32], "a": 1 }, + { "px": [24,8], "src": [224,112], "f": 0, "t": 476, "d": [33], "a": 1 }, + { "px": [32,8], "src": [224,112], "f": 0, "t": 476, "d": [34], "a": 1 }, + { "px": [40,8], "src": [224,112], "f": 0, "t": 476, "d": [35], "a": 1 }, + { "px": [48,8], "src": [224,112], "f": 0, "t": 476, "d": [36], "a": 1 }, + { "px": [56,8], "src": [224,112], "f": 0, "t": 476, "d": [37], "a": 1 }, + { "px": [64,8], "src": [224,112], "f": 0, "t": 476, "d": [38], "a": 1 }, + { "px": [72,8], "src": [224,112], "f": 0, "t": 476, "d": [39], "a": 1 }, + { "px": [80,8], "src": [224,112], "f": 0, "t": 476, "d": [40], "a": 1 }, + { "px": [88,8], "src": [224,112], "f": 0, "t": 476, "d": [41], "a": 1 }, + { "px": [96,8], "src": [224,112], "f": 0, "t": 476, "d": [42], "a": 1 }, + { "px": [104,8], "src": [224,112], "f": 0, "t": 476, "d": [43], "a": 1 }, + { "px": [112,8], "src": [224,112], "f": 0, "t": 476, "d": [44], "a": 1 }, + { "px": [120,8], "src": [224,112], "f": 0, "t": 476, "d": [45], "a": 1 }, + { "px": [128,8], "src": [224,112], "f": 0, "t": 476, "d": [46], "a": 1 }, + { "px": [136,8], "src": [224,112], "f": 0, "t": 476, "d": [47], "a": 1 }, + { "px": [144,8], "src": [224,112], "f": 0, "t": 476, "d": [48], "a": 1 }, + { "px": [152,8], "src": [224,112], "f": 0, "t": 476, "d": [49], "a": 1 }, + { "px": [160,8], "src": [224,112], "f": 0, "t": 476, "d": [50], "a": 1 }, + { "px": [168,8], "src": [224,112], "f": 0, "t": 476, "d": [51], "a": 1 }, + { "px": [176,8], "src": [224,112], "f": 0, "t": 476, "d": [52], "a": 1 }, + { "px": [184,8], "src": [224,112], "f": 0, "t": 476, "d": [53], "a": 1 }, + { "px": [192,8], "src": [224,112], "f": 0, "t": 476, "d": [54], "a": 1 }, + { "px": [200,8], "src": [224,112], "f": 0, "t": 476, "d": [55], "a": 1 }, + { "px": [208,8], "src": [224,112], "f": 0, "t": 476, "d": [56], "a": 1 }, + { "px": [216,8], "src": [224,112], "f": 0, "t": 476, "d": [57], "a": 1 }, + { "px": [224,8], "src": [224,112], "f": 0, "t": 476, "d": [58], "a": 1 }, + { "px": [232,8], "src": [232,112], "f": 0, "t": 477, "d": [59], "a": 1 }, + { "px": [0,16], "src": [216,112], "f": 0, "t": 475, "d": [60], "a": 1 }, + { "px": [8,16], "src": [224,112], "f": 0, "t": 476, "d": [61], "a": 1 }, + { "px": [16,16], "src": [224,112], "f": 0, "t": 476, "d": [62], "a": 1 }, + { "px": [24,16], "src": [224,112], "f": 0, "t": 476, "d": [63], "a": 1 }, + { "px": [32,16], "src": [224,112], "f": 0, "t": 476, "d": [64], "a": 1 }, + { "px": [40,16], "src": [224,112], "f": 0, "t": 476, "d": [65], "a": 1 }, + { "px": [48,16], "src": [224,112], "f": 0, "t": 476, "d": [66], "a": 1 }, + { "px": [56,16], "src": [224,112], "f": 0, "t": 476, "d": [67], "a": 1 }, + { "px": [64,16], "src": [224,112], "f": 0, "t": 476, "d": [68], "a": 1 }, + { "px": [72,16], "src": [224,112], "f": 0, "t": 476, "d": [69], "a": 1 }, + { "px": [80,16], "src": [224,112], "f": 0, "t": 476, "d": [70], "a": 1 }, + { "px": [88,16], "src": [224,112], "f": 0, "t": 476, "d": [71], "a": 1 }, + { "px": [96,16], "src": [224,112], "f": 0, "t": 476, "d": [72], "a": 1 }, + { "px": [104,16], "src": [224,112], "f": 0, "t": 476, "d": [73], "a": 1 }, + { "px": [112,16], "src": [224,112], "f": 0, "t": 476, "d": [74], "a": 1 }, + { "px": [120,16], "src": [224,112], "f": 0, "t": 476, "d": [75], "a": 1 }, + { "px": [128,16], "src": [224,112], "f": 0, "t": 476, "d": [76], "a": 1 }, + { "px": [136,16], "src": [224,112], "f": 0, "t": 476, "d": [77], "a": 1 }, + { "px": [144,16], "src": [224,112], "f": 0, "t": 476, "d": [78], "a": 1 }, + { "px": [152,16], "src": [224,112], "f": 0, "t": 476, "d": [79], "a": 1 }, + { "px": [160,16], "src": [224,112], "f": 0, "t": 476, "d": [80], "a": 1 }, + { "px": [168,16], "src": [224,112], "f": 0, "t": 476, "d": [81], "a": 1 }, + { "px": [176,16], "src": [224,112], "f": 0, "t": 476, "d": [82], "a": 1 }, + { "px": [184,16], "src": [224,112], "f": 0, "t": 476, "d": [83], "a": 1 }, + { "px": [192,16], "src": [224,112], "f": 0, "t": 476, "d": [84], "a": 1 }, + { "px": [200,16], "src": [224,112], "f": 0, "t": 476, "d": [85], "a": 1 }, + { "px": [208,16], "src": [224,112], "f": 0, "t": 476, "d": [86], "a": 1 }, + { "px": [216,16], "src": [224,112], "f": 0, "t": 476, "d": [87], "a": 1 }, + { "px": [224,16], "src": [224,112], "f": 0, "t": 476, "d": [88], "a": 1 }, + { "px": [232,16], "src": [232,112], "f": 0, "t": 477, "d": [89], "a": 1 }, + { "px": [0,24], "src": [216,112], "f": 0, "t": 475, "d": [90], "a": 1 }, + { "px": [8,24], "src": [224,112], "f": 0, "t": 476, "d": [91], "a": 1 }, + { "px": [16,24], "src": [224,112], "f": 0, "t": 476, "d": [92], "a": 1 }, + { "px": [24,24], "src": [224,112], "f": 0, "t": 476, "d": [93], "a": 1 }, + { "px": [32,24], "src": [224,112], "f": 0, "t": 476, "d": [94], "a": 1 }, + { "px": [40,24], "src": [224,112], "f": 0, "t": 476, "d": [95], "a": 1 }, + { "px": [48,24], "src": [224,112], "f": 0, "t": 476, "d": [96], "a": 1 }, + { "px": [56,24], "src": [224,112], "f": 0, "t": 476, "d": [97], "a": 1 }, + { "px": [64,24], "src": [224,112], "f": 0, "t": 476, "d": [98], "a": 1 }, + { "px": [72,24], "src": [224,112], "f": 0, "t": 476, "d": [99], "a": 1 }, + { "px": [80,24], "src": [224,112], "f": 0, "t": 476, "d": [100], "a": 1 }, + { "px": [88,24], "src": [224,112], "f": 0, "t": 476, "d": [101], "a": 1 }, + { "px": [96,24], "src": [224,112], "f": 0, "t": 476, "d": [102], "a": 1 }, + { "px": [104,24], "src": [224,112], "f": 0, "t": 476, "d": [103], "a": 1 }, + { "px": [112,24], "src": [224,112], "f": 0, "t": 476, "d": [104], "a": 1 }, + { "px": [120,24], "src": [224,112], "f": 0, "t": 476, "d": [105], "a": 1 }, + { "px": [128,24], "src": [224,112], "f": 0, "t": 476, "d": [106], "a": 1 }, + { "px": [136,24], "src": [224,112], "f": 0, "t": 476, "d": [107], "a": 1 }, + { "px": [144,24], "src": [224,112], "f": 0, "t": 476, "d": [108], "a": 1 }, + { "px": [152,24], "src": [224,112], "f": 0, "t": 476, "d": [109], "a": 1 }, + { "px": [160,24], "src": [224,112], "f": 0, "t": 476, "d": [110], "a": 1 }, + { "px": [168,24], "src": [224,112], "f": 0, "t": 476, "d": [111], "a": 1 }, + { "px": [176,24], "src": [224,112], "f": 0, "t": 476, "d": [112], "a": 1 }, + { "px": [184,24], "src": [224,112], "f": 0, "t": 476, "d": [113], "a": 1 }, + { "px": [192,24], "src": [224,112], "f": 0, "t": 476, "d": [114], "a": 1 }, + { "px": [200,24], "src": [224,112], "f": 0, "t": 476, "d": [115], "a": 1 }, + { "px": [208,24], "src": [224,112], "f": 0, "t": 476, "d": [116], "a": 1 }, + { "px": [216,24], "src": [224,112], "f": 0, "t": 476, "d": [117], "a": 1 }, + { "px": [224,24], "src": [224,112], "f": 0, "t": 476, "d": [118], "a": 1 }, + { "px": [232,24], "src": [232,112], "f": 0, "t": 477, "d": [119], "a": 1 }, + { "px": [0,32], "src": [216,112], "f": 0, "t": 475, "d": [120], "a": 1 }, + { "px": [8,32], "src": [224,112], "f": 0, "t": 476, "d": [121], "a": 1 }, + { "px": [16,32], "src": [224,112], "f": 0, "t": 476, "d": [122], "a": 1 }, + { "px": [24,32], "src": [224,112], "f": 0, "t": 476, "d": [123], "a": 1 }, + { "px": [32,32], "src": [224,112], "f": 0, "t": 476, "d": [124], "a": 1 }, + { "px": [40,32], "src": [224,112], "f": 0, "t": 476, "d": [125], "a": 1 }, + { "px": [48,32], "src": [224,112], "f": 0, "t": 476, "d": [126], "a": 1 }, + { "px": [56,32], "src": [224,112], "f": 0, "t": 476, "d": [127], "a": 1 }, + { "px": [64,32], "src": [224,112], "f": 0, "t": 476, "d": [128], "a": 1 }, + { "px": [72,32], "src": [224,112], "f": 0, "t": 476, "d": [129], "a": 1 }, + { "px": [80,32], "src": [224,112], "f": 0, "t": 476, "d": [130], "a": 1 }, + { "px": [88,32], "src": [224,112], "f": 0, "t": 476, "d": [131], "a": 1 }, + { "px": [96,32], "src": [224,112], "f": 0, "t": 476, "d": [132], "a": 1 }, + { "px": [104,32], "src": [224,112], "f": 0, "t": 476, "d": [133], "a": 1 }, + { "px": [112,32], "src": [224,112], "f": 0, "t": 476, "d": [134], "a": 1 }, + { "px": [120,32], "src": [224,112], "f": 0, "t": 476, "d": [135], "a": 1 }, + { "px": [128,32], "src": [224,112], "f": 0, "t": 476, "d": [136], "a": 1 }, + { "px": [136,32], "src": [224,112], "f": 0, "t": 476, "d": [137], "a": 1 }, + { "px": [144,32], "src": [224,112], "f": 0, "t": 476, "d": [138], "a": 1 }, + { "px": [152,32], "src": [224,112], "f": 0, "t": 476, "d": [139], "a": 1 }, + { "px": [160,32], "src": [224,112], "f": 0, "t": 476, "d": [140], "a": 1 }, + { "px": [168,32], "src": [224,112], "f": 0, "t": 476, "d": [141], "a": 1 }, + { "px": [176,32], "src": [224,112], "f": 0, "t": 476, "d": [142], "a": 1 }, + { "px": [184,32], "src": [224,112], "f": 0, "t": 476, "d": [143], "a": 1 }, + { "px": [192,32], "src": [224,112], "f": 0, "t": 476, "d": [144], "a": 1 }, + { "px": [200,32], "src": [224,112], "f": 0, "t": 476, "d": [145], "a": 1 }, + { "px": [208,32], "src": [224,112], "f": 0, "t": 476, "d": [146], "a": 1 }, + { "px": [216,32], "src": [224,112], "f": 0, "t": 476, "d": [147], "a": 1 }, + { "px": [224,32], "src": [224,112], "f": 0, "t": 476, "d": [148], "a": 1 }, + { "px": [232,32], "src": [232,112], "f": 0, "t": 477, "d": [149], "a": 1 }, + { "px": [0,40], "src": [216,112], "f": 0, "t": 475, "d": [150], "a": 1 }, + { "px": [8,40], "src": [224,112], "f": 0, "t": 476, "d": [151], "a": 1 }, + { "px": [16,40], "src": [224,112], "f": 0, "t": 476, "d": [152], "a": 1 }, + { "px": [24,40], "src": [224,112], "f": 0, "t": 476, "d": [153], "a": 1 }, + { "px": [32,40], "src": [224,112], "f": 0, "t": 476, "d": [154], "a": 1 }, + { "px": [40,40], "src": [224,112], "f": 0, "t": 476, "d": [155], "a": 1 }, + { "px": [48,40], "src": [224,112], "f": 0, "t": 476, "d": [156], "a": 1 }, + { "px": [56,40], "src": [224,112], "f": 0, "t": 476, "d": [157], "a": 1 }, + { "px": [64,40], "src": [224,112], "f": 0, "t": 476, "d": [158], "a": 1 }, + { "px": [72,40], "src": [224,112], "f": 0, "t": 476, "d": [159], "a": 1 }, + { "px": [80,40], "src": [224,112], "f": 0, "t": 476, "d": [160], "a": 1 }, + { "px": [88,40], "src": [224,112], "f": 0, "t": 476, "d": [161], "a": 1 }, + { "px": [96,40], "src": [224,112], "f": 0, "t": 476, "d": [162], "a": 1 }, + { "px": [104,40], "src": [224,112], "f": 0, "t": 476, "d": [163], "a": 1 }, + { "px": [112,40], "src": [224,112], "f": 0, "t": 476, "d": [164], "a": 1 }, + { "px": [120,40], "src": [224,112], "f": 0, "t": 476, "d": [165], "a": 1 }, + { "px": [128,40], "src": [224,112], "f": 0, "t": 476, "d": [166], "a": 1 }, + { "px": [136,40], "src": [224,112], "f": 0, "t": 476, "d": [167], "a": 1 }, + { "px": [144,40], "src": [224,112], "f": 0, "t": 476, "d": [168], "a": 1 }, + { "px": [152,40], "src": [224,112], "f": 0, "t": 476, "d": [169], "a": 1 }, + { "px": [160,40], "src": [224,112], "f": 0, "t": 476, "d": [170], "a": 1 }, + { "px": [168,40], "src": [224,112], "f": 0, "t": 476, "d": [171], "a": 1 }, + { "px": [176,40], "src": [224,112], "f": 0, "t": 476, "d": [172], "a": 1 }, + { "px": [184,40], "src": [224,112], "f": 0, "t": 476, "d": [173], "a": 1 }, + { "px": [192,40], "src": [224,112], "f": 0, "t": 476, "d": [174], "a": 1 }, + { "px": [200,40], "src": [224,112], "f": 0, "t": 476, "d": [175], "a": 1 }, + { "px": [208,40], "src": [224,112], "f": 0, "t": 476, "d": [176], "a": 1 }, + { "px": [216,40], "src": [224,112], "f": 0, "t": 476, "d": [177], "a": 1 }, + { "px": [224,40], "src": [224,112], "f": 0, "t": 476, "d": [178], "a": 1 }, + { "px": [232,40], "src": [232,112], "f": 0, "t": 477, "d": [179], "a": 1 }, + { "px": [0,48], "src": [216,112], "f": 0, "t": 475, "d": [180], "a": 1 }, + { "px": [8,48], "src": [224,112], "f": 0, "t": 476, "d": [181], "a": 1 }, + { "px": [16,48], "src": [224,112], "f": 0, "t": 476, "d": [182], "a": 1 }, + { "px": [24,48], "src": [224,112], "f": 0, "t": 476, "d": [183], "a": 1 }, + { "px": [32,48], "src": [224,112], "f": 0, "t": 476, "d": [184], "a": 1 }, + { "px": [40,48], "src": [224,112], "f": 0, "t": 476, "d": [185], "a": 1 }, + { "px": [48,48], "src": [224,112], "f": 0, "t": 476, "d": [186], "a": 1 }, + { "px": [56,48], "src": [224,112], "f": 0, "t": 476, "d": [187], "a": 1 }, + { "px": [64,48], "src": [224,112], "f": 0, "t": 476, "d": [188], "a": 1 }, + { "px": [72,48], "src": [224,112], "f": 0, "t": 476, "d": [189], "a": 1 }, + { "px": [80,48], "src": [224,112], "f": 0, "t": 476, "d": [190], "a": 1 }, + { "px": [88,48], "src": [224,112], "f": 0, "t": 476, "d": [191], "a": 1 }, + { "px": [96,48], "src": [224,112], "f": 0, "t": 476, "d": [192], "a": 1 }, + { "px": [104,48], "src": [224,112], "f": 0, "t": 476, "d": [193], "a": 1 }, + { "px": [112,48], "src": [224,112], "f": 0, "t": 476, "d": [194], "a": 1 }, + { "px": [120,48], "src": [224,112], "f": 0, "t": 476, "d": [195], "a": 1 }, + { "px": [128,48], "src": [224,112], "f": 0, "t": 476, "d": [196], "a": 1 }, + { "px": [136,48], "src": [224,112], "f": 0, "t": 476, "d": [197], "a": 1 }, + { "px": [144,48], "src": [224,112], "f": 0, "t": 476, "d": [198], "a": 1 }, + { "px": [152,48], "src": [224,112], "f": 0, "t": 476, "d": [199], "a": 1 }, + { "px": [160,48], "src": [224,112], "f": 0, "t": 476, "d": [200], "a": 1 }, + { "px": [168,48], "src": [224,112], "f": 0, "t": 476, "d": [201], "a": 1 }, + { "px": [176,48], "src": [224,112], "f": 0, "t": 476, "d": [202], "a": 1 }, + { "px": [184,48], "src": [224,112], "f": 0, "t": 476, "d": [203], "a": 1 }, + { "px": [192,48], "src": [224,112], "f": 0, "t": 476, "d": [204], "a": 1 }, + { "px": [200,48], "src": [224,112], "f": 0, "t": 476, "d": [205], "a": 1 }, + { "px": [208,48], "src": [224,112], "f": 0, "t": 476, "d": [206], "a": 1 }, + { "px": [216,48], "src": [224,112], "f": 0, "t": 476, "d": [207], "a": 1 }, + { "px": [224,48], "src": [224,112], "f": 0, "t": 476, "d": [208], "a": 1 }, + { "px": [232,48], "src": [232,112], "f": 0, "t": 477, "d": [209], "a": 1 }, + { "px": [0,56], "src": [216,112], "f": 0, "t": 475, "d": [210], "a": 1 }, + { "px": [8,56], "src": [224,112], "f": 0, "t": 476, "d": [211], "a": 1 }, + { "px": [16,56], "src": [224,112], "f": 0, "t": 476, "d": [212], "a": 1 }, + { "px": [24,56], "src": [224,112], "f": 0, "t": 476, "d": [213], "a": 1 }, + { "px": [32,56], "src": [224,112], "f": 0, "t": 476, "d": [214], "a": 1 }, + { "px": [40,56], "src": [224,112], "f": 0, "t": 476, "d": [215], "a": 1 }, + { "px": [48,56], "src": [224,112], "f": 0, "t": 476, "d": [216], "a": 1 }, + { "px": [56,56], "src": [224,112], "f": 0, "t": 476, "d": [217], "a": 1 }, + { "px": [64,56], "src": [224,112], "f": 0, "t": 476, "d": [218], "a": 1 }, + { "px": [72,56], "src": [224,112], "f": 0, "t": 476, "d": [219], "a": 1 }, + { "px": [80,56], "src": [224,112], "f": 0, "t": 476, "d": [220], "a": 1 }, + { "px": [88,56], "src": [224,112], "f": 0, "t": 476, "d": [221], "a": 1 }, + { "px": [96,56], "src": [224,112], "f": 0, "t": 476, "d": [222], "a": 1 }, + { "px": [104,56], "src": [224,112], "f": 0, "t": 476, "d": [223], "a": 1 }, + { "px": [112,56], "src": [224,112], "f": 0, "t": 476, "d": [224], "a": 1 }, + { "px": [120,56], "src": [224,112], "f": 0, "t": 476, "d": [225], "a": 1 }, + { "px": [128,56], "src": [224,112], "f": 0, "t": 476, "d": [226], "a": 1 }, + { "px": [136,56], "src": [224,112], "f": 0, "t": 476, "d": [227], "a": 1 }, + { "px": [144,56], "src": [224,112], "f": 0, "t": 476, "d": [228], "a": 1 }, + { "px": [152,56], "src": [224,112], "f": 0, "t": 476, "d": [229], "a": 1 }, + { "px": [160,56], "src": [224,112], "f": 0, "t": 476, "d": [230], "a": 1 }, + { "px": [168,56], "src": [224,112], "f": 0, "t": 476, "d": [231], "a": 1 }, + { "px": [176,56], "src": [224,112], "f": 0, "t": 476, "d": [232], "a": 1 }, + { "px": [184,56], "src": [224,112], "f": 0, "t": 476, "d": [233], "a": 1 }, + { "px": [192,56], "src": [224,112], "f": 0, "t": 476, "d": [234], "a": 1 }, + { "px": [200,56], "src": [224,112], "f": 0, "t": 476, "d": [235], "a": 1 }, + { "px": [208,56], "src": [224,112], "f": 0, "t": 476, "d": [236], "a": 1 }, + { "px": [216,56], "src": [224,112], "f": 0, "t": 476, "d": [237], "a": 1 }, + { "px": [224,56], "src": [224,112], "f": 0, "t": 476, "d": [238], "a": 1 }, + { "px": [232,56], "src": [232,112], "f": 0, "t": 477, "d": [239], "a": 1 }, + { "px": [0,64], "src": [216,112], "f": 0, "t": 475, "d": [240], "a": 1 }, + { "px": [8,64], "src": [224,112], "f": 0, "t": 476, "d": [241], "a": 1 }, + { "px": [16,64], "src": [224,112], "f": 0, "t": 476, "d": [242], "a": 1 }, + { "px": [24,64], "src": [224,112], "f": 0, "t": 476, "d": [243], "a": 1 }, + { "px": [32,64], "src": [224,112], "f": 0, "t": 476, "d": [244], "a": 1 }, + { "px": [40,64], "src": [224,112], "f": 0, "t": 476, "d": [245], "a": 1 }, + { "px": [48,64], "src": [224,112], "f": 0, "t": 476, "d": [246], "a": 1 }, + { "px": [56,64], "src": [224,112], "f": 0, "t": 476, "d": [247], "a": 1 }, + { "px": [64,64], "src": [224,112], "f": 0, "t": 476, "d": [248], "a": 1 }, + { "px": [72,64], "src": [224,112], "f": 0, "t": 476, "d": [249], "a": 1 }, + { "px": [80,64], "src": [224,112], "f": 0, "t": 476, "d": [250], "a": 1 }, + { "px": [88,64], "src": [224,112], "f": 0, "t": 476, "d": [251], "a": 1 }, + { "px": [96,64], "src": [224,112], "f": 0, "t": 476, "d": [252], "a": 1 }, + { "px": [104,64], "src": [224,112], "f": 0, "t": 476, "d": [253], "a": 1 }, + { "px": [112,64], "src": [224,112], "f": 0, "t": 476, "d": [254], "a": 1 }, + { "px": [120,64], "src": [224,112], "f": 0, "t": 476, "d": [255], "a": 1 }, + { "px": [128,64], "src": [224,112], "f": 0, "t": 476, "d": [256], "a": 1 }, + { "px": [136,64], "src": [224,112], "f": 0, "t": 476, "d": [257], "a": 1 }, + { "px": [144,64], "src": [224,112], "f": 0, "t": 476, "d": [258], "a": 1 }, + { "px": [152,64], "src": [224,112], "f": 0, "t": 476, "d": [259], "a": 1 }, + { "px": [160,64], "src": [224,112], "f": 0, "t": 476, "d": [260], "a": 1 }, + { "px": [168,64], "src": [224,112], "f": 0, "t": 476, "d": [261], "a": 1 }, + { "px": [176,64], "src": [224,112], "f": 0, "t": 476, "d": [262], "a": 1 }, + { "px": [184,64], "src": [224,112], "f": 0, "t": 476, "d": [263], "a": 1 }, + { "px": [192,64], "src": [224,112], "f": 0, "t": 476, "d": [264], "a": 1 }, + { "px": [200,64], "src": [224,112], "f": 0, "t": 476, "d": [265], "a": 1 }, + { "px": [208,64], "src": [224,112], "f": 0, "t": 476, "d": [266], "a": 1 }, + { "px": [216,64], "src": [224,112], "f": 0, "t": 476, "d": [267], "a": 1 }, + { "px": [224,64], "src": [224,112], "f": 0, "t": 476, "d": [268], "a": 1 }, + { "px": [232,64], "src": [232,112], "f": 0, "t": 477, "d": [269], "a": 1 }, + { "px": [0,72], "src": [216,112], "f": 0, "t": 475, "d": [270], "a": 1 }, + { "px": [8,72], "src": [224,112], "f": 0, "t": 476, "d": [271], "a": 1 }, + { "px": [16,72], "src": [224,112], "f": 0, "t": 476, "d": [272], "a": 1 }, + { "px": [24,72], "src": [224,112], "f": 0, "t": 476, "d": [273], "a": 1 }, + { "px": [32,72], "src": [224,112], "f": 0, "t": 476, "d": [274], "a": 1 }, + { "px": [40,72], "src": [224,112], "f": 0, "t": 476, "d": [275], "a": 1 }, + { "px": [48,72], "src": [224,112], "f": 0, "t": 476, "d": [276], "a": 1 }, + { "px": [56,72], "src": [224,112], "f": 0, "t": 476, "d": [277], "a": 1 }, + { "px": [64,72], "src": [224,112], "f": 0, "t": 476, "d": [278], "a": 1 }, + { "px": [72,72], "src": [224,112], "f": 0, "t": 476, "d": [279], "a": 1 }, + { "px": [80,72], "src": [224,112], "f": 0, "t": 476, "d": [280], "a": 1 }, + { "px": [88,72], "src": [224,112], "f": 0, "t": 476, "d": [281], "a": 1 }, + { "px": [96,72], "src": [224,112], "f": 0, "t": 476, "d": [282], "a": 1 }, + { "px": [104,72], "src": [224,112], "f": 0, "t": 476, "d": [283], "a": 1 }, + { "px": [112,72], "src": [224,112], "f": 0, "t": 476, "d": [284], "a": 1 }, + { "px": [120,72], "src": [224,112], "f": 0, "t": 476, "d": [285], "a": 1 }, + { "px": [128,72], "src": [224,112], "f": 0, "t": 476, "d": [286], "a": 1 }, + { "px": [136,72], "src": [224,112], "f": 0, "t": 476, "d": [287], "a": 1 }, + { "px": [144,72], "src": [224,112], "f": 0, "t": 476, "d": [288], "a": 1 }, + { "px": [152,72], "src": [224,112], "f": 0, "t": 476, "d": [289], "a": 1 }, + { "px": [160,72], "src": [224,112], "f": 0, "t": 476, "d": [290], "a": 1 }, + { "px": [168,72], "src": [224,112], "f": 0, "t": 476, "d": [291], "a": 1 }, + { "px": [176,72], "src": [224,112], "f": 0, "t": 476, "d": [292], "a": 1 }, + { "px": [184,72], "src": [224,112], "f": 0, "t": 476, "d": [293], "a": 1 }, + { "px": [192,72], "src": [224,112], "f": 0, "t": 476, "d": [294], "a": 1 }, + { "px": [200,72], "src": [224,112], "f": 0, "t": 476, "d": [295], "a": 1 }, + { "px": [208,72], "src": [224,112], "f": 0, "t": 476, "d": [296], "a": 1 }, + { "px": [216,72], "src": [224,112], "f": 0, "t": 476, "d": [297], "a": 1 }, + { "px": [224,72], "src": [224,112], "f": 0, "t": 476, "d": [298], "a": 1 }, + { "px": [232,72], "src": [232,112], "f": 0, "t": 477, "d": [299], "a": 1 }, + { "px": [0,80], "src": [216,112], "f": 0, "t": 475, "d": [300], "a": 1 }, + { "px": [8,80], "src": [224,112], "f": 0, "t": 476, "d": [301], "a": 1 }, + { "px": [16,80], "src": [224,112], "f": 0, "t": 476, "d": [302], "a": 1 }, + { "px": [24,80], "src": [224,112], "f": 0, "t": 476, "d": [303], "a": 1 }, + { "px": [32,80], "src": [224,112], "f": 0, "t": 476, "d": [304], "a": 1 }, + { "px": [40,80], "src": [224,112], "f": 0, "t": 476, "d": [305], "a": 1 }, + { "px": [48,80], "src": [224,112], "f": 0, "t": 476, "d": [306], "a": 1 }, + { "px": [56,80], "src": [224,112], "f": 0, "t": 476, "d": [307], "a": 1 }, + { "px": [64,80], "src": [224,112], "f": 0, "t": 476, "d": [308], "a": 1 }, + { "px": [72,80], "src": [224,112], "f": 0, "t": 476, "d": [309], "a": 1 }, + { "px": [80,80], "src": [224,112], "f": 0, "t": 476, "d": [310], "a": 1 }, + { "px": [88,80], "src": [224,112], "f": 0, "t": 476, "d": [311], "a": 1 }, + { "px": [96,80], "src": [224,112], "f": 0, "t": 476, "d": [312], "a": 1 }, + { "px": [104,80], "src": [224,112], "f": 0, "t": 476, "d": [313], "a": 1 }, + { "px": [112,80], "src": [224,112], "f": 0, "t": 476, "d": [314], "a": 1 }, + { "px": [120,80], "src": [224,112], "f": 0, "t": 476, "d": [315], "a": 1 }, + { "px": [128,80], "src": [224,112], "f": 0, "t": 476, "d": [316], "a": 1 }, + { "px": [136,80], "src": [224,112], "f": 0, "t": 476, "d": [317], "a": 1 }, + { "px": [144,80], "src": [224,112], "f": 0, "t": 476, "d": [318], "a": 1 }, + { "px": [152,80], "src": [224,112], "f": 0, "t": 476, "d": [319], "a": 1 }, + { "px": [160,80], "src": [224,112], "f": 0, "t": 476, "d": [320], "a": 1 }, + { "px": [168,80], "src": [224,112], "f": 0, "t": 476, "d": [321], "a": 1 }, + { "px": [176,80], "src": [224,112], "f": 0, "t": 476, "d": [322], "a": 1 }, + { "px": [184,80], "src": [224,112], "f": 0, "t": 476, "d": [323], "a": 1 }, + { "px": [192,80], "src": [224,112], "f": 0, "t": 476, "d": [324], "a": 1 }, + { "px": [200,80], "src": [224,112], "f": 0, "t": 476, "d": [325], "a": 1 }, + { "px": [208,80], "src": [224,112], "f": 0, "t": 476, "d": [326], "a": 1 }, + { "px": [216,80], "src": [224,112], "f": 0, "t": 476, "d": [327], "a": 1 }, + { "px": [224,80], "src": [224,112], "f": 0, "t": 476, "d": [328], "a": 1 }, + { "px": [232,80], "src": [232,112], "f": 0, "t": 477, "d": [329], "a": 1 }, + { "px": [0,88], "src": [216,112], "f": 0, "t": 475, "d": [330], "a": 1 }, + { "px": [8,88], "src": [224,112], "f": 0, "t": 476, "d": [331], "a": 1 }, + { "px": [16,88], "src": [224,112], "f": 0, "t": 476, "d": [332], "a": 1 }, + { "px": [24,88], "src": [224,112], "f": 0, "t": 476, "d": [333], "a": 1 }, + { "px": [32,88], "src": [224,112], "f": 0, "t": 476, "d": [334], "a": 1 }, + { "px": [40,88], "src": [224,112], "f": 0, "t": 476, "d": [335], "a": 1 }, + { "px": [48,88], "src": [224,112], "f": 0, "t": 476, "d": [336], "a": 1 }, + { "px": [56,88], "src": [224,112], "f": 0, "t": 476, "d": [337], "a": 1 }, + { "px": [64,88], "src": [224,112], "f": 0, "t": 476, "d": [338], "a": 1 }, + { "px": [72,88], "src": [224,112], "f": 0, "t": 476, "d": [339], "a": 1 }, + { "px": [80,88], "src": [224,112], "f": 0, "t": 476, "d": [340], "a": 1 }, + { "px": [88,88], "src": [224,112], "f": 0, "t": 476, "d": [341], "a": 1 }, + { "px": [96,88], "src": [224,112], "f": 0, "t": 476, "d": [342], "a": 1 }, + { "px": [104,88], "src": [224,112], "f": 0, "t": 476, "d": [343], "a": 1 }, + { "px": [112,88], "src": [224,112], "f": 0, "t": 476, "d": [344], "a": 1 }, + { "px": [120,88], "src": [224,112], "f": 0, "t": 476, "d": [345], "a": 1 }, + { "px": [128,88], "src": [224,112], "f": 0, "t": 476, "d": [346], "a": 1 }, + { "px": [136,88], "src": [224,112], "f": 0, "t": 476, "d": [347], "a": 1 }, + { "px": [144,88], "src": [224,112], "f": 0, "t": 476, "d": [348], "a": 1 }, + { "px": [152,88], "src": [224,112], "f": 0, "t": 476, "d": [349], "a": 1 }, + { "px": [160,88], "src": [224,112], "f": 0, "t": 476, "d": [350], "a": 1 }, + { "px": [168,88], "src": [224,112], "f": 0, "t": 476, "d": [351], "a": 1 }, + { "px": [176,88], "src": [224,112], "f": 0, "t": 476, "d": [352], "a": 1 }, + { "px": [184,88], "src": [224,112], "f": 0, "t": 476, "d": [353], "a": 1 }, + { "px": [192,88], "src": [224,112], "f": 0, "t": 476, "d": [354], "a": 1 }, + { "px": [200,88], "src": [224,112], "f": 0, "t": 476, "d": [355], "a": 1 }, + { "px": [208,88], "src": [224,112], "f": 0, "t": 476, "d": [356], "a": 1 }, + { "px": [216,88], "src": [224,112], "f": 0, "t": 476, "d": [357], "a": 1 }, + { "px": [224,88], "src": [224,112], "f": 0, "t": 476, "d": [358], "a": 1 }, + { "px": [232,88], "src": [232,112], "f": 0, "t": 477, "d": [359], "a": 1 }, + { "px": [0,96], "src": [216,112], "f": 0, "t": 475, "d": [360], "a": 1 }, + { "px": [8,96], "src": [224,112], "f": 0, "t": 476, "d": [361], "a": 1 }, + { "px": [16,96], "src": [224,112], "f": 0, "t": 476, "d": [362], "a": 1 }, + { "px": [24,96], "src": [224,112], "f": 0, "t": 476, "d": [363], "a": 1 }, + { "px": [32,96], "src": [224,112], "f": 0, "t": 476, "d": [364], "a": 1 }, + { "px": [40,96], "src": [224,112], "f": 0, "t": 476, "d": [365], "a": 1 }, + { "px": [48,96], "src": [224,112], "f": 0, "t": 476, "d": [366], "a": 1 }, + { "px": [56,96], "src": [224,112], "f": 0, "t": 476, "d": [367], "a": 1 }, + { "px": [64,96], "src": [224,112], "f": 0, "t": 476, "d": [368], "a": 1 }, + { "px": [72,96], "src": [224,112], "f": 0, "t": 476, "d": [369], "a": 1 }, + { "px": [80,96], "src": [224,112], "f": 0, "t": 476, "d": [370], "a": 1 }, + { "px": [88,96], "src": [224,112], "f": 0, "t": 476, "d": [371], "a": 1 }, + { "px": [96,96], "src": [224,112], "f": 0, "t": 476, "d": [372], "a": 1 }, + { "px": [104,96], "src": [224,112], "f": 0, "t": 476, "d": [373], "a": 1 }, + { "px": [112,96], "src": [224,112], "f": 0, "t": 476, "d": [374], "a": 1 }, + { "px": [120,96], "src": [224,112], "f": 0, "t": 476, "d": [375], "a": 1 }, + { "px": [128,96], "src": [224,112], "f": 0, "t": 476, "d": [376], "a": 1 }, + { "px": [136,96], "src": [224,112], "f": 0, "t": 476, "d": [377], "a": 1 }, + { "px": [144,96], "src": [224,112], "f": 0, "t": 476, "d": [378], "a": 1 }, + { "px": [152,96], "src": [224,112], "f": 0, "t": 476, "d": [379], "a": 1 }, + { "px": [160,96], "src": [224,112], "f": 0, "t": 476, "d": [380], "a": 1 }, + { "px": [168,96], "src": [224,112], "f": 0, "t": 476, "d": [381], "a": 1 }, + { "px": [176,96], "src": [224,112], "f": 0, "t": 476, "d": [382], "a": 1 }, + { "px": [184,96], "src": [224,112], "f": 0, "t": 476, "d": [383], "a": 1 }, + { "px": [192,96], "src": [224,112], "f": 0, "t": 476, "d": [384], "a": 1 }, + { "px": [200,96], "src": [224,112], "f": 0, "t": 476, "d": [385], "a": 1 }, + { "px": [208,96], "src": [224,112], "f": 0, "t": 476, "d": [386], "a": 1 }, + { "px": [216,96], "src": [224,112], "f": 0, "t": 476, "d": [387], "a": 1 }, + { "px": [224,96], "src": [224,112], "f": 0, "t": 476, "d": [388], "a": 1 }, + { "px": [232,96], "src": [232,112], "f": 0, "t": 477, "d": [389], "a": 1 }, + { "px": [0,104], "src": [216,112], "f": 0, "t": 475, "d": [390], "a": 1 }, + { "px": [8,104], "src": [224,112], "f": 0, "t": 476, "d": [391], "a": 1 }, + { "px": [16,104], "src": [224,112], "f": 0, "t": 476, "d": [392], "a": 1 }, + { "px": [24,104], "src": [224,112], "f": 0, "t": 476, "d": [393], "a": 1 }, + { "px": [32,104], "src": [224,112], "f": 0, "t": 476, "d": [394], "a": 1 }, + { "px": [40,104], "src": [224,112], "f": 0, "t": 476, "d": [395], "a": 1 }, + { "px": [48,104], "src": [224,112], "f": 0, "t": 476, "d": [396], "a": 1 }, + { "px": [56,104], "src": [224,112], "f": 0, "t": 476, "d": [397], "a": 1 }, + { "px": [64,104], "src": [224,112], "f": 0, "t": 476, "d": [398], "a": 1 }, + { "px": [72,104], "src": [224,112], "f": 0, "t": 476, "d": [399], "a": 1 }, + { "px": [80,104], "src": [224,112], "f": 0, "t": 476, "d": [400], "a": 1 }, + { "px": [88,104], "src": [224,112], "f": 0, "t": 476, "d": [401], "a": 1 }, + { "px": [96,104], "src": [224,112], "f": 0, "t": 476, "d": [402], "a": 1 }, + { "px": [104,104], "src": [224,112], "f": 0, "t": 476, "d": [403], "a": 1 }, + { "px": [112,104], "src": [224,112], "f": 0, "t": 476, "d": [404], "a": 1 }, + { "px": [120,104], "src": [224,112], "f": 0, "t": 476, "d": [405], "a": 1 }, + { "px": [128,104], "src": [224,112], "f": 0, "t": 476, "d": [406], "a": 1 }, + { "px": [136,104], "src": [224,112], "f": 0, "t": 476, "d": [407], "a": 1 }, + { "px": [144,104], "src": [224,112], "f": 0, "t": 476, "d": [408], "a": 1 }, + { "px": [152,104], "src": [224,112], "f": 0, "t": 476, "d": [409], "a": 1 }, + { "px": [160,104], "src": [224,112], "f": 0, "t": 476, "d": [410], "a": 1 }, + { "px": [168,104], "src": [224,112], "f": 0, "t": 476, "d": [411], "a": 1 }, + { "px": [176,104], "src": [224,112], "f": 0, "t": 476, "d": [412], "a": 1 }, + { "px": [184,104], "src": [224,112], "f": 0, "t": 476, "d": [413], "a": 1 }, + { "px": [192,104], "src": [224,112], "f": 0, "t": 476, "d": [414], "a": 1 }, + { "px": [200,104], "src": [224,112], "f": 0, "t": 476, "d": [415], "a": 1 }, + { "px": [208,104], "src": [224,112], "f": 0, "t": 476, "d": [416], "a": 1 }, + { "px": [216,104], "src": [224,112], "f": 0, "t": 476, "d": [417], "a": 1 }, + { "px": [224,104], "src": [224,112], "f": 0, "t": 476, "d": [418], "a": 1 }, + { "px": [232,104], "src": [232,112], "f": 0, "t": 477, "d": [419], "a": 1 }, + { "px": [0,112], "src": [216,104], "f": 2, "t": 443, "d": [420], "a": 1 }, + { "px": [8,112], "src": [224,120], "f": 0, "t": 508, "d": [421], "a": 1 }, + { "px": [16,112], "src": [224,120], "f": 0, "t": 508, "d": [422], "a": 1 }, + { "px": [24,112], "src": [224,120], "f": 0, "t": 508, "d": [423], "a": 1 }, + { "px": [32,112], "src": [224,120], "f": 0, "t": 508, "d": [424], "a": 1 }, + { "px": [40,112], "src": [224,120], "f": 0, "t": 508, "d": [425], "a": 1 }, + { "px": [48,112], "src": [224,120], "f": 0, "t": 508, "d": [426], "a": 1 }, + { "px": [56,112], "src": [224,120], "f": 0, "t": 508, "d": [427], "a": 1 }, + { "px": [64,112], "src": [224,120], "f": 0, "t": 508, "d": [428], "a": 1 }, + { "px": [72,112], "src": [224,120], "f": 0, "t": 508, "d": [429], "a": 1 }, + { "px": [80,112], "src": [224,120], "f": 0, "t": 508, "d": [430], "a": 1 }, + { "px": [88,112], "src": [224,120], "f": 0, "t": 508, "d": [431], "a": 1 }, + { "px": [96,112], "src": [224,120], "f": 0, "t": 508, "d": [432], "a": 1 }, + { "px": [104,112], "src": [224,120], "f": 0, "t": 508, "d": [433], "a": 1 }, + { "px": [112,112], "src": [224,120], "f": 0, "t": 508, "d": [434], "a": 1 }, + { "px": [120,112], "src": [224,120], "f": 0, "t": 508, "d": [435], "a": 1 }, + { "px": [128,112], "src": [224,120], "f": 0, "t": 508, "d": [436], "a": 1 }, + { "px": [136,112], "src": [224,120], "f": 0, "t": 508, "d": [437], "a": 1 }, + { "px": [144,112], "src": [224,120], "f": 0, "t": 508, "d": [438], "a": 1 }, + { "px": [152,112], "src": [224,120], "f": 0, "t": 508, "d": [439], "a": 1 }, + { "px": [160,112], "src": [224,120], "f": 0, "t": 508, "d": [440], "a": 1 }, + { "px": [168,112], "src": [224,120], "f": 0, "t": 508, "d": [441], "a": 1 }, + { "px": [176,112], "src": [224,120], "f": 0, "t": 508, "d": [442], "a": 1 }, + { "px": [184,112], "src": [224,120], "f": 0, "t": 508, "d": [443], "a": 1 }, + { "px": [192,112], "src": [224,120], "f": 0, "t": 508, "d": [444], "a": 1 }, + { "px": [200,112], "src": [224,120], "f": 0, "t": 508, "d": [445], "a": 1 }, + { "px": [208,112], "src": [224,120], "f": 0, "t": 508, "d": [446], "a": 1 }, + { "px": [216,112], "src": [224,120], "f": 0, "t": 508, "d": [447], "a": 1 }, + { "px": [224,112], "src": [224,120], "f": 0, "t": 508, "d": [448], "a": 1 }, + { "px": [232,112], "src": [216,104], "f": 3, "t": 443, "d": [449], "a": 1 } + ], + "entityInstances": [] + } + ], + "__neighbours": [] + }, + { + "identifier": "MusicEditor", + "iid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "uid": 89, + "worldX": 816, + "worldY": 0, + "worldDepth": 0, + "pxWid": 384, + "pxHei": 256, + "__bgColor": "#696A79", + "bgColor": null, + "useAutoIdentifier": false, + "bgRelPath": null, + "bgPos": null, + "bgPivotX": 0.5, + "bgPivotY": 0.5, + "__smartColor": "#ADADB5", + "__bgPos": null, + "externalRelPath": null, + "fieldInstances": [], + "layerInstances": [ + { + "__identifier": "Widgets", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelId": 89, + "layerDefUid": 6, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 4288804, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "TextButton", + "__grid": [45,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "07d9dba0-fa90-11f0-9ead-05ea782fe5f1", + "width": 18, + "height": 16, + "defUid": 78, + "px": [360,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↧↨", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↧↨"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Red", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Red"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": "Save", "__tile": null, "defUid": 84, "realEditorValues": [{ + "id": "V_String", + "params": ["Save"] + }] } + ], + "__worldX": 1176, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [39,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "0883b030-fa90-11f0-9ead-999e302540dc", + "width": 44, + "height": 16, + "defUid": 78, + "px": [314,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "Export", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["Export"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["LigthBlue"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 1130, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [0,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "0e6a27e0-fa90-11f0-9ead-779ca56901ec", + "width": 88, + "height": 16, + "defUid": 78, + "px": [4,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "←→ Instrument", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["←→ Instrument"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Green", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Green"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": "tiny-instrument-editor.lua", "__tile": null, "defUid": 83, "realEditorValues": [{ + "id": "V_String", + "params": ["tiny-instrument-editor.lua"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 820, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [12,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "0edf30d0-fa90-11f0-9ead-dfab2f2037af", + "width": 96, + "height": 16, + "defUid": 78, + "px": [96,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↑↓ sound effect", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↑↓ sound effect"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": false, "__tile": null, "defUid": 80, "realEditorValues": [] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Yellow", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Yellow"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": "tiny-sfx-editor.lua", "__tile": null, "defUid": 83, "realEditorValues": [{ + "id": "V_String", + "params": ["tiny-sfx-editor.lua"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 912, + "__worldY": 4 + }, + { + "__identifier": "TextButton", + "__grid": [24,0], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#5A6988", + "iid": "0f44a960-fa90-11f0-9ead-559d7b92e52e", + "width": 60, + "height": 16, + "defUid": 78, + "px": [196,4], + "fieldInstances": [ + { "__identifier": "Label", "__type": "String", "__value": "↔↕Music", "__tile": null, "defUid": 79, "realEditorValues": [{ + "id": "V_String", + "params": ["↔↕Music"] + }] }, + { "__identifier": "IsActive", "__type": "Bool", "__value": true, "__tile": null, "defUid": 80, "realEditorValues": [{ + "id": "V_Bool", + "params": [ true ] + }] }, + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Orange", "__tile": null, "defUid": 82, "realEditorValues": [{ + "id": "V_String", + "params": ["Orange"] + }] }, + { "__identifier": "TinyExit", "__type": "String", "__value": null, "__tile": null, "defUid": 83, "realEditorValues": [] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 84, "realEditorValues": [] } + ], + "__worldX": 1012, + "__worldY": 4 + }, + { + "__identifier": "Speaker", + "__grid": [6,23], + "__pivot": [0,0], + "__tags": [], + "__tile": { "tilesetUid": 72, "x": 208, "y": 0, "w": 48, "h": 96 }, + "__smartColor": "#C0CBDC", + "iid": "0e7e1980-fa90-11f0-9ead-d1744692a3c3", + "width": 98, + "height": 96, + "defUid": 76, + "px": [48,184], + "fieldInstances": [], + "__worldX": 864, + "__worldY": 184 + }, + { + "__identifier": "Button", + "__grid": [6,4], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "348200b0-fa90-11f0-9ead-e99c8684d40d", + "width": 13, + "height": 13, + "defUid": 1, + "px": [48,38], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": null, "__tile": null, "defUid": 13, "realEditorValues": [] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Play", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Play"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 864, + "__worldY": 38 + }, + { + "__identifier": "Dropdown", + "__grid": [6,11], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "3229ea70-fa90-11f0-9ead-b333f7210bae", + "width": 120, + "height": 16, + "defUid": 2, + "px": [54,92], + "fieldInstances": [], + "__worldX": 870, + "__worldY": 92 + }, + { + "__identifier": "Dropdown", + "__grid": [7,18], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "32cf0410-fa90-11f0-9ead-e9c92c8921e0", + "width": 112, + "height": 16, + "defUid": 2, + "px": [62,144], + "fieldInstances": [], + "__worldX": 878, + "__worldY": 144 + }, + { + "__identifier": "Dropdown", + "__grid": [25,14], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "34003110-fa90-11f0-9ead-ed6beeb66f81", + "width": 152, + "height": 16, + "defUid": 2, + "px": [206,112], + "fieldInstances": [], + "__worldX": 1022, + "__worldY": 112 + }, + { + "__identifier": "Dropdown", + "__grid": [25,20], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "345f8f20-fa90-11f0-9ead-936860ff124d", + "width": 152, + "height": 16, + "defUid": 2, + "px": [206,160], + "fieldInstances": [], + "__worldX": 1022, + "__worldY": 160 + }, + { + "__identifier": "Fader", + "__grid": [22,18], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "3be733b0-fa90-11f0-9ead-a9619424f073", + "width": 8, + "height": 32, + "defUid": 77, + "px": [176,144], + "fieldInstances": [], + "__worldX": 992, + "__worldY": 144 + }, + { + "__identifier": "Fader", + "__grid": [22,12], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "3c72d1e0-fa90-11f0-9ead-b169c3291a2f", + "width": 8, + "height": 32, + "defUid": 77, + "px": [176,96], + "fieldInstances": [], + "__worldX": 992, + "__worldY": 96 + }, + { + "__identifier": "Fader", + "__grid": [45,12], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "3d1357a0-fa90-11f0-9ead-3154bb31f3e1", + "width": 8, + "height": 32, + "defUid": 77, + "px": [360,96], + "fieldInstances": [], + "__worldX": 1176, + "__worldY": 96 + }, + { + "__identifier": "Fader", + "__grid": [45,18], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "3d7b8f50-fa90-11f0-9ead-c907c95f81b4", + "width": 8, + "height": 32, + "defUid": 77, + "px": [360,144], + "fieldInstances": [], + "__worldX": 1176, + "__worldY": 144 + }, + { + "__identifier": "Dropdown", + "__grid": [3,8], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "cde96ed0-fa90-11f0-9ead-75069c70a8df", + "width": 160, + "height": 16, + "defUid": 2, + "px": [24,68], + "fieldInstances": [], + "__worldX": 840, + "__worldY": 68 + }, + { + "__identifier": "MusicGenerator", + "__grid": [23,24], + "__pivot": [0,0], + "__tags": [], + "__tile": null, + "__smartColor": "#3A4466", + "iid": "3578e860-fa90-11f0-9ead-db1e9bae1b7b", + "width": 16, + "height": 16, + "defUid": 90, + "px": [184,192], + "fieldInstances": [ + { "__identifier": "DrumPattern", "__type": "EntityRef", "__value": { + "entityIid": "345f8f20-fa90-11f0-9ead-936860ff124d", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 91, "realEditorValues": [{ + "id": "V_String", + "params": ["345f8f20-fa90-11f0-9ead-936860ff124d"] + }] }, + { "__identifier": "DrumVolume", "__type": "EntityRef", "__value": { + "entityIid": "3d7b8f50-fa90-11f0-9ead-c907c95f81b4", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 92, "realEditorValues": [{ + "id": "V_String", + "params": ["3d7b8f50-fa90-11f0-9ead-c907c95f81b4"] + }] }, + { "__identifier": "MusicTheme", "__type": "EntityRef", "__value": { + "entityIid": "cde96ed0-fa90-11f0-9ead-75069c70a8df", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 94, "realEditorValues": [{ + "id": "V_String", + "params": ["cde96ed0-fa90-11f0-9ead-75069c70a8df"] + }] }, + { "__identifier": "MusicScale", "__type": "EntityRef", "__value": { + "entityIid": "384e4820-fa90-11f0-9ead-cbd74a75cb33", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 93, "realEditorValues": [{ + "id": "V_String", + "params": ["384e4820-fa90-11f0-9ead-cbd74a75cb33"] + }] }, + { "__identifier": "LeadInstrument", "__type": "EntityRef", "__value": { + "entityIid": "34003110-fa90-11f0-9ead-ed6beeb66f81", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 95, "realEditorValues": [{ + "id": "V_String", + "params": ["34003110-fa90-11f0-9ead-ed6beeb66f81"] + }] }, + { "__identifier": "LeadVolume", "__type": "EntityRef", "__value": { + "entityIid": "3d1357a0-fa90-11f0-9ead-3154bb31f3e1", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 96, "realEditorValues": [{ + "id": "V_String", + "params": ["3d1357a0-fa90-11f0-9ead-3154bb31f3e1"] + }] }, + { "__identifier": "BassInstrument", "__type": "EntityRef", "__value": { + "entityIid": "32cf0410-fa90-11f0-9ead-e9c92c8921e0", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 97, "realEditorValues": [{ + "id": "V_String", + "params": ["32cf0410-fa90-11f0-9ead-e9c92c8921e0"] + }] }, + { "__identifier": "BassVolume", "__type": "EntityRef", "__value": { + "entityIid": "3be733b0-fa90-11f0-9ead-a9619424f073", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 98, "realEditorValues": [{ + "id": "V_String", + "params": ["3be733b0-fa90-11f0-9ead-a9619424f073"] + }] }, + { "__identifier": "RythmVolume", "__type": "EntityRef", "__value": { + "entityIid": "3c72d1e0-fa90-11f0-9ead-b169c3291a2f", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 99, "realEditorValues": [{ + "id": "V_String", + "params": ["3c72d1e0-fa90-11f0-9ead-b169c3291a2f"] + }] }, + { "__identifier": "RythmChordProgression", "__type": "EntityRef", "__value": { + "entityIid": "48a63de0-fa90-11f0-9ead-3711375dd545", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 100, "realEditorValues": [{ + "id": "V_String", + "params": ["48a63de0-fa90-11f0-9ead-3711375dd545"] + }] }, + { "__identifier": "RythmInstrument", "__type": "EntityRef", "__value": { + "entityIid": "3229ea70-fa90-11f0-9ead-b333f7210bae", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 101, "realEditorValues": [{ + "id": "V_String", + "params": ["3229ea70-fa90-11f0-9ead-b333f7210bae"] + }] }, + { "__identifier": "Play", "__type": "EntityRef", "__value": { + "entityIid": "348200b0-fa90-11f0-9ead-e99c8684d40d", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 102, "realEditorValues": [{ + "id": "V_String", + "params": ["348200b0-fa90-11f0-9ead-e99c8684d40d"] + }] }, + { "__identifier": "Volume", "__type": "EntityRef", "__value": { + "entityIid": "04d6c670-fa90-11f0-9ead-fded0c18e8e2", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 103, "realEditorValues": [{ + "id": "V_String", + "params": ["04d6c670-fa90-11f0-9ead-fded0c18e8e2"] + }] }, + { "__identifier": "Selector", "__type": "EntityRef", "__value": { + "entityIid": "28531860-fa90-11f0-9ead-3f8e285343d4", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 104, "realEditorValues": [{ + "id": "V_String", + "params": ["28531860-fa90-11f0-9ead-3f8e285343d4"] + }] }, + { "__identifier": "Export", "__type": "EntityRef", "__value": { + "entityIid": "0883b030-fa90-11f0-9ead-999e302540dc", + "layerIid": "c5000ed1-fa90-11f0-9ead-a1240c4664a1", + "levelIid": "c5000ed0-fa90-11f0-9ead-c7908e5e7535", + "worldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" + }, "__tile": null, "defUid": 105, "realEditorValues": [{ + "id": "V_String", + "params": ["0883b030-fa90-11f0-9ead-999e302540dc"] + }] } + ], + "__worldX": 1000, + "__worldY": 192 + }, + { + "__identifier": "Dropdown", + "__grid": [2,14], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "7343ffe0-fa90-11f0-9ead-576269b9b089", + "width": 152, + "height": 16, + "defUid": 2, + "px": [21,112], + "fieldInstances": [], + "__worldX": 837, + "__worldY": 112 + }, + { + "__identifier": "Fader", + "__grid": [19,23], + "__pivot": [0,0], + "__tags": [ "Widget", "PercentOutput" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 24, "w": 8, "h": 24 }, + "__smartColor": "#8B9BB4", + "iid": "04d6c670-fa90-11f0-9ead-fded0c18e8e2", + "width": 8, + "height": 56, + "defUid": 77, + "px": [152,184], + "fieldInstances": [], + "__worldX": 968, + "__worldY": 184 + }, + { + "__identifier": "Dropdown", + "__grid": [12,4], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "28531860-fa90-11f0-9ead-3f8e285343d4", + "width": 192, + "height": 16, + "defUid": 2, + "px": [100,35], + "fieldInstances": [], + "__worldX": 916, + "__worldY": 35 + }, + { + "__identifier": "Button", + "__grid": [36,4], + "__pivot": [0,0], + "__tags": [ "Widget", "Action" ], + "__tile": { "tilesetUid": 72, "x": 0, "y": 56, "w": 16, "h": 16 }, + "__smartColor": "#BE4A2F", + "iid": "2b9990d0-fa90-11f0-9ead-a9c4de7670bb", + "width": 13, + "height": 13, + "defUid": 1, + "px": [293,37], + "fieldInstances": [ + { "__identifier": "Modal", "__type": "String", "__value": "NameModal", "__tile": null, "defUid": 13, "realEditorValues": [{ + "id": "V_String", + "params": ["NameModal"] + }] }, + { "__identifier": "IconName", "__type": "LocalEnum.Icon", "__value": "Gear", "__tile": null, "defUid": 15, "realEditorValues": [{ + "id": "V_String", + "params": ["Gear"] + }] }, + { "__identifier": "Action", "__type": "LocalEnum.Action", "__value": null, "__tile": null, "defUid": 18, "realEditorValues": [] }, + { "__identifier": "Layer", "__type": "String", "__value": null, "__tile": null, "defUid": 28, "realEditorValues": [] } + ], + "__worldX": 1109, + "__worldY": 37 + }, + { + "__identifier": "Dropdown", + "__grid": [26,8], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "384e4820-fa90-11f0-9ead-cbd74a75cb33", + "width": 160, + "height": 16, + "defUid": 2, + "px": [208,68], + "fieldInstances": [], + "__worldX": 1024, + "__worldY": 68 + }, + { + "__identifier": "Dropdown", + "__grid": [2,20], + "__pivot": [0,0], + "__tags": ["Widget"], + "__tile": { "tilesetUid": 72, "x": 24, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#D77643", + "iid": "48a63de0-fa90-11f0-9ead-3711375dd545", + "width": 152, + "height": 16, + "defUid": 2, + "px": [22,160], + "fieldInstances": [], + "__worldX": 838, + "__worldY": 160 + } + ] + }, + { + "__identifier": "Panels", + "__type": "Entities", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": null, + "__tilesetRelPath": null, + "iid": "c5000ed2-fa90-11f0-9ead-657cdaed2cac", + "levelId": 89, + "layerDefUid": 75, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 1444730, + "overrideTilesetUid": null, + "gridTiles": [], + "entityInstances": [ + { + "__identifier": "Panel", + "__grid": [2,11], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "2a823bc0-fa90-11f0-9ead-fb15f3d46ffd", + "width": 176, + "height": 48, + "defUid": 71, + "px": [16,88], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "HardBlue", "__tile": null, "defUid": 74, "realEditorValues": [{ + "id": "V_String", + "params": ["HardBlue"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": "Rythm", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Rythm"] + }] } + ], + "__worldX": 832, + "__worldY": 88 + }, + { + "__identifier": "Panel", + "__grid": [25,11], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "e52bcaa0-fa90-11f0-9ead-e1a0b870b228", + "width": 176, + "height": 48, + "defUid": 71, + "px": [200,88], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Orange", "__tile": null, "defUid": 74, "realEditorValues": [{ + "id": "V_String", + "params": ["Orange"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": "Melody", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Melody"] + }] } + ], + "__worldX": 1016, + "__worldY": 88 + }, + { + "__identifier": "Panel", + "__grid": [2,17], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "e7972550-fa90-11f0-9ead-af6100a30ea6", + "width": 176, + "height": 48, + "defUid": 71, + "px": [16,136], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Purple", "__tile": null, "defUid": 74, "realEditorValues": [{ + "id": "V_String", + "params": ["Purple"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": "Bass", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Bass"] + }] } + ], + "__worldX": 832, + "__worldY": 136 + }, + { + "__identifier": "Panel", + "__grid": [25,17], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "e9b4d530-fa90-11f0-9ead-930f3c8a73eb", + "width": 176, + "height": 48, + "defUid": 71, + "px": [200,136], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "Yellow", "__tile": null, "defUid": 74, "realEditorValues": [{ + "id": "V_String", + "params": ["Yellow"] + }] }, + { "__identifier": "Label", "__type": "String", "__value": "Drums", "__tile": null, "defUid": 81, "realEditorValues": [{ + "id": "V_String", + "params": ["Drums"] + }] } + ], + "__worldX": 1016, + "__worldY": 136 + }, + { + "__identifier": "Panel", + "__grid": [2,8], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "2bddc890-fa90-11f0-9ead-4be14b174530", + "width": 176, + "height": 24, + "defUid": 71, + "px": [16,64], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 832, + "__worldY": 64 + }, + { + "__identifier": "Panel", + "__grid": [25,8], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "c1d82370-fa90-11f0-9ead-0dd1fa44b09d", + "width": 176, + "height": 24, + "defUid": 71, + "px": [200,64], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 1016, + "__worldY": 64 + }, + { + "__identifier": "Panel", + "__grid": [10,4], + "__pivot": [0,0], + "__tags": ["panel"], + "__tile": { "tilesetUid": 72, "x": 0, "y": 0, "w": 24, "h": 24 }, + "__smartColor": "#FFFFFF", + "iid": "bc4784a0-21a0-11f1-ba55-ed020f8e5258", + "width": 232, + "height": 24, + "defUid": 71, + "px": [80,32], + "fieldInstances": [ + { "__identifier": "Variant", "__type": "LocalEnum.Variant", "__value": "LigthBlue", "__tile": null, "defUid": 74, "realEditorValues": [] }, + { "__identifier": "Label", "__type": "String", "__value": null, "__tile": null, "defUid": 81, "realEditorValues": [] } + ], + "__worldX": 896, + "__worldY": 32 + } + ] + }, + { + "__identifier": "Background", + "__type": "Tiles", + "__cWid": 48, + "__cHei": 32, + "__gridSize": 8, + "__opacity": 1, + "__pxTotalOffsetX": 0, + "__pxTotalOffsetY": 0, + "__tilesetDefUid": 72, + "__tilesetRelPath": "sprite-sheet.png", + "iid": "c5000ed3-fa90-11f0-9ead-4986d3c8a734", + "levelId": 89, + "layerDefUid": 10, + "pxOffsetX": 0, + "pxOffsetY": 0, + "visible": true, + "optionalRules": [], + "intGridCsv": [], + "autoLayerTiles": [], + "seed": 4337250, + "overrideTilesetUid": null, + "gridTiles": [ + { "px": [0,0], "src": [216,104], "f": 0, "t": 443, "d": [0], "a": 1 }, + { "px": [8,0], "src": [224,104], "f": 0, "t": 444, "d": [1], "a": 1 }, + { "px": [16,0], "src": [224,104], "f": 0, "t": 444, "d": [2], "a": 1 }, + { "px": [24,0], "src": [224,104], "f": 0, "t": 444, "d": [3], "a": 1 }, + { "px": [32,0], "src": [224,104], "f": 0, "t": 444, "d": [4], "a": 1 }, + { "px": [40,0], "src": [224,104], "f": 0, "t": 444, "d": [5], "a": 1 }, + { "px": [48,0], "src": [224,104], "f": 0, "t": 444, "d": [6], "a": 1 }, + { "px": [56,0], "src": [224,104], "f": 0, "t": 444, "d": [7], "a": 1 }, + { "px": [64,0], "src": [224,104], "f": 0, "t": 444, "d": [8], "a": 1 }, + { "px": [72,0], "src": [224,104], "f": 0, "t": 444, "d": [9], "a": 1 }, + { "px": [80,0], "src": [224,104], "f": 0, "t": 444, "d": [10], "a": 1 }, + { "px": [88,0], "src": [224,104], "f": 0, "t": 444, "d": [11], "a": 1 }, + { "px": [96,0], "src": [224,104], "f": 0, "t": 444, "d": [12], "a": 1 }, + { "px": [104,0], "src": [224,104], "f": 0, "t": 444, "d": [13], "a": 1 }, + { "px": [112,0], "src": [224,104], "f": 0, "t": 444, "d": [14], "a": 1 }, + { "px": [120,0], "src": [224,104], "f": 0, "t": 444, "d": [15], "a": 1 }, + { "px": [128,0], "src": [224,104], "f": 0, "t": 444, "d": [16], "a": 1 }, + { "px": [136,0], "src": [224,104], "f": 0, "t": 444, "d": [17], "a": 1 }, + { "px": [144,0], "src": [224,104], "f": 0, "t": 444, "d": [18], "a": 1 }, + { "px": [152,0], "src": [224,104], "f": 0, "t": 444, "d": [19], "a": 1 }, + { "px": [160,0], "src": [224,104], "f": 0, "t": 444, "d": [20], "a": 1 }, + { "px": [168,0], "src": [224,104], "f": 0, "t": 444, "d": [21], "a": 1 }, + { "px": [176,0], "src": [224,104], "f": 0, "t": 444, "d": [22], "a": 1 }, + { "px": [184,0], "src": [224,104], "f": 0, "t": 444, "d": [23], "a": 1 }, + { "px": [192,0], "src": [224,104], "f": 0, "t": 444, "d": [24], "a": 1 }, + { "px": [200,0], "src": [224,104], "f": 0, "t": 444, "d": [25], "a": 1 }, + { "px": [208,0], "src": [224,104], "f": 0, "t": 444, "d": [26], "a": 1 }, + { "px": [216,0], "src": [224,104], "f": 0, "t": 444, "d": [27], "a": 1 }, + { "px": [224,0], "src": [224,104], "f": 0, "t": 444, "d": [28], "a": 1 }, + { "px": [232,0], "src": [224,104], "f": 0, "t": 444, "d": [29], "a": 1 }, + { "px": [240,0], "src": [224,104], "f": 0, "t": 444, "d": [30], "a": 1 }, + { "px": [248,0], "src": [224,104], "f": 0, "t": 444, "d": [31], "a": 1 }, + { "px": [256,0], "src": [224,104], "f": 0, "t": 444, "d": [32], "a": 1 }, + { "px": [264,0], "src": [224,104], "f": 0, "t": 444, "d": [33], "a": 1 }, + { "px": [272,0], "src": [224,104], "f": 0, "t": 444, "d": [34], "a": 1 }, + { "px": [280,0], "src": [224,104], "f": 0, "t": 444, "d": [35], "a": 1 }, + { "px": [288,0], "src": [224,104], "f": 0, "t": 444, "d": [36], "a": 1 }, + { "px": [296,0], "src": [224,104], "f": 0, "t": 444, "d": [37], "a": 1 }, + { "px": [304,0], "src": [224,104], "f": 0, "t": 444, "d": [38], "a": 1 }, + { "px": [312,0], "src": [224,104], "f": 0, "t": 444, "d": [39], "a": 1 }, + { "px": [320,0], "src": [224,104], "f": 0, "t": 444, "d": [40], "a": 1 }, + { "px": [328,0], "src": [224,104], "f": 0, "t": 444, "d": [41], "a": 1 }, + { "px": [336,0], "src": [224,104], "f": 0, "t": 444, "d": [42], "a": 1 }, + { "px": [344,0], "src": [224,104], "f": 0, "t": 444, "d": [43], "a": 1 }, + { "px": [352,0], "src": [224,104], "f": 0, "t": 444, "d": [44], "a": 1 }, + { "px": [360,0], "src": [224,104], "f": 0, "t": 444, "d": [45], "a": 1 }, + { "px": [368,0], "src": [224,104], "f": 0, "t": 444, "d": [46], "a": 1 }, + { "px": [376,0], "src": [232,104], "f": 0, "t": 445, "d": [47], "a": 1 }, + { "px": [0,8], "src": [216,112], "f": 0, "t": 475, "d": [48], "a": 1 }, + { "px": [8,8], "src": [224,112], "f": 0, "t": 476, "d": [49], "a": 1 }, + { "px": [16,8], "src": [224,112], "f": 0, "t": 476, "d": [50], "a": 1 }, + { "px": [24,8], "src": [224,112], "f": 0, "t": 476, "d": [51], "a": 1 }, + { "px": [32,8], "src": [224,112], "f": 0, "t": 476, "d": [52], "a": 1 }, + { "px": [40,8], "src": [224,112], "f": 0, "t": 476, "d": [53], "a": 1 }, + { "px": [48,8], "src": [224,112], "f": 0, "t": 476, "d": [54], "a": 1 }, + { "px": [56,8], "src": [224,112], "f": 0, "t": 476, "d": [55], "a": 1 }, + { "px": [64,8], "src": [224,112], "f": 0, "t": 476, "d": [56], "a": 1 }, + { "px": [72,8], "src": [224,112], "f": 0, "t": 476, "d": [57], "a": 1 }, + { "px": [80,8], "src": [224,112], "f": 0, "t": 476, "d": [58], "a": 1 }, + { "px": [88,8], "src": [224,112], "f": 0, "t": 476, "d": [59], "a": 1 }, + { "px": [96,8], "src": [224,112], "f": 0, "t": 476, "d": [60], "a": 1 }, + { "px": [104,8], "src": [224,112], "f": 0, "t": 476, "d": [61], "a": 1 }, + { "px": [112,8], "src": [224,112], "f": 0, "t": 476, "d": [62], "a": 1 }, + { "px": [120,8], "src": [224,112], "f": 0, "t": 476, "d": [63], "a": 1 }, + { "px": [128,8], "src": [224,112], "f": 0, "t": 476, "d": [64], "a": 1 }, + { "px": [136,8], "src": [224,112], "f": 0, "t": 476, "d": [65], "a": 1 }, + { "px": [144,8], "src": [224,112], "f": 0, "t": 476, "d": [66], "a": 1 }, + { "px": [152,8], "src": [224,112], "f": 0, "t": 476, "d": [67], "a": 1 }, + { "px": [160,8], "src": [224,112], "f": 0, "t": 476, "d": [68], "a": 1 }, + { "px": [168,8], "src": [224,112], "f": 0, "t": 476, "d": [69], "a": 1 }, + { "px": [176,8], "src": [224,112], "f": 0, "t": 476, "d": [70], "a": 1 }, + { "px": [184,8], "src": [224,112], "f": 0, "t": 476, "d": [71], "a": 1 }, + { "px": [192,8], "src": [224,112], "f": 0, "t": 476, "d": [72], "a": 1 }, + { "px": [200,8], "src": [224,112], "f": 0, "t": 476, "d": [73], "a": 1 }, + { "px": [208,8], "src": [224,112], "f": 0, "t": 476, "d": [74], "a": 1 }, + { "px": [216,8], "src": [224,112], "f": 0, "t": 476, "d": [75], "a": 1 }, + { "px": [224,8], "src": [224,112], "f": 0, "t": 476, "d": [76], "a": 1 }, + { "px": [232,8], "src": [224,112], "f": 0, "t": 476, "d": [77], "a": 1 }, + { "px": [240,8], "src": [224,112], "f": 0, "t": 476, "d": [78], "a": 1 }, + { "px": [248,8], "src": [224,112], "f": 0, "t": 476, "d": [79], "a": 1 }, + { "px": [256,8], "src": [224,112], "f": 0, "t": 476, "d": [80], "a": 1 }, + { "px": [264,8], "src": [224,112], "f": 0, "t": 476, "d": [81], "a": 1 }, + { "px": [272,8], "src": [224,112], "f": 0, "t": 476, "d": [82], "a": 1 }, + { "px": [280,8], "src": [224,112], "f": 0, "t": 476, "d": [83], "a": 1 }, + { "px": [288,8], "src": [224,112], "f": 0, "t": 476, "d": [84], "a": 1 }, + { "px": [296,8], "src": [224,112], "f": 0, "t": 476, "d": [85], "a": 1 }, + { "px": [304,8], "src": [224,112], "f": 0, "t": 476, "d": [86], "a": 1 }, + { "px": [312,8], "src": [224,112], "f": 0, "t": 476, "d": [87], "a": 1 }, + { "px": [320,8], "src": [224,112], "f": 0, "t": 476, "d": [88], "a": 1 }, + { "px": [328,8], "src": [224,112], "f": 0, "t": 476, "d": [89], "a": 1 }, + { "px": [336,8], "src": [224,112], "f": 0, "t": 476, "d": [90], "a": 1 }, + { "px": [344,8], "src": [224,112], "f": 0, "t": 476, "d": [91], "a": 1 }, + { "px": [352,8], "src": [224,112], "f": 0, "t": 476, "d": [92], "a": 1 }, + { "px": [360,8], "src": [224,112], "f": 0, "t": 476, "d": [93], "a": 1 }, + { "px": [368,8], "src": [224,112], "f": 0, "t": 476, "d": [94], "a": 1 }, + { "px": [376,8], "src": [232,112], "f": 0, "t": 477, "d": [95], "a": 1 }, + { "px": [0,16], "src": [216,120], "f": 0, "t": 507, "d": [96], "a": 1 }, + { "px": [8,16], "src": [224,120], "f": 0, "t": 508, "d": [97], "a": 1 }, + { "px": [16,16], "src": [224,120], "f": 0, "t": 508, "d": [98], "a": 1 }, + { "px": [24,16], "src": [224,120], "f": 0, "t": 508, "d": [99], "a": 1 }, + { "px": [32,16], "src": [224,120], "f": 0, "t": 508, "d": [100], "a": 1 }, + { "px": [40,16], "src": [224,120], "f": 0, "t": 508, "d": [101], "a": 1 }, + { "px": [48,16], "src": [224,120], "f": 0, "t": 508, "d": [102], "a": 1 }, + { "px": [56,16], "src": [224,120], "f": 0, "t": 508, "d": [103], "a": 1 }, + { "px": [64,16], "src": [224,120], "f": 0, "t": 508, "d": [104], "a": 1 }, + { "px": [72,16], "src": [224,120], "f": 0, "t": 508, "d": [105], "a": 1 }, + { "px": [80,16], "src": [224,120], "f": 0, "t": 508, "d": [106], "a": 1 }, + { "px": [88,16], "src": [224,120], "f": 0, "t": 508, "d": [107], "a": 1 }, + { "px": [96,16], "src": [224,120], "f": 0, "t": 508, "d": [108], "a": 1 }, + { "px": [104,16], "src": [224,120], "f": 0, "t": 508, "d": [109], "a": 1 }, + { "px": [112,16], "src": [224,120], "f": 0, "t": 508, "d": [110], "a": 1 }, + { "px": [120,16], "src": [224,120], "f": 0, "t": 508, "d": [111], "a": 1 }, + { "px": [128,16], "src": [224,120], "f": 0, "t": 508, "d": [112], "a": 1 }, + { "px": [136,16], "src": [224,120], "f": 0, "t": 508, "d": [113], "a": 1 }, + { "px": [144,16], "src": [224,120], "f": 0, "t": 508, "d": [114], "a": 1 }, + { "px": [152,16], "src": [224,120], "f": 0, "t": 508, "d": [115], "a": 1 }, + { "px": [160,16], "src": [224,120], "f": 0, "t": 508, "d": [116], "a": 1 }, + { "px": [168,16], "src": [224,120], "f": 0, "t": 508, "d": [117], "a": 1 }, + { "px": [176,16], "src": [224,120], "f": 0, "t": 508, "d": [118], "a": 1 }, + { "px": [184,16], "src": [224,120], "f": 0, "t": 508, "d": [119], "a": 1 }, + { "px": [192,16], "src": [224,120], "f": 0, "t": 508, "d": [120], "a": 1 }, + { "px": [200,16], "src": [224,120], "f": 0, "t": 508, "d": [121], "a": 1 }, + { "px": [208,16], "src": [224,120], "f": 0, "t": 508, "d": [122], "a": 1 }, + { "px": [216,16], "src": [224,120], "f": 0, "t": 508, "d": [123], "a": 1 }, + { "px": [224,16], "src": [224,120], "f": 0, "t": 508, "d": [124], "a": 1 }, + { "px": [232,16], "src": [224,120], "f": 0, "t": 508, "d": [125], "a": 1 }, + { "px": [240,16], "src": [224,120], "f": 0, "t": 508, "d": [126], "a": 1 }, + { "px": [248,16], "src": [224,120], "f": 0, "t": 508, "d": [127], "a": 1 }, + { "px": [256,16], "src": [224,120], "f": 0, "t": 508, "d": [128], "a": 1 }, + { "px": [264,16], "src": [224,120], "f": 0, "t": 508, "d": [129], "a": 1 }, + { "px": [272,16], "src": [224,120], "f": 0, "t": 508, "d": [130], "a": 1 }, + { "px": [280,16], "src": [224,120], "f": 0, "t": 508, "d": [131], "a": 1 }, + { "px": [288,16], "src": [224,120], "f": 0, "t": 508, "d": [132], "a": 1 }, + { "px": [296,16], "src": [224,120], "f": 0, "t": 508, "d": [133], "a": 1 }, + { "px": [304,16], "src": [224,120], "f": 0, "t": 508, "d": [134], "a": 1 }, + { "px": [312,16], "src": [224,120], "f": 0, "t": 508, "d": [135], "a": 1 }, + { "px": [320,16], "src": [224,120], "f": 0, "t": 508, "d": [136], "a": 1 }, + { "px": [328,16], "src": [224,120], "f": 0, "t": 508, "d": [137], "a": 1 }, + { "px": [336,16], "src": [224,120], "f": 0, "t": 508, "d": [138], "a": 1 }, + { "px": [344,16], "src": [224,120], "f": 0, "t": 508, "d": [139], "a": 1 }, + { "px": [352,16], "src": [224,120], "f": 0, "t": 508, "d": [140], "a": 1 }, + { "px": [360,16], "src": [224,120], "f": 0, "t": 508, "d": [141], "a": 1 }, + { "px": [368,16], "src": [224,120], "f": 0, "t": 508, "d": [142], "a": 1 }, + { "px": [376,16], "src": [232,120], "f": 0, "t": 509, "d": [143], "a": 1 }, + { "px": [0,24], "src": [216,128], "f": 0, "t": 539, "d": [144], "a": 1 }, + { "px": [8,24], "src": [224,128], "f": 0, "t": 540, "d": [145], "a": 1 }, + { "px": [16,24], "src": [224,128], "f": 0, "t": 540, "d": [146], "a": 1 }, + { "px": [24,24], "src": [224,128], "f": 0, "t": 540, "d": [147], "a": 1 }, + { "px": [32,24], "src": [224,128], "f": 0, "t": 540, "d": [148], "a": 1 }, + { "px": [40,24], "src": [224,128], "f": 0, "t": 540, "d": [149], "a": 1 }, + { "px": [48,24], "src": [224,128], "f": 0, "t": 540, "d": [150], "a": 1 }, + { "px": [56,24], "src": [224,128], "f": 0, "t": 540, "d": [151], "a": 1 }, + { "px": [64,24], "src": [224,128], "f": 0, "t": 540, "d": [152], "a": 1 }, + { "px": [72,24], "src": [224,128], "f": 0, "t": 540, "d": [153], "a": 1 }, + { "px": [80,24], "src": [224,128], "f": 0, "t": 540, "d": [154], "a": 1 }, + { "px": [88,24], "src": [224,128], "f": 0, "t": 540, "d": [155], "a": 1 }, + { "px": [96,24], "src": [224,128], "f": 0, "t": 540, "d": [156], "a": 1 }, + { "px": [104,24], "src": [224,128], "f": 0, "t": 540, "d": [157], "a": 1 }, + { "px": [112,24], "src": [224,128], "f": 0, "t": 540, "d": [158], "a": 1 }, + { "px": [120,24], "src": [224,128], "f": 0, "t": 540, "d": [159], "a": 1 }, + { "px": [128,24], "src": [224,128], "f": 0, "t": 540, "d": [160], "a": 1 }, + { "px": [136,24], "src": [224,128], "f": 0, "t": 540, "d": [161], "a": 1 }, + { "px": [144,24], "src": [224,128], "f": 0, "t": 540, "d": [162], "a": 1 }, + { "px": [152,24], "src": [224,128], "f": 0, "t": 540, "d": [163], "a": 1 }, + { "px": [160,24], "src": [224,128], "f": 0, "t": 540, "d": [164], "a": 1 }, + { "px": [168,24], "src": [224,128], "f": 0, "t": 540, "d": [165], "a": 1 }, + { "px": [176,24], "src": [224,128], "f": 0, "t": 540, "d": [166], "a": 1 }, + { "px": [184,24], "src": [224,128], "f": 0, "t": 540, "d": [167], "a": 1 }, + { "px": [192,24], "src": [224,128], "f": 0, "t": 540, "d": [168], "a": 1 }, + { "px": [200,24], "src": [224,128], "f": 0, "t": 540, "d": [169], "a": 1 }, + { "px": [208,24], "src": [224,128], "f": 0, "t": 540, "d": [170], "a": 1 }, + { "px": [216,24], "src": [224,128], "f": 0, "t": 540, "d": [171], "a": 1 }, + { "px": [224,24], "src": [224,128], "f": 0, "t": 540, "d": [172], "a": 1 }, + { "px": [232,24], "src": [224,128], "f": 0, "t": 540, "d": [173], "a": 1 }, + { "px": [240,24], "src": [224,128], "f": 0, "t": 540, "d": [174], "a": 1 }, + { "px": [248,24], "src": [224,128], "f": 0, "t": 540, "d": [175], "a": 1 }, + { "px": [256,24], "src": [224,128], "f": 0, "t": 540, "d": [176], "a": 1 }, + { "px": [264,24], "src": [224,128], "f": 0, "t": 540, "d": [177], "a": 1 }, + { "px": [272,24], "src": [224,128], "f": 0, "t": 540, "d": [178], "a": 1 }, + { "px": [280,24], "src": [224,128], "f": 0, "t": 540, "d": [179], "a": 1 }, + { "px": [288,24], "src": [224,128], "f": 0, "t": 540, "d": [180], "a": 1 }, + { "px": [296,24], "src": [224,128], "f": 0, "t": 540, "d": [181], "a": 1 }, + { "px": [304,24], "src": [224,128], "f": 0, "t": 540, "d": [182], "a": 1 }, + { "px": [312,24], "src": [224,128], "f": 0, "t": 540, "d": [183], "a": 1 }, + { "px": [320,24], "src": [224,128], "f": 0, "t": 540, "d": [184], "a": 1 }, + { "px": [328,24], "src": [224,128], "f": 0, "t": 540, "d": [185], "a": 1 }, + { "px": [336,24], "src": [224,128], "f": 0, "t": 540, "d": [186], "a": 1 }, + { "px": [344,24], "src": [224,128], "f": 0, "t": 540, "d": [187], "a": 1 }, + { "px": [352,24], "src": [224,128], "f": 0, "t": 540, "d": [188], "a": 1 }, + { "px": [360,24], "src": [224,128], "f": 0, "t": 540, "d": [189], "a": 1 }, + { "px": [368,24], "src": [224,128], "f": 0, "t": 540, "d": [190], "a": 1 }, + { "px": [376,24], "src": [232,128], "f": 0, "t": 541, "d": [191], "a": 1 }, + { "px": [0,32], "src": [216,128], "f": 0, "t": 539, "d": [192], "a": 1 }, + { "px": [8,32], "src": [224,128], "f": 0, "t": 540, "d": [193], "a": 1 }, + { "px": [16,32], "src": [224,128], "f": 0, "t": 540, "d": [194], "a": 1 }, + { "px": [24,32], "src": [224,128], "f": 0, "t": 540, "d": [195], "a": 1 }, + { "px": [32,32], "src": [224,128], "f": 0, "t": 540, "d": [196], "a": 1 }, + { "px": [40,32], "src": [224,128], "f": 0, "t": 540, "d": [197], "a": 1 }, + { "px": [48,32], "src": [224,128], "f": 0, "t": 540, "d": [198], "a": 1 }, + { "px": [56,32], "src": [224,128], "f": 0, "t": 540, "d": [199], "a": 1 }, + { "px": [64,32], "src": [224,128], "f": 0, "t": 540, "d": [200], "a": 1 }, + { "px": [72,32], "src": [224,128], "f": 0, "t": 540, "d": [201], "a": 1 }, + { "px": [80,32], "src": [224,128], "f": 0, "t": 540, "d": [202], "a": 1 }, + { "px": [88,32], "src": [224,128], "f": 0, "t": 540, "d": [203], "a": 1 }, + { "px": [96,32], "src": [224,128], "f": 0, "t": 540, "d": [204], "a": 1 }, + { "px": [104,32], "src": [224,128], "f": 0, "t": 540, "d": [205], "a": 1 }, + { "px": [112,32], "src": [224,128], "f": 0, "t": 540, "d": [206], "a": 1 }, + { "px": [120,32], "src": [224,128], "f": 0, "t": 540, "d": [207], "a": 1 }, + { "px": [128,32], "src": [224,128], "f": 0, "t": 540, "d": [208], "a": 1 }, + { "px": [136,32], "src": [224,128], "f": 0, "t": 540, "d": [209], "a": 1 }, + { "px": [144,32], "src": [224,128], "f": 0, "t": 540, "d": [210], "a": 1 }, + { "px": [152,32], "src": [224,128], "f": 0, "t": 540, "d": [211], "a": 1 }, + { "px": [160,32], "src": [224,128], "f": 0, "t": 540, "d": [212], "a": 1 }, + { "px": [168,32], "src": [224,128], "f": 0, "t": 540, "d": [213], "a": 1 }, + { "px": [176,32], "src": [224,128], "f": 0, "t": 540, "d": [214], "a": 1 }, + { "px": [184,32], "src": [224,128], "f": 0, "t": 540, "d": [215], "a": 1 }, + { "px": [192,32], "src": [224,128], "f": 0, "t": 540, "d": [216], "a": 1 }, + { "px": [200,32], "src": [224,128], "f": 0, "t": 540, "d": [217], "a": 1 }, + { "px": [208,32], "src": [224,128], "f": 0, "t": 540, "d": [218], "a": 1 }, + { "px": [216,32], "src": [224,128], "f": 0, "t": 540, "d": [219], "a": 1 }, + { "px": [224,32], "src": [224,128], "f": 0, "t": 540, "d": [220], "a": 1 }, + { "px": [232,32], "src": [224,128], "f": 0, "t": 540, "d": [221], "a": 1 }, + { "px": [240,32], "src": [224,128], "f": 0, "t": 540, "d": [222], "a": 1 }, + { "px": [248,32], "src": [224,128], "f": 0, "t": 540, "d": [223], "a": 1 }, + { "px": [256,32], "src": [224,128], "f": 0, "t": 540, "d": [224], "a": 1 }, + { "px": [264,32], "src": [224,128], "f": 0, "t": 540, "d": [225], "a": 1 }, + { "px": [272,32], "src": [224,128], "f": 0, "t": 540, "d": [226], "a": 1 }, + { "px": [280,32], "src": [224,128], "f": 0, "t": 540, "d": [227], "a": 1 }, + { "px": [288,32], "src": [224,128], "f": 0, "t": 540, "d": [228], "a": 1 }, + { "px": [296,32], "src": [224,128], "f": 0, "t": 540, "d": [229], "a": 1 }, + { "px": [304,32], "src": [224,128], "f": 0, "t": 540, "d": [230], "a": 1 }, + { "px": [312,32], "src": [224,128], "f": 0, "t": 540, "d": [231], "a": 1 }, + { "px": [320,32], "src": [224,128], "f": 0, "t": 540, "d": [232], "a": 1 }, + { "px": [328,32], "src": [224,128], "f": 0, "t": 540, "d": [233], "a": 1 }, + { "px": [336,32], "src": [224,128], "f": 0, "t": 540, "d": [234], "a": 1 }, + { "px": [344,32], "src": [224,128], "f": 0, "t": 540, "d": [235], "a": 1 }, + { "px": [352,32], "src": [224,128], "f": 0, "t": 540, "d": [236], "a": 1 }, + { "px": [360,32], "src": [224,128], "f": 0, "t": 540, "d": [237], "a": 1 }, + { "px": [368,32], "src": [224,128], "f": 0, "t": 540, "d": [238], "a": 1 }, + { "px": [376,32], "src": [232,128], "f": 0, "t": 541, "d": [239], "a": 1 }, + { "px": [0,40], "src": [216,128], "f": 0, "t": 539, "d": [240], "a": 1 }, + { "px": [8,40], "src": [224,128], "f": 0, "t": 540, "d": [241], "a": 1 }, + { "px": [16,40], "src": [224,128], "f": 0, "t": 540, "d": [242], "a": 1 }, + { "px": [24,40], "src": [224,128], "f": 0, "t": 540, "d": [243], "a": 1 }, + { "px": [32,40], "src": [224,128], "f": 0, "t": 540, "d": [244], "a": 1 }, + { "px": [40,40], "src": [224,128], "f": 0, "t": 540, "d": [245], "a": 1 }, + { "px": [48,40], "src": [224,128], "f": 0, "t": 540, "d": [246], "a": 1 }, + { "px": [56,40], "src": [224,128], "f": 0, "t": 540, "d": [247], "a": 1 }, + { "px": [64,40], "src": [224,128], "f": 0, "t": 540, "d": [248], "a": 1 }, + { "px": [72,40], "src": [224,128], "f": 0, "t": 540, "d": [249], "a": 1 }, + { "px": [80,40], "src": [224,128], "f": 0, "t": 540, "d": [250], "a": 1 }, + { "px": [88,40], "src": [224,128], "f": 0, "t": 540, "d": [251], "a": 1 }, + { "px": [96,40], "src": [224,128], "f": 0, "t": 540, "d": [252], "a": 1 }, + { "px": [104,40], "src": [224,128], "f": 0, "t": 540, "d": [253], "a": 1 }, + { "px": [112,40], "src": [224,128], "f": 0, "t": 540, "d": [254], "a": 1 }, + { "px": [120,40], "src": [224,128], "f": 0, "t": 540, "d": [255], "a": 1 }, + { "px": [128,40], "src": [224,128], "f": 0, "t": 540, "d": [256], "a": 1 }, + { "px": [136,40], "src": [224,128], "f": 0, "t": 540, "d": [257], "a": 1 }, + { "px": [144,40], "src": [224,128], "f": 0, "t": 540, "d": [258], "a": 1 }, + { "px": [152,40], "src": [224,128], "f": 0, "t": 540, "d": [259], "a": 1 }, + { "px": [160,40], "src": [224,128], "f": 0, "t": 540, "d": [260], "a": 1 }, + { "px": [168,40], "src": [224,128], "f": 0, "t": 540, "d": [261], "a": 1 }, + { "px": [176,40], "src": [224,128], "f": 0, "t": 540, "d": [262], "a": 1 }, + { "px": [184,40], "src": [224,128], "f": 0, "t": 540, "d": [263], "a": 1 }, + { "px": [192,40], "src": [224,128], "f": 0, "t": 540, "d": [264], "a": 1 }, + { "px": [200,40], "src": [224,128], "f": 0, "t": 540, "d": [265], "a": 1 }, + { "px": [208,40], "src": [224,128], "f": 0, "t": 540, "d": [266], "a": 1 }, + { "px": [216,40], "src": [224,128], "f": 0, "t": 540, "d": [267], "a": 1 }, + { "px": [224,40], "src": [224,128], "f": 0, "t": 540, "d": [268], "a": 1 }, + { "px": [232,40], "src": [224,128], "f": 0, "t": 540, "d": [269], "a": 1 }, + { "px": [240,40], "src": [224,128], "f": 0, "t": 540, "d": [270], "a": 1 }, + { "px": [248,40], "src": [224,128], "f": 0, "t": 540, "d": [271], "a": 1 }, + { "px": [256,40], "src": [224,128], "f": 0, "t": 540, "d": [272], "a": 1 }, + { "px": [264,40], "src": [224,128], "f": 0, "t": 540, "d": [273], "a": 1 }, + { "px": [272,40], "src": [224,128], "f": 0, "t": 540, "d": [274], "a": 1 }, + { "px": [280,40], "src": [224,128], "f": 0, "t": 540, "d": [275], "a": 1 }, + { "px": [288,40], "src": [224,128], "f": 0, "t": 540, "d": [276], "a": 1 }, + { "px": [296,40], "src": [224,128], "f": 0, "t": 540, "d": [277], "a": 1 }, + { "px": [304,40], "src": [224,128], "f": 0, "t": 540, "d": [278], "a": 1 }, + { "px": [312,40], "src": [224,128], "f": 0, "t": 540, "d": [279], "a": 1 }, + { "px": [320,40], "src": [224,128], "f": 0, "t": 540, "d": [280], "a": 1 }, + { "px": [328,40], "src": [224,128], "f": 0, "t": 540, "d": [281], "a": 1 }, + { "px": [336,40], "src": [224,128], "f": 0, "t": 540, "d": [282], "a": 1 }, + { "px": [344,40], "src": [224,128], "f": 0, "t": 540, "d": [283], "a": 1 }, + { "px": [352,40], "src": [224,128], "f": 0, "t": 540, "d": [284], "a": 1 }, + { "px": [360,40], "src": [224,128], "f": 0, "t": 540, "d": [285], "a": 1 }, + { "px": [368,40], "src": [224,128], "f": 0, "t": 540, "d": [286], "a": 1 }, + { "px": [376,40], "src": [232,128], "f": 0, "t": 541, "d": [287], "a": 1 }, + { "px": [0,48], "src": [216,128], "f": 0, "t": 539, "d": [288], "a": 1 }, + { "px": [8,48], "src": [224,128], "f": 0, "t": 540, "d": [289], "a": 1 }, + { "px": [16,48], "src": [224,128], "f": 0, "t": 540, "d": [290], "a": 1 }, + { "px": [24,48], "src": [224,128], "f": 0, "t": 540, "d": [291], "a": 1 }, + { "px": [32,48], "src": [224,128], "f": 0, "t": 540, "d": [292], "a": 1 }, + { "px": [40,48], "src": [224,128], "f": 0, "t": 540, "d": [293], "a": 1 }, + { "px": [48,48], "src": [224,128], "f": 0, "t": 540, "d": [294], "a": 1 }, + { "px": [56,48], "src": [224,128], "f": 0, "t": 540, "d": [295], "a": 1 }, + { "px": [64,48], "src": [224,128], "f": 0, "t": 540, "d": [296], "a": 1 }, + { "px": [72,48], "src": [224,128], "f": 0, "t": 540, "d": [297], "a": 1 }, + { "px": [80,48], "src": [224,128], "f": 0, "t": 540, "d": [298], "a": 1 }, + { "px": [88,48], "src": [224,128], "f": 0, "t": 540, "d": [299], "a": 1 }, + { "px": [96,48], "src": [224,128], "f": 0, "t": 540, "d": [300], "a": 1 }, + { "px": [104,48], "src": [224,128], "f": 0, "t": 540, "d": [301], "a": 1 }, + { "px": [112,48], "src": [224,128], "f": 0, "t": 540, "d": [302], "a": 1 }, + { "px": [120,48], "src": [224,128], "f": 0, "t": 540, "d": [303], "a": 1 }, + { "px": [128,48], "src": [224,128], "f": 0, "t": 540, "d": [304], "a": 1 }, + { "px": [136,48], "src": [224,128], "f": 0, "t": 540, "d": [305], "a": 1 }, + { "px": [144,48], "src": [224,128], "f": 0, "t": 540, "d": [306], "a": 1 }, + { "px": [152,48], "src": [224,128], "f": 0, "t": 540, "d": [307], "a": 1 }, + { "px": [160,48], "src": [224,128], "f": 0, "t": 540, "d": [308], "a": 1 }, + { "px": [168,48], "src": [224,128], "f": 0, "t": 540, "d": [309], "a": 1 }, + { "px": [176,48], "src": [224,128], "f": 0, "t": 540, "d": [310], "a": 1 }, + { "px": [184,48], "src": [224,128], "f": 0, "t": 540, "d": [311], "a": 1 }, + { "px": [192,48], "src": [224,128], "f": 0, "t": 540, "d": [312], "a": 1 }, + { "px": [200,48], "src": [224,128], "f": 0, "t": 540, "d": [313], "a": 1 }, + { "px": [208,48], "src": [224,128], "f": 0, "t": 540, "d": [314], "a": 1 }, + { "px": [216,48], "src": [224,128], "f": 0, "t": 540, "d": [315], "a": 1 }, + { "px": [224,48], "src": [224,128], "f": 0, "t": 540, "d": [316], "a": 1 }, + { "px": [232,48], "src": [224,128], "f": 0, "t": 540, "d": [317], "a": 1 }, + { "px": [240,48], "src": [224,128], "f": 0, "t": 540, "d": [318], "a": 1 }, + { "px": [248,48], "src": [224,128], "f": 0, "t": 540, "d": [319], "a": 1 }, + { "px": [256,48], "src": [224,128], "f": 0, "t": 540, "d": [320], "a": 1 }, + { "px": [264,48], "src": [224,128], "f": 0, "t": 540, "d": [321], "a": 1 }, + { "px": [272,48], "src": [224,128], "f": 0, "t": 540, "d": [322], "a": 1 }, + { "px": [280,48], "src": [224,128], "f": 0, "t": 540, "d": [323], "a": 1 }, + { "px": [288,48], "src": [224,128], "f": 0, "t": 540, "d": [324], "a": 1 }, + { "px": [296,48], "src": [224,128], "f": 0, "t": 540, "d": [325], "a": 1 }, + { "px": [304,48], "src": [224,128], "f": 0, "t": 540, "d": [326], "a": 1 }, + { "px": [312,48], "src": [224,128], "f": 0, "t": 540, "d": [327], "a": 1 }, + { "px": [320,48], "src": [224,128], "f": 0, "t": 540, "d": [328], "a": 1 }, + { "px": [328,48], "src": [224,128], "f": 0, "t": 540, "d": [329], "a": 1 }, + { "px": [336,48], "src": [224,128], "f": 0, "t": 540, "d": [330], "a": 1 }, + { "px": [344,48], "src": [224,128], "f": 0, "t": 540, "d": [331], "a": 1 }, + { "px": [352,48], "src": [224,128], "f": 0, "t": 540, "d": [332], "a": 1 }, + { "px": [360,48], "src": [224,128], "f": 0, "t": 540, "d": [333], "a": 1 }, + { "px": [368,48], "src": [224,128], "f": 0, "t": 540, "d": [334], "a": 1 }, + { "px": [376,48], "src": [232,128], "f": 0, "t": 541, "d": [335], "a": 1 }, + { "px": [0,56], "src": [216,128], "f": 0, "t": 539, "d": [336], "a": 1 }, + { "px": [8,56], "src": [224,128], "f": 0, "t": 540, "d": [337], "a": 1 }, + { "px": [16,56], "src": [224,128], "f": 0, "t": 540, "d": [338], "a": 1 }, + { "px": [24,56], "src": [224,128], "f": 0, "t": 540, "d": [339], "a": 1 }, + { "px": [32,56], "src": [224,128], "f": 0, "t": 540, "d": [340], "a": 1 }, + { "px": [40,56], "src": [224,128], "f": 0, "t": 540, "d": [341], "a": 1 }, + { "px": [48,56], "src": [224,128], "f": 0, "t": 540, "d": [342], "a": 1 }, + { "px": [56,56], "src": [224,128], "f": 0, "t": 540, "d": [343], "a": 1 }, + { "px": [64,56], "src": [224,128], "f": 0, "t": 540, "d": [344], "a": 1 }, + { "px": [72,56], "src": [224,128], "f": 0, "t": 540, "d": [345], "a": 1 }, + { "px": [80,56], "src": [224,128], "f": 0, "t": 540, "d": [346], "a": 1 }, + { "px": [88,56], "src": [224,128], "f": 0, "t": 540, "d": [347], "a": 1 }, + { "px": [96,56], "src": [224,128], "f": 0, "t": 540, "d": [348], "a": 1 }, + { "px": [104,56], "src": [224,128], "f": 0, "t": 540, "d": [349], "a": 1 }, + { "px": [112,56], "src": [224,128], "f": 0, "t": 540, "d": [350], "a": 1 }, + { "px": [120,56], "src": [224,128], "f": 0, "t": 540, "d": [351], "a": 1 }, + { "px": [128,56], "src": [224,128], "f": 0, "t": 540, "d": [352], "a": 1 }, + { "px": [136,56], "src": [224,128], "f": 0, "t": 540, "d": [353], "a": 1 }, + { "px": [144,56], "src": [224,128], "f": 0, "t": 540, "d": [354], "a": 1 }, + { "px": [152,56], "src": [224,128], "f": 0, "t": 540, "d": [355], "a": 1 }, + { "px": [160,56], "src": [224,128], "f": 0, "t": 540, "d": [356], "a": 1 }, + { "px": [168,56], "src": [224,128], "f": 0, "t": 540, "d": [357], "a": 1 }, + { "px": [176,56], "src": [224,128], "f": 0, "t": 540, "d": [358], "a": 1 }, + { "px": [184,56], "src": [224,128], "f": 0, "t": 540, "d": [359], "a": 1 }, + { "px": [192,56], "src": [224,128], "f": 0, "t": 540, "d": [360], "a": 1 }, + { "px": [200,56], "src": [224,128], "f": 0, "t": 540, "d": [361], "a": 1 }, + { "px": [208,56], "src": [224,128], "f": 0, "t": 540, "d": [362], "a": 1 }, + { "px": [216,56], "src": [224,128], "f": 0, "t": 540, "d": [363], "a": 1 }, + { "px": [224,56], "src": [224,128], "f": 0, "t": 540, "d": [364], "a": 1 }, + { "px": [232,56], "src": [224,128], "f": 0, "t": 540, "d": [365], "a": 1 }, + { "px": [240,56], "src": [224,128], "f": 0, "t": 540, "d": [366], "a": 1 }, + { "px": [248,56], "src": [224,128], "f": 0, "t": 540, "d": [367], "a": 1 }, + { "px": [256,56], "src": [224,128], "f": 0, "t": 540, "d": [368], "a": 1 }, + { "px": [264,56], "src": [224,128], "f": 0, "t": 540, "d": [369], "a": 1 }, + { "px": [272,56], "src": [224,128], "f": 0, "t": 540, "d": [370], "a": 1 }, + { "px": [280,56], "src": [224,128], "f": 0, "t": 540, "d": [371], "a": 1 }, + { "px": [288,56], "src": [224,128], "f": 0, "t": 540, "d": [372], "a": 1 }, + { "px": [296,56], "src": [224,128], "f": 0, "t": 540, "d": [373], "a": 1 }, + { "px": [304,56], "src": [224,128], "f": 0, "t": 540, "d": [374], "a": 1 }, + { "px": [312,56], "src": [224,128], "f": 0, "t": 540, "d": [375], "a": 1 }, + { "px": [320,56], "src": [224,128], "f": 0, "t": 540, "d": [376], "a": 1 }, + { "px": [328,56], "src": [224,128], "f": 0, "t": 540, "d": [377], "a": 1 }, + { "px": [336,56], "src": [224,128], "f": 0, "t": 540, "d": [378], "a": 1 }, + { "px": [344,56], "src": [224,128], "f": 0, "t": 540, "d": [379], "a": 1 }, + { "px": [352,56], "src": [224,128], "f": 0, "t": 540, "d": [380], "a": 1 }, + { "px": [360,56], "src": [224,128], "f": 0, "t": 540, "d": [381], "a": 1 }, + { "px": [368,56], "src": [224,128], "f": 0, "t": 540, "d": [382], "a": 1 }, + { "px": [376,56], "src": [232,128], "f": 0, "t": 541, "d": [383], "a": 1 }, + { "px": [0,64], "src": [216,128], "f": 0, "t": 539, "d": [384], "a": 1 }, + { "px": [8,64], "src": [224,128], "f": 0, "t": 540, "d": [385], "a": 1 }, + { "px": [16,64], "src": [224,128], "f": 0, "t": 540, "d": [386], "a": 1 }, + { "px": [24,64], "src": [224,128], "f": 0, "t": 540, "d": [387], "a": 1 }, + { "px": [32,64], "src": [224,128], "f": 0, "t": 540, "d": [388], "a": 1 }, + { "px": [40,64], "src": [224,128], "f": 0, "t": 540, "d": [389], "a": 1 }, + { "px": [48,64], "src": [224,128], "f": 0, "t": 540, "d": [390], "a": 1 }, + { "px": [56,64], "src": [224,128], "f": 0, "t": 540, "d": [391], "a": 1 }, + { "px": [64,64], "src": [224,128], "f": 0, "t": 540, "d": [392], "a": 1 }, + { "px": [72,64], "src": [224,128], "f": 0, "t": 540, "d": [393], "a": 1 }, + { "px": [80,64], "src": [224,128], "f": 0, "t": 540, "d": [394], "a": 1 }, + { "px": [88,64], "src": [224,128], "f": 0, "t": 540, "d": [395], "a": 1 }, + { "px": [96,64], "src": [224,128], "f": 0, "t": 540, "d": [396], "a": 1 }, + { "px": [104,64], "src": [224,128], "f": 0, "t": 540, "d": [397], "a": 1 }, + { "px": [112,64], "src": [224,128], "f": 0, "t": 540, "d": [398], "a": 1 }, + { "px": [120,64], "src": [224,128], "f": 0, "t": 540, "d": [399], "a": 1 }, + { "px": [128,64], "src": [224,128], "f": 0, "t": 540, "d": [400], "a": 1 }, + { "px": [136,64], "src": [224,128], "f": 0, "t": 540, "d": [401], "a": 1 }, + { "px": [144,64], "src": [224,128], "f": 0, "t": 540, "d": [402], "a": 1 }, + { "px": [152,64], "src": [224,128], "f": 0, "t": 540, "d": [403], "a": 1 }, + { "px": [160,64], "src": [224,128], "f": 0, "t": 540, "d": [404], "a": 1 }, + { "px": [168,64], "src": [224,128], "f": 0, "t": 540, "d": [405], "a": 1 }, + { "px": [176,64], "src": [224,128], "f": 0, "t": 540, "d": [406], "a": 1 }, + { "px": [184,64], "src": [224,128], "f": 0, "t": 540, "d": [407], "a": 1 }, + { "px": [192,64], "src": [224,128], "f": 0, "t": 540, "d": [408], "a": 1 }, + { "px": [200,64], "src": [224,128], "f": 0, "t": 540, "d": [409], "a": 1 }, + { "px": [208,64], "src": [224,128], "f": 0, "t": 540, "d": [410], "a": 1 }, + { "px": [216,64], "src": [224,128], "f": 0, "t": 540, "d": [411], "a": 1 }, + { "px": [224,64], "src": [224,128], "f": 0, "t": 540, "d": [412], "a": 1 }, + { "px": [232,64], "src": [224,128], "f": 0, "t": 540, "d": [413], "a": 1 }, + { "px": [240,64], "src": [224,128], "f": 0, "t": 540, "d": [414], "a": 1 }, + { "px": [248,64], "src": [224,128], "f": 0, "t": 540, "d": [415], "a": 1 }, + { "px": [256,64], "src": [224,128], "f": 0, "t": 540, "d": [416], "a": 1 }, + { "px": [264,64], "src": [224,128], "f": 0, "t": 540, "d": [417], "a": 1 }, + { "px": [272,64], "src": [224,128], "f": 0, "t": 540, "d": [418], "a": 1 }, + { "px": [280,64], "src": [224,128], "f": 0, "t": 540, "d": [419], "a": 1 }, + { "px": [288,64], "src": [224,128], "f": 0, "t": 540, "d": [420], "a": 1 }, + { "px": [296,64], "src": [224,128], "f": 0, "t": 540, "d": [421], "a": 1 }, + { "px": [304,64], "src": [224,128], "f": 0, "t": 540, "d": [422], "a": 1 }, + { "px": [312,64], "src": [224,128], "f": 0, "t": 540, "d": [423], "a": 1 }, + { "px": [320,64], "src": [224,128], "f": 0, "t": 540, "d": [424], "a": 1 }, + { "px": [328,64], "src": [224,128], "f": 0, "t": 540, "d": [425], "a": 1 }, + { "px": [336,64], "src": [224,128], "f": 0, "t": 540, "d": [426], "a": 1 }, + { "px": [344,64], "src": [224,128], "f": 0, "t": 540, "d": [427], "a": 1 }, + { "px": [352,64], "src": [224,128], "f": 0, "t": 540, "d": [428], "a": 1 }, + { "px": [360,64], "src": [224,128], "f": 0, "t": 540, "d": [429], "a": 1 }, + { "px": [368,64], "src": [224,128], "f": 0, "t": 540, "d": [430], "a": 1 }, + { "px": [376,64], "src": [232,128], "f": 0, "t": 541, "d": [431], "a": 1 }, + { "px": [0,72], "src": [216,128], "f": 0, "t": 539, "d": [432], "a": 1 }, + { "px": [8,72], "src": [224,128], "f": 0, "t": 540, "d": [433], "a": 1 }, + { "px": [16,72], "src": [224,128], "f": 0, "t": 540, "d": [434], "a": 1 }, + { "px": [24,72], "src": [224,128], "f": 0, "t": 540, "d": [435], "a": 1 }, + { "px": [32,72], "src": [224,128], "f": 0, "t": 540, "d": [436], "a": 1 }, + { "px": [40,72], "src": [224,128], "f": 0, "t": 540, "d": [437], "a": 1 }, + { "px": [48,72], "src": [224,128], "f": 0, "t": 540, "d": [438], "a": 1 }, + { "px": [56,72], "src": [224,128], "f": 0, "t": 540, "d": [439], "a": 1 }, + { "px": [64,72], "src": [224,128], "f": 0, "t": 540, "d": [440], "a": 1 }, + { "px": [72,72], "src": [224,128], "f": 0, "t": 540, "d": [441], "a": 1 }, + { "px": [80,72], "src": [224,128], "f": 0, "t": 540, "d": [442], "a": 1 }, + { "px": [88,72], "src": [224,128], "f": 0, "t": 540, "d": [443], "a": 1 }, + { "px": [96,72], "src": [224,128], "f": 0, "t": 540, "d": [444], "a": 1 }, + { "px": [104,72], "src": [224,128], "f": 0, "t": 540, "d": [445], "a": 1 }, + { "px": [112,72], "src": [224,128], "f": 0, "t": 540, "d": [446], "a": 1 }, + { "px": [120,72], "src": [224,128], "f": 0, "t": 540, "d": [447], "a": 1 }, + { "px": [128,72], "src": [224,128], "f": 0, "t": 540, "d": [448], "a": 1 }, + { "px": [136,72], "src": [224,128], "f": 0, "t": 540, "d": [449], "a": 1 }, + { "px": [144,72], "src": [224,128], "f": 0, "t": 540, "d": [450], "a": 1 }, + { "px": [152,72], "src": [224,128], "f": 0, "t": 540, "d": [451], "a": 1 }, + { "px": [160,72], "src": [224,128], "f": 0, "t": 540, "d": [452], "a": 1 }, + { "px": [168,72], "src": [224,128], "f": 0, "t": 540, "d": [453], "a": 1 }, + { "px": [176,72], "src": [224,128], "f": 0, "t": 540, "d": [454], "a": 1 }, + { "px": [184,72], "src": [224,128], "f": 0, "t": 540, "d": [455], "a": 1 }, + { "px": [192,72], "src": [224,128], "f": 0, "t": 540, "d": [456], "a": 1 }, + { "px": [200,72], "src": [224,128], "f": 0, "t": 540, "d": [457], "a": 1 }, + { "px": [208,72], "src": [224,128], "f": 0, "t": 540, "d": [458], "a": 1 }, + { "px": [216,72], "src": [224,128], "f": 0, "t": 540, "d": [459], "a": 1 }, + { "px": [224,72], "src": [224,128], "f": 0, "t": 540, "d": [460], "a": 1 }, + { "px": [232,72], "src": [224,128], "f": 0, "t": 540, "d": [461], "a": 1 }, + { "px": [240,72], "src": [224,128], "f": 0, "t": 540, "d": [462], "a": 1 }, + { "px": [248,72], "src": [224,128], "f": 0, "t": 540, "d": [463], "a": 1 }, + { "px": [256,72], "src": [224,128], "f": 0, "t": 540, "d": [464], "a": 1 }, + { "px": [264,72], "src": [224,128], "f": 0, "t": 540, "d": [465], "a": 1 }, + { "px": [272,72], "src": [224,128], "f": 0, "t": 540, "d": [466], "a": 1 }, + { "px": [280,72], "src": [224,128], "f": 0, "t": 540, "d": [467], "a": 1 }, + { "px": [288,72], "src": [224,128], "f": 0, "t": 540, "d": [468], "a": 1 }, + { "px": [296,72], "src": [224,128], "f": 0, "t": 540, "d": [469], "a": 1 }, + { "px": [304,72], "src": [224,128], "f": 0, "t": 540, "d": [470], "a": 1 }, + { "px": [312,72], "src": [224,128], "f": 0, "t": 540, "d": [471], "a": 1 }, + { "px": [320,72], "src": [224,128], "f": 0, "t": 540, "d": [472], "a": 1 }, + { "px": [328,72], "src": [224,128], "f": 0, "t": 540, "d": [473], "a": 1 }, + { "px": [336,72], "src": [224,128], "f": 0, "t": 540, "d": [474], "a": 1 }, + { "px": [344,72], "src": [224,128], "f": 0, "t": 540, "d": [475], "a": 1 }, + { "px": [352,72], "src": [224,128], "f": 0, "t": 540, "d": [476], "a": 1 }, + { "px": [360,72], "src": [224,128], "f": 0, "t": 540, "d": [477], "a": 1 }, + { "px": [368,72], "src": [224,128], "f": 0, "t": 540, "d": [478], "a": 1 }, + { "px": [376,72], "src": [232,128], "f": 0, "t": 541, "d": [479], "a": 1 }, + { "px": [0,80], "src": [216,128], "f": 0, "t": 539, "d": [480], "a": 1 }, + { "px": [8,80], "src": [224,128], "f": 0, "t": 540, "d": [481], "a": 1 }, + { "px": [16,80], "src": [224,128], "f": 0, "t": 540, "d": [482], "a": 1 }, + { "px": [24,80], "src": [224,128], "f": 0, "t": 540, "d": [483], "a": 1 }, + { "px": [32,80], "src": [224,128], "f": 0, "t": 540, "d": [484], "a": 1 }, + { "px": [40,80], "src": [224,128], "f": 0, "t": 540, "d": [485], "a": 1 }, + { "px": [48,80], "src": [224,128], "f": 0, "t": 540, "d": [486], "a": 1 }, + { "px": [56,80], "src": [224,128], "f": 0, "t": 540, "d": [487], "a": 1 }, + { "px": [64,80], "src": [224,128], "f": 0, "t": 540, "d": [488], "a": 1 }, + { "px": [72,80], "src": [224,128], "f": 0, "t": 540, "d": [489], "a": 1 }, + { "px": [80,80], "src": [224,128], "f": 0, "t": 540, "d": [490], "a": 1 }, + { "px": [88,80], "src": [224,128], "f": 0, "t": 540, "d": [491], "a": 1 }, + { "px": [96,80], "src": [224,128], "f": 0, "t": 540, "d": [492], "a": 1 }, + { "px": [104,80], "src": [224,128], "f": 0, "t": 540, "d": [493], "a": 1 }, + { "px": [112,80], "src": [224,128], "f": 0, "t": 540, "d": [494], "a": 1 }, + { "px": [120,80], "src": [224,128], "f": 0, "t": 540, "d": [495], "a": 1 }, + { "px": [128,80], "src": [224,128], "f": 0, "t": 540, "d": [496], "a": 1 }, + { "px": [136,80], "src": [224,128], "f": 0, "t": 540, "d": [497], "a": 1 }, + { "px": [144,80], "src": [224,128], "f": 0, "t": 540, "d": [498], "a": 1 }, + { "px": [152,80], "src": [224,128], "f": 0, "t": 540, "d": [499], "a": 1 }, + { "px": [160,80], "src": [224,128], "f": 0, "t": 540, "d": [500], "a": 1 }, + { "px": [168,80], "src": [224,128], "f": 0, "t": 540, "d": [501], "a": 1 }, + { "px": [176,80], "src": [224,128], "f": 0, "t": 540, "d": [502], "a": 1 }, + { "px": [184,80], "src": [224,128], "f": 0, "t": 540, "d": [503], "a": 1 }, + { "px": [192,80], "src": [224,128], "f": 0, "t": 540, "d": [504], "a": 1 }, + { "px": [200,80], "src": [224,128], "f": 0, "t": 540, "d": [505], "a": 1 }, + { "px": [208,80], "src": [224,128], "f": 0, "t": 540, "d": [506], "a": 1 }, + { "px": [216,80], "src": [224,128], "f": 0, "t": 540, "d": [507], "a": 1 }, + { "px": [224,80], "src": [224,128], "f": 0, "t": 540, "d": [508], "a": 1 }, + { "px": [232,80], "src": [224,128], "f": 0, "t": 540, "d": [509], "a": 1 }, + { "px": [240,80], "src": [224,128], "f": 0, "t": 540, "d": [510], "a": 1 }, + { "px": [248,80], "src": [224,128], "f": 0, "t": 540, "d": [511], "a": 1 }, + { "px": [256,80], "src": [224,128], "f": 0, "t": 540, "d": [512], "a": 1 }, + { "px": [264,80], "src": [224,128], "f": 0, "t": 540, "d": [513], "a": 1 }, + { "px": [272,80], "src": [224,128], "f": 0, "t": 540, "d": [514], "a": 1 }, + { "px": [280,80], "src": [224,128], "f": 0, "t": 540, "d": [515], "a": 1 }, + { "px": [288,80], "src": [224,128], "f": 0, "t": 540, "d": [516], "a": 1 }, + { "px": [296,80], "src": [224,128], "f": 0, "t": 540, "d": [517], "a": 1 }, + { "px": [304,80], "src": [224,128], "f": 0, "t": 540, "d": [518], "a": 1 }, + { "px": [312,80], "src": [224,128], "f": 0, "t": 540, "d": [519], "a": 1 }, + { "px": [320,80], "src": [224,128], "f": 0, "t": 540, "d": [520], "a": 1 }, + { "px": [328,80], "src": [224,128], "f": 0, "t": 540, "d": [521], "a": 1 }, + { "px": [336,80], "src": [224,128], "f": 0, "t": 540, "d": [522], "a": 1 }, + { "px": [344,80], "src": [224,128], "f": 0, "t": 540, "d": [523], "a": 1 }, + { "px": [352,80], "src": [224,128], "f": 0, "t": 540, "d": [524], "a": 1 }, + { "px": [360,80], "src": [224,128], "f": 0, "t": 540, "d": [525], "a": 1 }, + { "px": [368,80], "src": [224,128], "f": 0, "t": 540, "d": [526], "a": 1 }, + { "px": [376,80], "src": [232,128], "f": 0, "t": 541, "d": [527], "a": 1 }, + { "px": [0,88], "src": [216,128], "f": 0, "t": 539, "d": [528], "a": 1 }, + { "px": [8,88], "src": [224,128], "f": 0, "t": 540, "d": [529], "a": 1 }, + { "px": [16,88], "src": [224,128], "f": 0, "t": 540, "d": [530], "a": 1 }, + { "px": [24,88], "src": [224,128], "f": 0, "t": 540, "d": [531], "a": 1 }, + { "px": [32,88], "src": [224,128], "f": 0, "t": 540, "d": [532], "a": 1 }, + { "px": [40,88], "src": [224,128], "f": 0, "t": 540, "d": [533], "a": 1 }, + { "px": [48,88], "src": [224,128], "f": 0, "t": 540, "d": [534], "a": 1 }, + { "px": [56,88], "src": [224,128], "f": 0, "t": 540, "d": [535], "a": 1 }, + { "px": [64,88], "src": [224,128], "f": 0, "t": 540, "d": [536], "a": 1 }, + { "px": [72,88], "src": [224,128], "f": 0, "t": 540, "d": [537], "a": 1 }, + { "px": [80,88], "src": [224,128], "f": 0, "t": 540, "d": [538], "a": 1 }, + { "px": [88,88], "src": [224,128], "f": 0, "t": 540, "d": [539], "a": 1 }, + { "px": [96,88], "src": [224,128], "f": 0, "t": 540, "d": [540], "a": 1 }, + { "px": [104,88], "src": [224,128], "f": 0, "t": 540, "d": [541], "a": 1 }, + { "px": [112,88], "src": [224,128], "f": 0, "t": 540, "d": [542], "a": 1 }, + { "px": [120,88], "src": [224,128], "f": 0, "t": 540, "d": [543], "a": 1 }, + { "px": [128,88], "src": [224,128], "f": 0, "t": 540, "d": [544], "a": 1 }, + { "px": [136,88], "src": [224,128], "f": 0, "t": 540, "d": [545], "a": 1 }, + { "px": [144,88], "src": [224,128], "f": 0, "t": 540, "d": [546], "a": 1 }, + { "px": [152,88], "src": [224,128], "f": 0, "t": 540, "d": [547], "a": 1 }, + { "px": [160,88], "src": [224,128], "f": 0, "t": 540, "d": [548], "a": 1 }, + { "px": [168,88], "src": [224,128], "f": 0, "t": 540, "d": [549], "a": 1 }, + { "px": [176,88], "src": [224,128], "f": 0, "t": 540, "d": [550], "a": 1 }, + { "px": [184,88], "src": [224,128], "f": 0, "t": 540, "d": [551], "a": 1 }, + { "px": [192,88], "src": [224,128], "f": 0, "t": 540, "d": [552], "a": 1 }, + { "px": [200,88], "src": [224,128], "f": 0, "t": 540, "d": [553], "a": 1 }, + { "px": [208,88], "src": [224,128], "f": 0, "t": 540, "d": [554], "a": 1 }, + { "px": [216,88], "src": [224,128], "f": 0, "t": 540, "d": [555], "a": 1 }, + { "px": [224,88], "src": [224,128], "f": 0, "t": 540, "d": [556], "a": 1 }, + { "px": [232,88], "src": [224,128], "f": 0, "t": 540, "d": [557], "a": 1 }, + { "px": [240,88], "src": [224,128], "f": 0, "t": 540, "d": [558], "a": 1 }, + { "px": [248,88], "src": [224,128], "f": 0, "t": 540, "d": [559], "a": 1 }, + { "px": [256,88], "src": [224,128], "f": 0, "t": 540, "d": [560], "a": 1 }, + { "px": [264,88], "src": [224,128], "f": 0, "t": 540, "d": [561], "a": 1 }, + { "px": [272,88], "src": [224,128], "f": 0, "t": 540, "d": [562], "a": 1 }, + { "px": [280,88], "src": [224,128], "f": 0, "t": 540, "d": [563], "a": 1 }, + { "px": [288,88], "src": [224,128], "f": 0, "t": 540, "d": [564], "a": 1 }, + { "px": [296,88], "src": [224,128], "f": 0, "t": 540, "d": [565], "a": 1 }, + { "px": [304,88], "src": [224,128], "f": 0, "t": 540, "d": [566], "a": 1 }, + { "px": [312,88], "src": [224,128], "f": 0, "t": 540, "d": [567], "a": 1 }, + { "px": [320,88], "src": [224,128], "f": 0, "t": 540, "d": [568], "a": 1 }, + { "px": [328,88], "src": [224,128], "f": 0, "t": 540, "d": [569], "a": 1 }, + { "px": [336,88], "src": [224,128], "f": 0, "t": 540, "d": [570], "a": 1 }, + { "px": [344,88], "src": [224,128], "f": 0, "t": 540, "d": [571], "a": 1 }, + { "px": [352,88], "src": [224,128], "f": 0, "t": 540, "d": [572], "a": 1 }, + { "px": [360,88], "src": [224,128], "f": 0, "t": 540, "d": [573], "a": 1 }, + { "px": [368,88], "src": [224,128], "f": 0, "t": 540, "d": [574], "a": 1 }, + { "px": [376,88], "src": [232,128], "f": 0, "t": 541, "d": [575], "a": 1 }, + { "px": [0,96], "src": [216,128], "f": 0, "t": 539, "d": [576], "a": 1 }, + { "px": [8,96], "src": [224,128], "f": 0, "t": 540, "d": [577], "a": 1 }, + { "px": [16,96], "src": [224,128], "f": 0, "t": 540, "d": [578], "a": 1 }, + { "px": [24,96], "src": [224,128], "f": 0, "t": 540, "d": [579], "a": 1 }, + { "px": [32,96], "src": [224,128], "f": 0, "t": 540, "d": [580], "a": 1 }, + { "px": [40,96], "src": [224,128], "f": 0, "t": 540, "d": [581], "a": 1 }, + { "px": [48,96], "src": [224,128], "f": 0, "t": 540, "d": [582], "a": 1 }, + { "px": [56,96], "src": [224,128], "f": 0, "t": 540, "d": [583], "a": 1 }, + { "px": [64,96], "src": [224,128], "f": 0, "t": 540, "d": [584], "a": 1 }, + { "px": [72,96], "src": [224,128], "f": 0, "t": 540, "d": [585], "a": 1 }, + { "px": [80,96], "src": [224,128], "f": 0, "t": 540, "d": [586], "a": 1 }, + { "px": [88,96], "src": [224,128], "f": 0, "t": 540, "d": [587], "a": 1 }, + { "px": [96,96], "src": [224,128], "f": 0, "t": 540, "d": [588], "a": 1 }, + { "px": [104,96], "src": [224,128], "f": 0, "t": 540, "d": [589], "a": 1 }, + { "px": [112,96], "src": [224,128], "f": 0, "t": 540, "d": [590], "a": 1 }, + { "px": [120,96], "src": [224,128], "f": 0, "t": 540, "d": [591], "a": 1 }, + { "px": [128,96], "src": [224,128], "f": 0, "t": 540, "d": [592], "a": 1 }, + { "px": [136,96], "src": [224,128], "f": 0, "t": 540, "d": [593], "a": 1 }, + { "px": [144,96], "src": [224,128], "f": 0, "t": 540, "d": [594], "a": 1 }, + { "px": [152,96], "src": [224,128], "f": 0, "t": 540, "d": [595], "a": 1 }, + { "px": [160,96], "src": [224,128], "f": 0, "t": 540, "d": [596], "a": 1 }, + { "px": [168,96], "src": [224,128], "f": 0, "t": 540, "d": [597], "a": 1 }, + { "px": [176,96], "src": [224,128], "f": 0, "t": 540, "d": [598], "a": 1 }, + { "px": [184,96], "src": [224,128], "f": 0, "t": 540, "d": [599], "a": 1 }, + { "px": [192,96], "src": [224,128], "f": 0, "t": 540, "d": [600], "a": 1 }, + { "px": [200,96], "src": [224,128], "f": 0, "t": 540, "d": [601], "a": 1 }, + { "px": [208,96], "src": [224,128], "f": 0, "t": 540, "d": [602], "a": 1 }, + { "px": [216,96], "src": [224,128], "f": 0, "t": 540, "d": [603], "a": 1 }, + { "px": [224,96], "src": [224,128], "f": 0, "t": 540, "d": [604], "a": 1 }, + { "px": [232,96], "src": [224,128], "f": 0, "t": 540, "d": [605], "a": 1 }, + { "px": [240,96], "src": [224,128], "f": 0, "t": 540, "d": [606], "a": 1 }, + { "px": [248,96], "src": [224,128], "f": 0, "t": 540, "d": [607], "a": 1 }, + { "px": [256,96], "src": [224,128], "f": 0, "t": 540, "d": [608], "a": 1 }, + { "px": [264,96], "src": [224,128], "f": 0, "t": 540, "d": [609], "a": 1 }, + { "px": [272,96], "src": [224,128], "f": 0, "t": 540, "d": [610], "a": 1 }, + { "px": [280,96], "src": [224,128], "f": 0, "t": 540, "d": [611], "a": 1 }, + { "px": [288,96], "src": [224,128], "f": 0, "t": 540, "d": [612], "a": 1 }, + { "px": [296,96], "src": [224,128], "f": 0, "t": 540, "d": [613], "a": 1 }, + { "px": [304,96], "src": [224,128], "f": 0, "t": 540, "d": [614], "a": 1 }, + { "px": [312,96], "src": [224,128], "f": 0, "t": 540, "d": [615], "a": 1 }, + { "px": [320,96], "src": [224,128], "f": 0, "t": 540, "d": [616], "a": 1 }, + { "px": [328,96], "src": [224,128], "f": 0, "t": 540, "d": [617], "a": 1 }, + { "px": [336,96], "src": [224,128], "f": 0, "t": 540, "d": [618], "a": 1 }, + { "px": [344,96], "src": [224,128], "f": 0, "t": 540, "d": [619], "a": 1 }, + { "px": [352,96], "src": [224,128], "f": 0, "t": 540, "d": [620], "a": 1 }, + { "px": [360,96], "src": [224,128], "f": 0, "t": 540, "d": [621], "a": 1 }, + { "px": [368,96], "src": [224,128], "f": 0, "t": 540, "d": [622], "a": 1 }, + { "px": [376,96], "src": [232,128], "f": 0, "t": 541, "d": [623], "a": 1 }, + { "px": [0,104], "src": [216,128], "f": 0, "t": 539, "d": [624], "a": 1 }, + { "px": [8,104], "src": [224,128], "f": 0, "t": 540, "d": [625], "a": 1 }, + { "px": [16,104], "src": [224,128], "f": 0, "t": 540, "d": [626], "a": 1 }, + { "px": [24,104], "src": [224,128], "f": 0, "t": 540, "d": [627], "a": 1 }, + { "px": [32,104], "src": [224,128], "f": 0, "t": 540, "d": [628], "a": 1 }, + { "px": [40,104], "src": [224,128], "f": 0, "t": 540, "d": [629], "a": 1 }, + { "px": [48,104], "src": [224,128], "f": 0, "t": 540, "d": [630], "a": 1 }, + { "px": [56,104], "src": [224,128], "f": 0, "t": 540, "d": [631], "a": 1 }, + { "px": [64,104], "src": [224,128], "f": 0, "t": 540, "d": [632], "a": 1 }, + { "px": [72,104], "src": [224,128], "f": 0, "t": 540, "d": [633], "a": 1 }, + { "px": [80,104], "src": [224,128], "f": 0, "t": 540, "d": [634], "a": 1 }, + { "px": [88,104], "src": [224,128], "f": 0, "t": 540, "d": [635], "a": 1 }, + { "px": [96,104], "src": [224,128], "f": 0, "t": 540, "d": [636], "a": 1 }, + { "px": [104,104], "src": [224,128], "f": 0, "t": 540, "d": [637], "a": 1 }, + { "px": [112,104], "src": [224,128], "f": 0, "t": 540, "d": [638], "a": 1 }, + { "px": [120,104], "src": [224,128], "f": 0, "t": 540, "d": [639], "a": 1 }, + { "px": [128,104], "src": [224,128], "f": 0, "t": 540, "d": [640], "a": 1 }, + { "px": [136,104], "src": [224,128], "f": 0, "t": 540, "d": [641], "a": 1 }, + { "px": [144,104], "src": [224,128], "f": 0, "t": 540, "d": [642], "a": 1 }, + { "px": [152,104], "src": [224,128], "f": 0, "t": 540, "d": [643], "a": 1 }, + { "px": [160,104], "src": [224,128], "f": 0, "t": 540, "d": [644], "a": 1 }, + { "px": [168,104], "src": [224,128], "f": 0, "t": 540, "d": [645], "a": 1 }, + { "px": [176,104], "src": [224,128], "f": 0, "t": 540, "d": [646], "a": 1 }, + { "px": [184,104], "src": [224,128], "f": 0, "t": 540, "d": [647], "a": 1 }, + { "px": [192,104], "src": [224,128], "f": 0, "t": 540, "d": [648], "a": 1 }, + { "px": [200,104], "src": [224,128], "f": 0, "t": 540, "d": [649], "a": 1 }, + { "px": [208,104], "src": [224,128], "f": 0, "t": 540, "d": [650], "a": 1 }, + { "px": [216,104], "src": [224,128], "f": 0, "t": 540, "d": [651], "a": 1 }, + { "px": [224,104], "src": [224,128], "f": 0, "t": 540, "d": [652], "a": 1 }, + { "px": [232,104], "src": [224,128], "f": 0, "t": 540, "d": [653], "a": 1 }, + { "px": [240,104], "src": [224,128], "f": 0, "t": 540, "d": [654], "a": 1 }, + { "px": [248,104], "src": [224,128], "f": 0, "t": 540, "d": [655], "a": 1 }, + { "px": [256,104], "src": [224,128], "f": 0, "t": 540, "d": [656], "a": 1 }, + { "px": [264,104], "src": [224,128], "f": 0, "t": 540, "d": [657], "a": 1 }, + { "px": [272,104], "src": [224,128], "f": 0, "t": 540, "d": [658], "a": 1 }, + { "px": [280,104], "src": [224,128], "f": 0, "t": 540, "d": [659], "a": 1 }, + { "px": [288,104], "src": [224,128], "f": 0, "t": 540, "d": [660], "a": 1 }, + { "px": [296,104], "src": [224,128], "f": 0, "t": 540, "d": [661], "a": 1 }, + { "px": [304,104], "src": [224,128], "f": 0, "t": 540, "d": [662], "a": 1 }, + { "px": [312,104], "src": [224,128], "f": 0, "t": 540, "d": [663], "a": 1 }, + { "px": [320,104], "src": [224,128], "f": 0, "t": 540, "d": [664], "a": 1 }, + { "px": [328,104], "src": [224,128], "f": 0, "t": 540, "d": [665], "a": 1 }, + { "px": [336,104], "src": [224,128], "f": 0, "t": 540, "d": [666], "a": 1 }, + { "px": [344,104], "src": [224,128], "f": 0, "t": 540, "d": [667], "a": 1 }, + { "px": [352,104], "src": [224,128], "f": 0, "t": 540, "d": [668], "a": 1 }, + { "px": [360,104], "src": [224,128], "f": 0, "t": 540, "d": [669], "a": 1 }, + { "px": [368,104], "src": [224,128], "f": 0, "t": 540, "d": [670], "a": 1 }, + { "px": [376,104], "src": [232,128], "f": 0, "t": 541, "d": [671], "a": 1 }, + { "px": [0,112], "src": [216,128], "f": 0, "t": 539, "d": [672], "a": 1 }, + { "px": [8,112], "src": [224,128], "f": 0, "t": 540, "d": [673], "a": 1 }, + { "px": [16,112], "src": [224,128], "f": 0, "t": 540, "d": [674], "a": 1 }, + { "px": [24,112], "src": [224,128], "f": 0, "t": 540, "d": [675], "a": 1 }, + { "px": [32,112], "src": [224,128], "f": 0, "t": 540, "d": [676], "a": 1 }, + { "px": [40,112], "src": [224,128], "f": 0, "t": 540, "d": [677], "a": 1 }, + { "px": [48,112], "src": [224,128], "f": 0, "t": 540, "d": [678], "a": 1 }, + { "px": [56,112], "src": [224,128], "f": 0, "t": 540, "d": [679], "a": 1 }, + { "px": [64,112], "src": [224,128], "f": 0, "t": 540, "d": [680], "a": 1 }, + { "px": [72,112], "src": [224,128], "f": 0, "t": 540, "d": [681], "a": 1 }, + { "px": [80,112], "src": [224,128], "f": 0, "t": 540, "d": [682], "a": 1 }, + { "px": [88,112], "src": [224,128], "f": 0, "t": 540, "d": [683], "a": 1 }, + { "px": [96,112], "src": [224,128], "f": 0, "t": 540, "d": [684], "a": 1 }, + { "px": [104,112], "src": [224,128], "f": 0, "t": 540, "d": [685], "a": 1 }, + { "px": [112,112], "src": [224,128], "f": 0, "t": 540, "d": [686], "a": 1 }, + { "px": [120,112], "src": [224,128], "f": 0, "t": 540, "d": [687], "a": 1 }, + { "px": [128,112], "src": [224,128], "f": 0, "t": 540, "d": [688], "a": 1 }, + { "px": [136,112], "src": [224,128], "f": 0, "t": 540, "d": [689], "a": 1 }, + { "px": [144,112], "src": [224,128], "f": 0, "t": 540, "d": [690], "a": 1 }, + { "px": [152,112], "src": [224,128], "f": 0, "t": 540, "d": [691], "a": 1 }, + { "px": [160,112], "src": [224,128], "f": 0, "t": 540, "d": [692], "a": 1 }, + { "px": [168,112], "src": [224,128], "f": 0, "t": 540, "d": [693], "a": 1 }, + { "px": [176,112], "src": [224,128], "f": 0, "t": 540, "d": [694], "a": 1 }, + { "px": [184,112], "src": [224,128], "f": 0, "t": 540, "d": [695], "a": 1 }, + { "px": [192,112], "src": [224,128], "f": 0, "t": 540, "d": [696], "a": 1 }, + { "px": [200,112], "src": [224,128], "f": 0, "t": 540, "d": [697], "a": 1 }, + { "px": [208,112], "src": [224,128], "f": 0, "t": 540, "d": [698], "a": 1 }, + { "px": [216,112], "src": [224,128], "f": 0, "t": 540, "d": [699], "a": 1 }, + { "px": [224,112], "src": [224,128], "f": 0, "t": 540, "d": [700], "a": 1 }, + { "px": [232,112], "src": [224,128], "f": 0, "t": 540, "d": [701], "a": 1 }, + { "px": [240,112], "src": [224,128], "f": 0, "t": 540, "d": [702], "a": 1 }, + { "px": [248,112], "src": [224,128], "f": 0, "t": 540, "d": [703], "a": 1 }, + { "px": [256,112], "src": [224,128], "f": 0, "t": 540, "d": [704], "a": 1 }, + { "px": [264,112], "src": [224,128], "f": 0, "t": 540, "d": [705], "a": 1 }, + { "px": [272,112], "src": [224,128], "f": 0, "t": 540, "d": [706], "a": 1 }, + { "px": [280,112], "src": [224,128], "f": 0, "t": 540, "d": [707], "a": 1 }, + { "px": [288,112], "src": [224,128], "f": 0, "t": 540, "d": [708], "a": 1 }, + { "px": [296,112], "src": [224,128], "f": 0, "t": 540, "d": [709], "a": 1 }, + { "px": [304,112], "src": [224,128], "f": 0, "t": 540, "d": [710], "a": 1 }, + { "px": [312,112], "src": [224,128], "f": 0, "t": 540, "d": [711], "a": 1 }, + { "px": [320,112], "src": [224,128], "f": 0, "t": 540, "d": [712], "a": 1 }, + { "px": [328,112], "src": [224,128], "f": 0, "t": 540, "d": [713], "a": 1 }, + { "px": [336,112], "src": [224,128], "f": 0, "t": 540, "d": [714], "a": 1 }, + { "px": [344,112], "src": [224,128], "f": 0, "t": 540, "d": [715], "a": 1 }, + { "px": [352,112], "src": [224,128], "f": 0, "t": 540, "d": [716], "a": 1 }, + { "px": [360,112], "src": [224,128], "f": 0, "t": 540, "d": [717], "a": 1 }, + { "px": [368,112], "src": [224,128], "f": 0, "t": 540, "d": [718], "a": 1 }, + { "px": [376,112], "src": [232,128], "f": 0, "t": 541, "d": [719], "a": 1 }, + { "px": [0,120], "src": [216,128], "f": 0, "t": 539, "d": [720], "a": 1 }, + { "px": [8,120], "src": [224,128], "f": 0, "t": 540, "d": [721], "a": 1 }, + { "px": [16,120], "src": [224,128], "f": 0, "t": 540, "d": [722], "a": 1 }, + { "px": [24,120], "src": [224,128], "f": 0, "t": 540, "d": [723], "a": 1 }, + { "px": [32,120], "src": [224,128], "f": 0, "t": 540, "d": [724], "a": 1 }, + { "px": [40,120], "src": [224,128], "f": 0, "t": 540, "d": [725], "a": 1 }, + { "px": [48,120], "src": [224,128], "f": 0, "t": 540, "d": [726], "a": 1 }, + { "px": [56,120], "src": [224,128], "f": 0, "t": 540, "d": [727], "a": 1 }, + { "px": [64,120], "src": [224,128], "f": 0, "t": 540, "d": [728], "a": 1 }, + { "px": [72,120], "src": [224,128], "f": 0, "t": 540, "d": [729], "a": 1 }, + { "px": [80,120], "src": [224,128], "f": 0, "t": 540, "d": [730], "a": 1 }, + { "px": [88,120], "src": [224,128], "f": 0, "t": 540, "d": [731], "a": 1 }, + { "px": [96,120], "src": [224,128], "f": 0, "t": 540, "d": [732], "a": 1 }, + { "px": [104,120], "src": [224,128], "f": 0, "t": 540, "d": [733], "a": 1 }, + { "px": [112,120], "src": [224,128], "f": 0, "t": 540, "d": [734], "a": 1 }, + { "px": [120,120], "src": [224,128], "f": 0, "t": 540, "d": [735], "a": 1 }, + { "px": [128,120], "src": [224,128], "f": 0, "t": 540, "d": [736], "a": 1 }, + { "px": [136,120], "src": [224,128], "f": 0, "t": 540, "d": [737], "a": 1 }, + { "px": [144,120], "src": [224,128], "f": 0, "t": 540, "d": [738], "a": 1 }, + { "px": [152,120], "src": [224,128], "f": 0, "t": 540, "d": [739], "a": 1 }, + { "px": [160,120], "src": [224,128], "f": 0, "t": 540, "d": [740], "a": 1 }, + { "px": [168,120], "src": [224,128], "f": 0, "t": 540, "d": [741], "a": 1 }, + { "px": [176,120], "src": [224,128], "f": 0, "t": 540, "d": [742], "a": 1 }, + { "px": [184,120], "src": [224,128], "f": 0, "t": 540, "d": [743], "a": 1 }, + { "px": [192,120], "src": [224,128], "f": 0, "t": 540, "d": [744], "a": 1 }, + { "px": [200,120], "src": [224,128], "f": 0, "t": 540, "d": [745], "a": 1 }, + { "px": [208,120], "src": [224,128], "f": 0, "t": 540, "d": [746], "a": 1 }, + { "px": [216,120], "src": [224,128], "f": 0, "t": 540, "d": [747], "a": 1 }, + { "px": [224,120], "src": [224,128], "f": 0, "t": 540, "d": [748], "a": 1 }, + { "px": [232,120], "src": [224,128], "f": 0, "t": 540, "d": [749], "a": 1 }, + { "px": [240,120], "src": [224,128], "f": 0, "t": 540, "d": [750], "a": 1 }, + { "px": [248,120], "src": [224,128], "f": 0, "t": 540, "d": [751], "a": 1 }, + { "px": [256,120], "src": [224,128], "f": 0, "t": 540, "d": [752], "a": 1 }, + { "px": [264,120], "src": [224,128], "f": 0, "t": 540, "d": [753], "a": 1 }, + { "px": [272,120], "src": [224,128], "f": 0, "t": 540, "d": [754], "a": 1 }, + { "px": [280,120], "src": [224,128], "f": 0, "t": 540, "d": [755], "a": 1 }, + { "px": [288,120], "src": [224,128], "f": 0, "t": 540, "d": [756], "a": 1 }, + { "px": [296,120], "src": [224,128], "f": 0, "t": 540, "d": [757], "a": 1 }, + { "px": [304,120], "src": [224,128], "f": 0, "t": 540, "d": [758], "a": 1 }, + { "px": [312,120], "src": [224,128], "f": 0, "t": 540, "d": [759], "a": 1 }, + { "px": [320,120], "src": [224,128], "f": 0, "t": 540, "d": [760], "a": 1 }, + { "px": [328,120], "src": [224,128], "f": 0, "t": 540, "d": [761], "a": 1 }, + { "px": [336,120], "src": [224,128], "f": 0, "t": 540, "d": [762], "a": 1 }, + { "px": [344,120], "src": [224,128], "f": 0, "t": 540, "d": [763], "a": 1 }, + { "px": [352,120], "src": [224,128], "f": 0, "t": 540, "d": [764], "a": 1 }, + { "px": [360,120], "src": [224,128], "f": 0, "t": 540, "d": [765], "a": 1 }, + { "px": [368,120], "src": [224,128], "f": 0, "t": 540, "d": [766], "a": 1 }, + { "px": [376,120], "src": [232,128], "f": 0, "t": 541, "d": [767], "a": 1 }, + { "px": [0,128], "src": [216,128], "f": 0, "t": 539, "d": [768], "a": 1 }, + { "px": [8,128], "src": [224,128], "f": 0, "t": 540, "d": [769], "a": 1 }, + { "px": [16,128], "src": [224,128], "f": 0, "t": 540, "d": [770], "a": 1 }, + { "px": [24,128], "src": [224,128], "f": 0, "t": 540, "d": [771], "a": 1 }, + { "px": [32,128], "src": [224,128], "f": 0, "t": 540, "d": [772], "a": 1 }, + { "px": [40,128], "src": [224,128], "f": 0, "t": 540, "d": [773], "a": 1 }, + { "px": [48,128], "src": [224,128], "f": 0, "t": 540, "d": [774], "a": 1 }, + { "px": [56,128], "src": [224,128], "f": 0, "t": 540, "d": [775], "a": 1 }, + { "px": [64,128], "src": [224,128], "f": 0, "t": 540, "d": [776], "a": 1 }, + { "px": [72,128], "src": [224,128], "f": 0, "t": 540, "d": [777], "a": 1 }, + { "px": [80,128], "src": [224,128], "f": 0, "t": 540, "d": [778], "a": 1 }, + { "px": [88,128], "src": [224,128], "f": 0, "t": 540, "d": [779], "a": 1 }, + { "px": [96,128], "src": [224,128], "f": 0, "t": 540, "d": [780], "a": 1 }, + { "px": [104,128], "src": [224,128], "f": 0, "t": 540, "d": [781], "a": 1 }, + { "px": [112,128], "src": [224,128], "f": 0, "t": 540, "d": [782], "a": 1 }, + { "px": [120,128], "src": [224,128], "f": 0, "t": 540, "d": [783], "a": 1 }, + { "px": [128,128], "src": [224,128], "f": 0, "t": 540, "d": [784], "a": 1 }, + { "px": [136,128], "src": [224,128], "f": 0, "t": 540, "d": [785], "a": 1 }, + { "px": [144,128], "src": [224,128], "f": 0, "t": 540, "d": [786], "a": 1 }, + { "px": [152,128], "src": [224,128], "f": 0, "t": 540, "d": [787], "a": 1 }, + { "px": [160,128], "src": [224,128], "f": 0, "t": 540, "d": [788], "a": 1 }, + { "px": [168,128], "src": [224,128], "f": 0, "t": 540, "d": [789], "a": 1 }, + { "px": [176,128], "src": [224,128], "f": 0, "t": 540, "d": [790], "a": 1 }, + { "px": [184,128], "src": [224,128], "f": 0, "t": 540, "d": [791], "a": 1 }, + { "px": [192,128], "src": [224,128], "f": 0, "t": 540, "d": [792], "a": 1 }, + { "px": [200,128], "src": [224,128], "f": 0, "t": 540, "d": [793], "a": 1 }, + { "px": [208,128], "src": [224,128], "f": 0, "t": 540, "d": [794], "a": 1 }, + { "px": [216,128], "src": [224,128], "f": 0, "t": 540, "d": [795], "a": 1 }, + { "px": [224,128], "src": [224,128], "f": 0, "t": 540, "d": [796], "a": 1 }, + { "px": [232,128], "src": [224,128], "f": 0, "t": 540, "d": [797], "a": 1 }, + { "px": [240,128], "src": [224,128], "f": 0, "t": 540, "d": [798], "a": 1 }, + { "px": [248,128], "src": [224,128], "f": 0, "t": 540, "d": [799], "a": 1 }, + { "px": [256,128], "src": [224,128], "f": 0, "t": 540, "d": [800], "a": 1 }, + { "px": [264,128], "src": [224,128], "f": 0, "t": 540, "d": [801], "a": 1 }, + { "px": [272,128], "src": [224,128], "f": 0, "t": 540, "d": [802], "a": 1 }, + { "px": [280,128], "src": [224,128], "f": 0, "t": 540, "d": [803], "a": 1 }, + { "px": [288,128], "src": [224,128], "f": 0, "t": 540, "d": [804], "a": 1 }, + { "px": [296,128], "src": [224,128], "f": 0, "t": 540, "d": [805], "a": 1 }, + { "px": [304,128], "src": [224,128], "f": 0, "t": 540, "d": [806], "a": 1 }, + { "px": [312,128], "src": [224,128], "f": 0, "t": 540, "d": [807], "a": 1 }, + { "px": [320,128], "src": [224,128], "f": 0, "t": 540, "d": [808], "a": 1 }, + { "px": [328,128], "src": [224,128], "f": 0, "t": 540, "d": [809], "a": 1 }, + { "px": [336,128], "src": [224,128], "f": 0, "t": 540, "d": [810], "a": 1 }, + { "px": [344,128], "src": [224,128], "f": 0, "t": 540, "d": [811], "a": 1 }, + { "px": [352,128], "src": [224,128], "f": 0, "t": 540, "d": [812], "a": 1 }, + { "px": [360,128], "src": [224,128], "f": 0, "t": 540, "d": [813], "a": 1 }, + { "px": [368,128], "src": [224,128], "f": 0, "t": 540, "d": [814], "a": 1 }, + { "px": [376,128], "src": [232,128], "f": 0, "t": 541, "d": [815], "a": 1 }, + { "px": [0,136], "src": [216,128], "f": 0, "t": 539, "d": [816], "a": 1 }, + { "px": [8,136], "src": [224,128], "f": 0, "t": 540, "d": [817], "a": 1 }, + { "px": [16,136], "src": [224,128], "f": 0, "t": 540, "d": [818], "a": 1 }, + { "px": [24,136], "src": [224,128], "f": 0, "t": 540, "d": [819], "a": 1 }, + { "px": [32,136], "src": [224,128], "f": 0, "t": 540, "d": [820], "a": 1 }, + { "px": [40,136], "src": [224,128], "f": 0, "t": 540, "d": [821], "a": 1 }, + { "px": [48,136], "src": [224,128], "f": 0, "t": 540, "d": [822], "a": 1 }, + { "px": [56,136], "src": [224,128], "f": 0, "t": 540, "d": [823], "a": 1 }, + { "px": [64,136], "src": [224,128], "f": 0, "t": 540, "d": [824], "a": 1 }, + { "px": [72,136], "src": [224,128], "f": 0, "t": 540, "d": [825], "a": 1 }, + { "px": [80,136], "src": [224,128], "f": 0, "t": 540, "d": [826], "a": 1 }, + { "px": [88,136], "src": [224,128], "f": 0, "t": 540, "d": [827], "a": 1 }, + { "px": [96,136], "src": [224,128], "f": 0, "t": 540, "d": [828], "a": 1 }, + { "px": [104,136], "src": [224,128], "f": 0, "t": 540, "d": [829], "a": 1 }, + { "px": [112,136], "src": [224,128], "f": 0, "t": 540, "d": [830], "a": 1 }, + { "px": [120,136], "src": [224,128], "f": 0, "t": 540, "d": [831], "a": 1 }, + { "px": [128,136], "src": [224,128], "f": 0, "t": 540, "d": [832], "a": 1 }, + { "px": [136,136], "src": [224,128], "f": 0, "t": 540, "d": [833], "a": 1 }, + { "px": [144,136], "src": [224,128], "f": 0, "t": 540, "d": [834], "a": 1 }, + { "px": [152,136], "src": [224,128], "f": 0, "t": 540, "d": [835], "a": 1 }, + { "px": [160,136], "src": [224,128], "f": 0, "t": 540, "d": [836], "a": 1 }, + { "px": [168,136], "src": [224,128], "f": 0, "t": 540, "d": [837], "a": 1 }, + { "px": [176,136], "src": [224,128], "f": 0, "t": 540, "d": [838], "a": 1 }, + { "px": [184,136], "src": [224,128], "f": 0, "t": 540, "d": [839], "a": 1 }, + { "px": [192,136], "src": [224,128], "f": 0, "t": 540, "d": [840], "a": 1 }, + { "px": [200,136], "src": [224,128], "f": 0, "t": 540, "d": [841], "a": 1 }, + { "px": [208,136], "src": [224,128], "f": 0, "t": 540, "d": [842], "a": 1 }, + { "px": [216,136], "src": [224,128], "f": 0, "t": 540, "d": [843], "a": 1 }, + { "px": [224,136], "src": [224,128], "f": 0, "t": 540, "d": [844], "a": 1 }, + { "px": [232,136], "src": [224,128], "f": 0, "t": 540, "d": [845], "a": 1 }, + { "px": [240,136], "src": [224,128], "f": 0, "t": 540, "d": [846], "a": 1 }, + { "px": [248,136], "src": [224,128], "f": 0, "t": 540, "d": [847], "a": 1 }, + { "px": [256,136], "src": [224,128], "f": 0, "t": 540, "d": [848], "a": 1 }, + { "px": [264,136], "src": [224,128], "f": 0, "t": 540, "d": [849], "a": 1 }, + { "px": [272,136], "src": [224,128], "f": 0, "t": 540, "d": [850], "a": 1 }, + { "px": [280,136], "src": [224,128], "f": 0, "t": 540, "d": [851], "a": 1 }, + { "px": [288,136], "src": [224,128], "f": 0, "t": 540, "d": [852], "a": 1 }, + { "px": [296,136], "src": [224,128], "f": 0, "t": 540, "d": [853], "a": 1 }, + { "px": [304,136], "src": [224,128], "f": 0, "t": 540, "d": [854], "a": 1 }, + { "px": [312,136], "src": [224,128], "f": 0, "t": 540, "d": [855], "a": 1 }, + { "px": [320,136], "src": [224,128], "f": 0, "t": 540, "d": [856], "a": 1 }, + { "px": [328,136], "src": [224,128], "f": 0, "t": 540, "d": [857], "a": 1 }, + { "px": [336,136], "src": [224,128], "f": 0, "t": 540, "d": [858], "a": 1 }, + { "px": [344,136], "src": [224,128], "f": 0, "t": 540, "d": [859], "a": 1 }, + { "px": [352,136], "src": [224,128], "f": 0, "t": 540, "d": [860], "a": 1 }, + { "px": [360,136], "src": [224,128], "f": 0, "t": 540, "d": [861], "a": 1 }, + { "px": [368,136], "src": [224,128], "f": 0, "t": 540, "d": [862], "a": 1 }, + { "px": [376,136], "src": [232,128], "f": 0, "t": 541, "d": [863], "a": 1 }, + { "px": [0,144], "src": [216,128], "f": 0, "t": 539, "d": [864], "a": 1 }, + { "px": [8,144], "src": [224,128], "f": 0, "t": 540, "d": [865], "a": 1 }, + { "px": [16,144], "src": [224,128], "f": 0, "t": 540, "d": [866], "a": 1 }, + { "px": [24,144], "src": [224,128], "f": 0, "t": 540, "d": [867], "a": 1 }, + { "px": [32,144], "src": [224,128], "f": 0, "t": 540, "d": [868], "a": 1 }, + { "px": [40,144], "src": [224,128], "f": 0, "t": 540, "d": [869], "a": 1 }, + { "px": [48,144], "src": [224,128], "f": 0, "t": 540, "d": [870], "a": 1 }, + { "px": [56,144], "src": [224,128], "f": 0, "t": 540, "d": [871], "a": 1 }, + { "px": [64,144], "src": [224,128], "f": 0, "t": 540, "d": [872], "a": 1 }, + { "px": [72,144], "src": [224,128], "f": 0, "t": 540, "d": [873], "a": 1 }, + { "px": [80,144], "src": [224,128], "f": 0, "t": 540, "d": [874], "a": 1 }, + { "px": [88,144], "src": [224,128], "f": 0, "t": 540, "d": [875], "a": 1 }, + { "px": [96,144], "src": [224,128], "f": 0, "t": 540, "d": [876], "a": 1 }, + { "px": [104,144], "src": [224,128], "f": 0, "t": 540, "d": [877], "a": 1 }, + { "px": [112,144], "src": [224,128], "f": 0, "t": 540, "d": [878], "a": 1 }, + { "px": [120,144], "src": [224,128], "f": 0, "t": 540, "d": [879], "a": 1 }, + { "px": [128,144], "src": [224,128], "f": 0, "t": 540, "d": [880], "a": 1 }, + { "px": [136,144], "src": [224,128], "f": 0, "t": 540, "d": [881], "a": 1 }, + { "px": [144,144], "src": [224,128], "f": 0, "t": 540, "d": [882], "a": 1 }, + { "px": [152,144], "src": [224,128], "f": 0, "t": 540, "d": [883], "a": 1 }, + { "px": [160,144], "src": [224,128], "f": 0, "t": 540, "d": [884], "a": 1 }, + { "px": [168,144], "src": [224,128], "f": 0, "t": 540, "d": [885], "a": 1 }, + { "px": [176,144], "src": [224,128], "f": 0, "t": 540, "d": [886], "a": 1 }, + { "px": [184,144], "src": [224,128], "f": 0, "t": 540, "d": [887], "a": 1 }, + { "px": [192,144], "src": [224,128], "f": 0, "t": 540, "d": [888], "a": 1 }, + { "px": [200,144], "src": [224,128], "f": 0, "t": 540, "d": [889], "a": 1 }, + { "px": [208,144], "src": [224,128], "f": 0, "t": 540, "d": [890], "a": 1 }, + { "px": [216,144], "src": [224,128], "f": 0, "t": 540, "d": [891], "a": 1 }, + { "px": [224,144], "src": [224,128], "f": 0, "t": 540, "d": [892], "a": 1 }, + { "px": [232,144], "src": [224,128], "f": 0, "t": 540, "d": [893], "a": 1 }, + { "px": [240,144], "src": [224,128], "f": 0, "t": 540, "d": [894], "a": 1 }, + { "px": [248,144], "src": [224,128], "f": 0, "t": 540, "d": [895], "a": 1 }, + { "px": [256,144], "src": [224,128], "f": 0, "t": 540, "d": [896], "a": 1 }, + { "px": [264,144], "src": [224,128], "f": 0, "t": 540, "d": [897], "a": 1 }, + { "px": [272,144], "src": [224,128], "f": 0, "t": 540, "d": [898], "a": 1 }, + { "px": [280,144], "src": [224,128], "f": 0, "t": 540, "d": [899], "a": 1 }, + { "px": [288,144], "src": [224,128], "f": 0, "t": 540, "d": [900], "a": 1 }, + { "px": [296,144], "src": [224,128], "f": 0, "t": 540, "d": [901], "a": 1 }, + { "px": [304,144], "src": [224,128], "f": 0, "t": 540, "d": [902], "a": 1 }, + { "px": [312,144], "src": [224,128], "f": 0, "t": 540, "d": [903], "a": 1 }, + { "px": [320,144], "src": [224,128], "f": 0, "t": 540, "d": [904], "a": 1 }, + { "px": [328,144], "src": [224,128], "f": 0, "t": 540, "d": [905], "a": 1 }, + { "px": [336,144], "src": [224,128], "f": 0, "t": 540, "d": [906], "a": 1 }, + { "px": [344,144], "src": [224,128], "f": 0, "t": 540, "d": [907], "a": 1 }, + { "px": [352,144], "src": [224,128], "f": 0, "t": 540, "d": [908], "a": 1 }, + { "px": [360,144], "src": [224,128], "f": 0, "t": 540, "d": [909], "a": 1 }, + { "px": [368,144], "src": [224,128], "f": 0, "t": 540, "d": [910], "a": 1 }, + { "px": [376,144], "src": [232,128], "f": 0, "t": 541, "d": [911], "a": 1 }, + { "px": [0,152], "src": [216,128], "f": 0, "t": 539, "d": [912], "a": 1 }, + { "px": [8,152], "src": [224,128], "f": 0, "t": 540, "d": [913], "a": 1 }, + { "px": [16,152], "src": [224,128], "f": 0, "t": 540, "d": [914], "a": 1 }, + { "px": [24,152], "src": [224,128], "f": 0, "t": 540, "d": [915], "a": 1 }, + { "px": [32,152], "src": [224,128], "f": 0, "t": 540, "d": [916], "a": 1 }, + { "px": [40,152], "src": [224,128], "f": 0, "t": 540, "d": [917], "a": 1 }, + { "px": [48,152], "src": [224,128], "f": 0, "t": 540, "d": [918], "a": 1 }, + { "px": [56,152], "src": [224,128], "f": 0, "t": 540, "d": [919], "a": 1 }, + { "px": [64,152], "src": [224,128], "f": 0, "t": 540, "d": [920], "a": 1 }, + { "px": [72,152], "src": [224,128], "f": 0, "t": 540, "d": [921], "a": 1 }, + { "px": [80,152], "src": [224,128], "f": 0, "t": 540, "d": [922], "a": 1 }, + { "px": [88,152], "src": [224,128], "f": 0, "t": 540, "d": [923], "a": 1 }, + { "px": [96,152], "src": [224,128], "f": 0, "t": 540, "d": [924], "a": 1 }, + { "px": [104,152], "src": [224,128], "f": 0, "t": 540, "d": [925], "a": 1 }, + { "px": [112,152], "src": [224,128], "f": 0, "t": 540, "d": [926], "a": 1 }, + { "px": [120,152], "src": [224,128], "f": 0, "t": 540, "d": [927], "a": 1 }, + { "px": [128,152], "src": [224,128], "f": 0, "t": 540, "d": [928], "a": 1 }, + { "px": [136,152], "src": [224,128], "f": 0, "t": 540, "d": [929], "a": 1 }, + { "px": [144,152], "src": [224,128], "f": 0, "t": 540, "d": [930], "a": 1 }, + { "px": [152,152], "src": [224,128], "f": 0, "t": 540, "d": [931], "a": 1 }, + { "px": [160,152], "src": [224,128], "f": 0, "t": 540, "d": [932], "a": 1 }, + { "px": [168,152], "src": [224,128], "f": 0, "t": 540, "d": [933], "a": 1 }, + { "px": [176,152], "src": [224,128], "f": 0, "t": 540, "d": [934], "a": 1 }, + { "px": [184,152], "src": [224,128], "f": 0, "t": 540, "d": [935], "a": 1 }, + { "px": [192,152], "src": [224,128], "f": 0, "t": 540, "d": [936], "a": 1 }, + { "px": [200,152], "src": [224,128], "f": 0, "t": 540, "d": [937], "a": 1 }, + { "px": [208,152], "src": [224,128], "f": 0, "t": 540, "d": [938], "a": 1 }, + { "px": [216,152], "src": [224,128], "f": 0, "t": 540, "d": [939], "a": 1 }, + { "px": [224,152], "src": [224,128], "f": 0, "t": 540, "d": [940], "a": 1 }, + { "px": [232,152], "src": [224,128], "f": 0, "t": 540, "d": [941], "a": 1 }, + { "px": [240,152], "src": [224,128], "f": 0, "t": 540, "d": [942], "a": 1 }, + { "px": [248,152], "src": [224,128], "f": 0, "t": 540, "d": [943], "a": 1 }, + { "px": [256,152], "src": [224,128], "f": 0, "t": 540, "d": [944], "a": 1 }, + { "px": [264,152], "src": [224,128], "f": 0, "t": 540, "d": [945], "a": 1 }, + { "px": [272,152], "src": [224,128], "f": 0, "t": 540, "d": [946], "a": 1 }, + { "px": [280,152], "src": [224,128], "f": 0, "t": 540, "d": [947], "a": 1 }, + { "px": [288,152], "src": [224,128], "f": 0, "t": 540, "d": [948], "a": 1 }, + { "px": [296,152], "src": [224,128], "f": 0, "t": 540, "d": [949], "a": 1 }, + { "px": [304,152], "src": [224,128], "f": 0, "t": 540, "d": [950], "a": 1 }, + { "px": [312,152], "src": [224,128], "f": 0, "t": 540, "d": [951], "a": 1 }, + { "px": [320,152], "src": [224,128], "f": 0, "t": 540, "d": [952], "a": 1 }, + { "px": [328,152], "src": [224,128], "f": 0, "t": 540, "d": [953], "a": 1 }, + { "px": [336,152], "src": [224,128], "f": 0, "t": 540, "d": [954], "a": 1 }, + { "px": [344,152], "src": [224,128], "f": 0, "t": 540, "d": [955], "a": 1 }, + { "px": [352,152], "src": [224,128], "f": 0, "t": 540, "d": [956], "a": 1 }, + { "px": [360,152], "src": [224,128], "f": 0, "t": 540, "d": [957], "a": 1 }, + { "px": [368,152], "src": [224,128], "f": 0, "t": 540, "d": [958], "a": 1 }, + { "px": [376,152], "src": [232,128], "f": 0, "t": 541, "d": [959], "a": 1 }, + { "px": [0,160], "src": [216,128], "f": 0, "t": 539, "d": [960], "a": 1 }, + { "px": [8,160], "src": [224,128], "f": 0, "t": 540, "d": [961], "a": 1 }, + { "px": [16,160], "src": [224,128], "f": 0, "t": 540, "d": [962], "a": 1 }, + { "px": [24,160], "src": [224,128], "f": 0, "t": 540, "d": [963], "a": 1 }, + { "px": [32,160], "src": [224,128], "f": 0, "t": 540, "d": [964], "a": 1 }, + { "px": [40,160], "src": [224,128], "f": 0, "t": 540, "d": [965], "a": 1 }, + { "px": [48,160], "src": [224,128], "f": 0, "t": 540, "d": [966], "a": 1 }, + { "px": [56,160], "src": [224,128], "f": 0, "t": 540, "d": [967], "a": 1 }, + { "px": [64,160], "src": [224,128], "f": 0, "t": 540, "d": [968], "a": 1 }, + { "px": [72,160], "src": [224,128], "f": 0, "t": 540, "d": [969], "a": 1 }, + { "px": [80,160], "src": [224,128], "f": 0, "t": 540, "d": [970], "a": 1 }, + { "px": [88,160], "src": [224,128], "f": 0, "t": 540, "d": [971], "a": 1 }, + { "px": [96,160], "src": [224,128], "f": 0, "t": 540, "d": [972], "a": 1 }, + { "px": [104,160], "src": [224,128], "f": 0, "t": 540, "d": [973], "a": 1 }, + { "px": [112,160], "src": [224,128], "f": 0, "t": 540, "d": [974], "a": 1 }, + { "px": [120,160], "src": [224,128], "f": 0, "t": 540, "d": [975], "a": 1 }, + { "px": [128,160], "src": [224,128], "f": 0, "t": 540, "d": [976], "a": 1 }, + { "px": [136,160], "src": [224,128], "f": 0, "t": 540, "d": [977], "a": 1 }, + { "px": [144,160], "src": [224,128], "f": 0, "t": 540, "d": [978], "a": 1 }, + { "px": [152,160], "src": [224,128], "f": 0, "t": 540, "d": [979], "a": 1 }, + { "px": [160,160], "src": [224,128], "f": 0, "t": 540, "d": [980], "a": 1 }, + { "px": [168,160], "src": [224,128], "f": 0, "t": 540, "d": [981], "a": 1 }, + { "px": [176,160], "src": [224,128], "f": 0, "t": 540, "d": [982], "a": 1 }, + { "px": [184,160], "src": [224,128], "f": 0, "t": 540, "d": [983], "a": 1 }, + { "px": [192,160], "src": [224,128], "f": 0, "t": 540, "d": [984], "a": 1 }, + { "px": [200,160], "src": [224,128], "f": 0, "t": 540, "d": [985], "a": 1 }, + { "px": [208,160], "src": [224,128], "f": 0, "t": 540, "d": [986], "a": 1 }, + { "px": [216,160], "src": [224,128], "f": 0, "t": 540, "d": [987], "a": 1 }, + { "px": [224,160], "src": [224,128], "f": 0, "t": 540, "d": [988], "a": 1 }, + { "px": [232,160], "src": [224,128], "f": 0, "t": 540, "d": [989], "a": 1 }, + { "px": [240,160], "src": [224,128], "f": 0, "t": 540, "d": [990], "a": 1 }, + { "px": [248,160], "src": [224,128], "f": 0, "t": 540, "d": [991], "a": 1 }, + { "px": [256,160], "src": [224,128], "f": 0, "t": 540, "d": [992], "a": 1 }, + { "px": [264,160], "src": [224,128], "f": 0, "t": 540, "d": [993], "a": 1 }, + { "px": [272,160], "src": [224,128], "f": 0, "t": 540, "d": [994], "a": 1 }, + { "px": [280,160], "src": [224,128], "f": 0, "t": 540, "d": [995], "a": 1 }, + { "px": [288,160], "src": [224,128], "f": 0, "t": 540, "d": [996], "a": 1 }, + { "px": [296,160], "src": [224,128], "f": 0, "t": 540, "d": [997], "a": 1 }, + { "px": [304,160], "src": [224,128], "f": 0, "t": 540, "d": [998], "a": 1 }, + { "px": [312,160], "src": [224,128], "f": 0, "t": 540, "d": [999], "a": 1 }, + { "px": [320,160], "src": [224,128], "f": 0, "t": 540, "d": [1000], "a": 1 }, + { "px": [328,160], "src": [224,128], "f": 0, "t": 540, "d": [1001], "a": 1 }, + { "px": [336,160], "src": [224,128], "f": 0, "t": 540, "d": [1002], "a": 1 }, + { "px": [344,160], "src": [224,128], "f": 0, "t": 540, "d": [1003], "a": 1 }, + { "px": [352,160], "src": [224,128], "f": 0, "t": 540, "d": [1004], "a": 1 }, + { "px": [360,160], "src": [224,128], "f": 0, "t": 540, "d": [1005], "a": 1 }, + { "px": [368,160], "src": [224,128], "f": 0, "t": 540, "d": [1006], "a": 1 }, + { "px": [376,160], "src": [232,128], "f": 0, "t": 541, "d": [1007], "a": 1 }, + { "px": [0,168], "src": [216,128], "f": 0, "t": 539, "d": [1008], "a": 1 }, + { "px": [8,168], "src": [224,128], "f": 0, "t": 540, "d": [1009], "a": 1 }, + { "px": [16,168], "src": [224,128], "f": 0, "t": 540, "d": [1010], "a": 1 }, + { "px": [24,168], "src": [224,128], "f": 0, "t": 540, "d": [1011], "a": 1 }, + { "px": [32,168], "src": [224,128], "f": 0, "t": 540, "d": [1012], "a": 1 }, + { "px": [40,168], "src": [224,128], "f": 0, "t": 540, "d": [1013], "a": 1 }, + { "px": [48,168], "src": [224,128], "f": 0, "t": 540, "d": [1014], "a": 1 }, + { "px": [56,168], "src": [224,128], "f": 0, "t": 540, "d": [1015], "a": 1 }, + { "px": [64,168], "src": [224,128], "f": 0, "t": 540, "d": [1016], "a": 1 }, + { "px": [72,168], "src": [224,128], "f": 0, "t": 540, "d": [1017], "a": 1 }, + { "px": [80,168], "src": [224,128], "f": 0, "t": 540, "d": [1018], "a": 1 }, + { "px": [88,168], "src": [224,128], "f": 0, "t": 540, "d": [1019], "a": 1 }, + { "px": [96,168], "src": [224,128], "f": 0, "t": 540, "d": [1020], "a": 1 }, + { "px": [104,168], "src": [224,128], "f": 0, "t": 540, "d": [1021], "a": 1 }, + { "px": [112,168], "src": [224,128], "f": 0, "t": 540, "d": [1022], "a": 1 }, + { "px": [120,168], "src": [224,128], "f": 0, "t": 540, "d": [1023], "a": 1 }, + { "px": [128,168], "src": [224,128], "f": 0, "t": 540, "d": [1024], "a": 1 }, + { "px": [136,168], "src": [224,128], "f": 0, "t": 540, "d": [1025], "a": 1 }, + { "px": [144,168], "src": [224,128], "f": 0, "t": 540, "d": [1026], "a": 1 }, + { "px": [152,168], "src": [224,128], "f": 0, "t": 540, "d": [1027], "a": 1 }, + { "px": [160,168], "src": [224,128], "f": 0, "t": 540, "d": [1028], "a": 1 }, + { "px": [168,168], "src": [224,128], "f": 0, "t": 540, "d": [1029], "a": 1 }, + { "px": [176,168], "src": [224,128], "f": 0, "t": 540, "d": [1030], "a": 1 }, + { "px": [184,168], "src": [224,128], "f": 0, "t": 540, "d": [1031], "a": 1 }, + { "px": [192,168], "src": [224,128], "f": 0, "t": 540, "d": [1032], "a": 1 }, + { "px": [200,168], "src": [224,128], "f": 0, "t": 540, "d": [1033], "a": 1 }, + { "px": [208,168], "src": [224,128], "f": 0, "t": 540, "d": [1034], "a": 1 }, + { "px": [216,168], "src": [224,128], "f": 0, "t": 540, "d": [1035], "a": 1 }, + { "px": [224,168], "src": [224,128], "f": 0, "t": 540, "d": [1036], "a": 1 }, + { "px": [232,168], "src": [224,128], "f": 0, "t": 540, "d": [1037], "a": 1 }, + { "px": [240,168], "src": [224,128], "f": 0, "t": 540, "d": [1038], "a": 1 }, + { "px": [248,168], "src": [224,128], "f": 0, "t": 540, "d": [1039], "a": 1 }, + { "px": [256,168], "src": [224,128], "f": 0, "t": 540, "d": [1040], "a": 1 }, + { "px": [264,168], "src": [224,128], "f": 0, "t": 540, "d": [1041], "a": 1 }, + { "px": [272,168], "src": [224,128], "f": 0, "t": 540, "d": [1042], "a": 1 }, + { "px": [280,168], "src": [224,128], "f": 0, "t": 540, "d": [1043], "a": 1 }, + { "px": [288,168], "src": [224,128], "f": 0, "t": 540, "d": [1044], "a": 1 }, + { "px": [296,168], "src": [224,128], "f": 0, "t": 540, "d": [1045], "a": 1 }, + { "px": [304,168], "src": [224,128], "f": 0, "t": 540, "d": [1046], "a": 1 }, + { "px": [312,168], "src": [224,128], "f": 0, "t": 540, "d": [1047], "a": 1 }, + { "px": [320,168], "src": [224,128], "f": 0, "t": 540, "d": [1048], "a": 1 }, + { "px": [328,168], "src": [224,128], "f": 0, "t": 540, "d": [1049], "a": 1 }, + { "px": [336,168], "src": [224,128], "f": 0, "t": 540, "d": [1050], "a": 1 }, + { "px": [344,168], "src": [224,128], "f": 0, "t": 540, "d": [1051], "a": 1 }, + { "px": [352,168], "src": [224,128], "f": 0, "t": 540, "d": [1052], "a": 1 }, + { "px": [360,168], "src": [224,128], "f": 0, "t": 540, "d": [1053], "a": 1 }, + { "px": [368,168], "src": [224,128], "f": 0, "t": 540, "d": [1054], "a": 1 }, + { "px": [376,168], "src": [232,128], "f": 0, "t": 541, "d": [1055], "a": 1 }, + { "px": [0,176], "src": [216,128], "f": 0, "t": 539, "d": [1056], "a": 1 }, + { "px": [8,176], "src": [224,128], "f": 0, "t": 540, "d": [1057], "a": 1 }, + { "px": [16,176], "src": [224,128], "f": 0, "t": 540, "d": [1058], "a": 1 }, + { "px": [24,176], "src": [224,128], "f": 0, "t": 540, "d": [1059], "a": 1 }, + { "px": [32,176], "src": [224,128], "f": 0, "t": 540, "d": [1060], "a": 1 }, + { "px": [40,176], "src": [224,128], "f": 0, "t": 540, "d": [1061], "a": 1 }, + { "px": [48,176], "src": [224,128], "f": 0, "t": 540, "d": [1062], "a": 1 }, + { "px": [56,176], "src": [224,128], "f": 0, "t": 540, "d": [1063], "a": 1 }, + { "px": [64,176], "src": [224,128], "f": 0, "t": 540, "d": [1064], "a": 1 }, + { "px": [72,176], "src": [224,128], "f": 0, "t": 540, "d": [1065], "a": 1 }, + { "px": [80,176], "src": [224,128], "f": 0, "t": 540, "d": [1066], "a": 1 }, + { "px": [88,176], "src": [224,128], "f": 0, "t": 540, "d": [1067], "a": 1 }, + { "px": [96,176], "src": [224,128], "f": 0, "t": 540, "d": [1068], "a": 1 }, + { "px": [104,176], "src": [224,128], "f": 0, "t": 540, "d": [1069], "a": 1 }, + { "px": [112,176], "src": [224,128], "f": 0, "t": 540, "d": [1070], "a": 1 }, + { "px": [120,176], "src": [224,128], "f": 0, "t": 540, "d": [1071], "a": 1 }, + { "px": [128,176], "src": [224,128], "f": 0, "t": 540, "d": [1072], "a": 1 }, + { "px": [136,176], "src": [224,128], "f": 0, "t": 540, "d": [1073], "a": 1 }, + { "px": [144,176], "src": [224,128], "f": 0, "t": 540, "d": [1074], "a": 1 }, + { "px": [152,176], "src": [224,128], "f": 0, "t": 540, "d": [1075], "a": 1 }, + { "px": [160,176], "src": [224,128], "f": 0, "t": 540, "d": [1076], "a": 1 }, + { "px": [168,176], "src": [224,128], "f": 0, "t": 540, "d": [1077], "a": 1 }, + { "px": [176,176], "src": [224,128], "f": 0, "t": 540, "d": [1078], "a": 1 }, + { "px": [184,176], "src": [224,128], "f": 0, "t": 540, "d": [1079], "a": 1 }, + { "px": [192,176], "src": [224,128], "f": 0, "t": 540, "d": [1080], "a": 1 }, + { "px": [200,176], "src": [224,128], "f": 0, "t": 540, "d": [1081], "a": 1 }, + { "px": [208,176], "src": [224,128], "f": 0, "t": 540, "d": [1082], "a": 1 }, + { "px": [216,176], "src": [224,128], "f": 0, "t": 540, "d": [1083], "a": 1 }, + { "px": [224,176], "src": [224,128], "f": 0, "t": 540, "d": [1084], "a": 1 }, + { "px": [232,176], "src": [224,128], "f": 0, "t": 540, "d": [1085], "a": 1 }, + { "px": [240,176], "src": [224,128], "f": 0, "t": 540, "d": [1086], "a": 1 }, + { "px": [248,176], "src": [224,128], "f": 0, "t": 540, "d": [1087], "a": 1 }, + { "px": [256,176], "src": [224,128], "f": 0, "t": 540, "d": [1088], "a": 1 }, + { "px": [264,176], "src": [224,128], "f": 0, "t": 540, "d": [1089], "a": 1 }, + { "px": [272,176], "src": [224,128], "f": 0, "t": 540, "d": [1090], "a": 1 }, + { "px": [280,176], "src": [224,128], "f": 0, "t": 540, "d": [1091], "a": 1 }, + { "px": [288,176], "src": [224,128], "f": 0, "t": 540, "d": [1092], "a": 1 }, + { "px": [296,176], "src": [224,128], "f": 0, "t": 540, "d": [1093], "a": 1 }, + { "px": [304,176], "src": [224,128], "f": 0, "t": 540, "d": [1094], "a": 1 }, + { "px": [312,176], "src": [224,128], "f": 0, "t": 540, "d": [1095], "a": 1 }, + { "px": [320,176], "src": [224,128], "f": 0, "t": 540, "d": [1096], "a": 1 }, + { "px": [328,176], "src": [224,128], "f": 0, "t": 540, "d": [1097], "a": 1 }, + { "px": [336,176], "src": [224,128], "f": 0, "t": 540, "d": [1098], "a": 1 }, + { "px": [344,176], "src": [224,128], "f": 0, "t": 540, "d": [1099], "a": 1 }, + { "px": [352,176], "src": [224,128], "f": 0, "t": 540, "d": [1100], "a": 1 }, + { "px": [360,176], "src": [224,128], "f": 0, "t": 540, "d": [1101], "a": 1 }, + { "px": [368,176], "src": [224,128], "f": 0, "t": 540, "d": [1102], "a": 1 }, + { "px": [376,176], "src": [232,128], "f": 0, "t": 541, "d": [1103], "a": 1 }, + { "px": [0,184], "src": [216,128], "f": 0, "t": 539, "d": [1104], "a": 1 }, + { "px": [8,184], "src": [224,128], "f": 0, "t": 540, "d": [1105], "a": 1 }, + { "px": [16,184], "src": [224,128], "f": 0, "t": 540, "d": [1106], "a": 1 }, + { "px": [24,184], "src": [224,128], "f": 0, "t": 540, "d": [1107], "a": 1 }, + { "px": [32,184], "src": [224,128], "f": 0, "t": 540, "d": [1108], "a": 1 }, + { "px": [40,184], "src": [224,128], "f": 0, "t": 540, "d": [1109], "a": 1 }, + { "px": [48,184], "src": [224,128], "f": 0, "t": 540, "d": [1110], "a": 1 }, + { "px": [56,184], "src": [224,128], "f": 0, "t": 540, "d": [1111], "a": 1 }, + { "px": [64,184], "src": [224,128], "f": 0, "t": 540, "d": [1112], "a": 1 }, + { "px": [72,184], "src": [224,128], "f": 0, "t": 540, "d": [1113], "a": 1 }, + { "px": [80,184], "src": [224,128], "f": 0, "t": 540, "d": [1114], "a": 1 }, + { "px": [88,184], "src": [224,128], "f": 0, "t": 540, "d": [1115], "a": 1 }, + { "px": [96,184], "src": [224,128], "f": 0, "t": 540, "d": [1116], "a": 1 }, + { "px": [104,184], "src": [224,128], "f": 0, "t": 540, "d": [1117], "a": 1 }, + { "px": [112,184], "src": [224,128], "f": 0, "t": 540, "d": [1118], "a": 1 }, + { "px": [120,184], "src": [224,128], "f": 0, "t": 540, "d": [1119], "a": 1 }, + { "px": [128,184], "src": [224,128], "f": 0, "t": 540, "d": [1120], "a": 1 }, + { "px": [136,184], "src": [224,128], "f": 0, "t": 540, "d": [1121], "a": 1 }, + { "px": [144,184], "src": [224,128], "f": 0, "t": 540, "d": [1122], "a": 1 }, + { "px": [152,184], "src": [224,128], "f": 0, "t": 540, "d": [1123], "a": 1 }, + { "px": [160,184], "src": [224,128], "f": 0, "t": 540, "d": [1124], "a": 1 }, + { "px": [168,184], "src": [224,128], "f": 0, "t": 540, "d": [1125], "a": 1 }, + { "px": [176,184], "src": [224,128], "f": 0, "t": 540, "d": [1126], "a": 1 }, + { "px": [184,184], "src": [224,128], "f": 0, "t": 540, "d": [1127], "a": 1 }, + { "px": [192,184], "src": [224,128], "f": 0, "t": 540, "d": [1128], "a": 1 }, + { "px": [200,184], "src": [224,128], "f": 0, "t": 540, "d": [1129], "a": 1 }, + { "px": [208,184], "src": [224,128], "f": 0, "t": 540, "d": [1130], "a": 1 }, + { "px": [216,184], "src": [224,128], "f": 0, "t": 540, "d": [1131], "a": 1 }, + { "px": [224,184], "src": [224,128], "f": 0, "t": 540, "d": [1132], "a": 1 }, + { "px": [232,184], "src": [224,128], "f": 0, "t": 540, "d": [1133], "a": 1 }, + { "px": [240,184], "src": [224,128], "f": 0, "t": 540, "d": [1134], "a": 1 }, + { "px": [248,184], "src": [224,128], "f": 0, "t": 540, "d": [1135], "a": 1 }, + { "px": [256,184], "src": [224,128], "f": 0, "t": 540, "d": [1136], "a": 1 }, + { "px": [264,184], "src": [224,128], "f": 0, "t": 540, "d": [1137], "a": 1 }, + { "px": [272,184], "src": [224,128], "f": 0, "t": 540, "d": [1138], "a": 1 }, + { "px": [280,184], "src": [224,128], "f": 0, "t": 540, "d": [1139], "a": 1 }, + { "px": [288,184], "src": [224,128], "f": 0, "t": 540, "d": [1140], "a": 1 }, + { "px": [296,184], "src": [224,128], "f": 0, "t": 540, "d": [1141], "a": 1 }, + { "px": [304,184], "src": [224,128], "f": 0, "t": 540, "d": [1142], "a": 1 }, + { "px": [312,184], "src": [224,128], "f": 0, "t": 540, "d": [1143], "a": 1 }, + { "px": [320,184], "src": [224,128], "f": 0, "t": 540, "d": [1144], "a": 1 }, + { "px": [328,184], "src": [224,128], "f": 0, "t": 540, "d": [1145], "a": 1 }, + { "px": [336,184], "src": [224,128], "f": 0, "t": 540, "d": [1146], "a": 1 }, + { "px": [344,184], "src": [224,128], "f": 0, "t": 540, "d": [1147], "a": 1 }, + { "px": [352,184], "src": [224,128], "f": 0, "t": 540, "d": [1148], "a": 1 }, + { "px": [360,184], "src": [224,128], "f": 0, "t": 540, "d": [1149], "a": 1 }, + { "px": [368,184], "src": [224,128], "f": 0, "t": 540, "d": [1150], "a": 1 }, + { "px": [376,184], "src": [232,128], "f": 0, "t": 541, "d": [1151], "a": 1 }, + { "px": [0,192], "src": [216,128], "f": 0, "t": 539, "d": [1152], "a": 1 }, + { "px": [8,192], "src": [224,128], "f": 0, "t": 540, "d": [1153], "a": 1 }, + { "px": [16,192], "src": [224,128], "f": 0, "t": 540, "d": [1154], "a": 1 }, + { "px": [24,192], "src": [224,128], "f": 0, "t": 540, "d": [1155], "a": 1 }, + { "px": [32,192], "src": [224,128], "f": 0, "t": 540, "d": [1156], "a": 1 }, + { "px": [40,192], "src": [224,128], "f": 0, "t": 540, "d": [1157], "a": 1 }, + { "px": [48,192], "src": [224,128], "f": 0, "t": 540, "d": [1158], "a": 1 }, + { "px": [56,192], "src": [224,128], "f": 0, "t": 540, "d": [1159], "a": 1 }, + { "px": [64,192], "src": [224,128], "f": 0, "t": 540, "d": [1160], "a": 1 }, + { "px": [72,192], "src": [224,128], "f": 0, "t": 540, "d": [1161], "a": 1 }, + { "px": [80,192], "src": [224,128], "f": 0, "t": 540, "d": [1162], "a": 1 }, + { "px": [88,192], "src": [224,128], "f": 0, "t": 540, "d": [1163], "a": 1 }, + { "px": [96,192], "src": [224,128], "f": 0, "t": 540, "d": [1164], "a": 1 }, + { "px": [104,192], "src": [224,128], "f": 0, "t": 540, "d": [1165], "a": 1 }, + { "px": [112,192], "src": [224,128], "f": 0, "t": 540, "d": [1166], "a": 1 }, + { "px": [120,192], "src": [224,128], "f": 0, "t": 540, "d": [1167], "a": 1 }, + { "px": [128,192], "src": [224,128], "f": 0, "t": 540, "d": [1168], "a": 1 }, + { "px": [136,192], "src": [224,128], "f": 0, "t": 540, "d": [1169], "a": 1 }, + { "px": [144,192], "src": [224,128], "f": 0, "t": 540, "d": [1170], "a": 1 }, + { "px": [152,192], "src": [224,128], "f": 0, "t": 540, "d": [1171], "a": 1 }, + { "px": [160,192], "src": [224,128], "f": 0, "t": 540, "d": [1172], "a": 1 }, + { "px": [168,192], "src": [224,128], "f": 0, "t": 540, "d": [1173], "a": 1 }, + { "px": [176,192], "src": [224,128], "f": 0, "t": 540, "d": [1174], "a": 1 }, + { "px": [184,192], "src": [224,128], "f": 0, "t": 540, "d": [1175], "a": 1 }, + { "px": [192,192], "src": [224,128], "f": 0, "t": 540, "d": [1176], "a": 1 }, + { "px": [200,192], "src": [224,128], "f": 0, "t": 540, "d": [1177], "a": 1 }, + { "px": [208,192], "src": [224,128], "f": 0, "t": 540, "d": [1178], "a": 1 }, + { "px": [216,192], "src": [224,128], "f": 0, "t": 540, "d": [1179], "a": 1 }, + { "px": [224,192], "src": [224,128], "f": 0, "t": 540, "d": [1180], "a": 1 }, + { "px": [232,192], "src": [224,128], "f": 0, "t": 540, "d": [1181], "a": 1 }, + { "px": [240,192], "src": [224,128], "f": 0, "t": 540, "d": [1182], "a": 1 }, + { "px": [248,192], "src": [224,128], "f": 0, "t": 540, "d": [1183], "a": 1 }, + { "px": [256,192], "src": [224,128], "f": 0, "t": 540, "d": [1184], "a": 1 }, + { "px": [264,192], "src": [224,128], "f": 0, "t": 540, "d": [1185], "a": 1 }, + { "px": [272,192], "src": [224,128], "f": 0, "t": 540, "d": [1186], "a": 1 }, + { "px": [280,192], "src": [224,128], "f": 0, "t": 540, "d": [1187], "a": 1 }, + { "px": [288,192], "src": [224,128], "f": 0, "t": 540, "d": [1188], "a": 1 }, + { "px": [296,192], "src": [224,128], "f": 0, "t": 540, "d": [1189], "a": 1 }, + { "px": [304,192], "src": [224,128], "f": 0, "t": 540, "d": [1190], "a": 1 }, + { "px": [312,192], "src": [224,128], "f": 0, "t": 540, "d": [1191], "a": 1 }, + { "px": [320,192], "src": [224,128], "f": 0, "t": 540, "d": [1192], "a": 1 }, + { "px": [328,192], "src": [224,128], "f": 0, "t": 540, "d": [1193], "a": 1 }, + { "px": [336,192], "src": [224,128], "f": 0, "t": 540, "d": [1194], "a": 1 }, + { "px": [344,192], "src": [224,128], "f": 0, "t": 540, "d": [1195], "a": 1 }, + { "px": [352,192], "src": [224,128], "f": 0, "t": 540, "d": [1196], "a": 1 }, + { "px": [360,192], "src": [224,128], "f": 0, "t": 540, "d": [1197], "a": 1 }, + { "px": [368,192], "src": [224,128], "f": 0, "t": 540, "d": [1198], "a": 1 }, + { "px": [376,192], "src": [232,128], "f": 0, "t": 541, "d": [1199], "a": 1 }, + { "px": [0,200], "src": [216,128], "f": 0, "t": 539, "d": [1200], "a": 1 }, + { "px": [8,200], "src": [224,128], "f": 0, "t": 540, "d": [1201], "a": 1 }, + { "px": [16,200], "src": [224,128], "f": 0, "t": 540, "d": [1202], "a": 1 }, + { "px": [24,200], "src": [224,128], "f": 0, "t": 540, "d": [1203], "a": 1 }, + { "px": [32,200], "src": [224,128], "f": 0, "t": 540, "d": [1204], "a": 1 }, + { "px": [40,200], "src": [224,128], "f": 0, "t": 540, "d": [1205], "a": 1 }, + { "px": [48,200], "src": [224,128], "f": 0, "t": 540, "d": [1206], "a": 1 }, + { "px": [56,200], "src": [224,128], "f": 0, "t": 540, "d": [1207], "a": 1 }, + { "px": [64,200], "src": [224,128], "f": 0, "t": 540, "d": [1208], "a": 1 }, + { "px": [72,200], "src": [224,128], "f": 0, "t": 540, "d": [1209], "a": 1 }, + { "px": [80,200], "src": [224,128], "f": 0, "t": 540, "d": [1210], "a": 1 }, + { "px": [88,200], "src": [224,128], "f": 0, "t": 540, "d": [1211], "a": 1 }, + { "px": [96,200], "src": [224,128], "f": 0, "t": 540, "d": [1212], "a": 1 }, + { "px": [104,200], "src": [224,128], "f": 0, "t": 540, "d": [1213], "a": 1 }, + { "px": [112,200], "src": [224,128], "f": 0, "t": 540, "d": [1214], "a": 1 }, + { "px": [120,200], "src": [224,128], "f": 0, "t": 540, "d": [1215], "a": 1 }, + { "px": [128,200], "src": [224,128], "f": 0, "t": 540, "d": [1216], "a": 1 }, + { "px": [136,200], "src": [224,128], "f": 0, "t": 540, "d": [1217], "a": 1 }, + { "px": [144,200], "src": [224,128], "f": 0, "t": 540, "d": [1218], "a": 1 }, + { "px": [152,200], "src": [224,128], "f": 0, "t": 540, "d": [1219], "a": 1 }, + { "px": [160,200], "src": [224,128], "f": 0, "t": 540, "d": [1220], "a": 1 }, + { "px": [168,200], "src": [224,128], "f": 0, "t": 540, "d": [1221], "a": 1 }, + { "px": [176,200], "src": [224,128], "f": 0, "t": 540, "d": [1222], "a": 1 }, + { "px": [184,200], "src": [224,128], "f": 0, "t": 540, "d": [1223], "a": 1 }, + { "px": [192,200], "src": [224,128], "f": 0, "t": 540, "d": [1224], "a": 1 }, + { "px": [200,200], "src": [224,128], "f": 0, "t": 540, "d": [1225], "a": 1 }, + { "px": [208,200], "src": [224,128], "f": 0, "t": 540, "d": [1226], "a": 1 }, + { "px": [216,200], "src": [224,128], "f": 0, "t": 540, "d": [1227], "a": 1 }, + { "px": [224,200], "src": [224,128], "f": 0, "t": 540, "d": [1228], "a": 1 }, + { "px": [232,200], "src": [224,128], "f": 0, "t": 540, "d": [1229], "a": 1 }, + { "px": [240,200], "src": [224,128], "f": 0, "t": 540, "d": [1230], "a": 1 }, + { "px": [248,200], "src": [224,128], "f": 0, "t": 540, "d": [1231], "a": 1 }, + { "px": [256,200], "src": [224,128], "f": 0, "t": 540, "d": [1232], "a": 1 }, + { "px": [264,200], "src": [224,128], "f": 0, "t": 540, "d": [1233], "a": 1 }, + { "px": [272,200], "src": [224,128], "f": 0, "t": 540, "d": [1234], "a": 1 }, + { "px": [280,200], "src": [224,128], "f": 0, "t": 540, "d": [1235], "a": 1 }, + { "px": [288,200], "src": [224,128], "f": 0, "t": 540, "d": [1236], "a": 1 }, + { "px": [296,200], "src": [224,128], "f": 0, "t": 540, "d": [1237], "a": 1 }, + { "px": [304,200], "src": [224,128], "f": 0, "t": 540, "d": [1238], "a": 1 }, + { "px": [312,200], "src": [224,128], "f": 0, "t": 540, "d": [1239], "a": 1 }, + { "px": [320,200], "src": [224,128], "f": 0, "t": 540, "d": [1240], "a": 1 }, + { "px": [328,200], "src": [224,128], "f": 0, "t": 540, "d": [1241], "a": 1 }, + { "px": [336,200], "src": [224,128], "f": 0, "t": 540, "d": [1242], "a": 1 }, + { "px": [344,200], "src": [224,128], "f": 0, "t": 540, "d": [1243], "a": 1 }, + { "px": [352,200], "src": [224,128], "f": 0, "t": 540, "d": [1244], "a": 1 }, + { "px": [360,200], "src": [224,128], "f": 0, "t": 540, "d": [1245], "a": 1 }, + { "px": [368,200], "src": [224,128], "f": 0, "t": 540, "d": [1246], "a": 1 }, + { "px": [376,200], "src": [232,128], "f": 0, "t": 541, "d": [1247], "a": 1 }, + { "px": [0,208], "src": [216,128], "f": 0, "t": 539, "d": [1248], "a": 1 }, + { "px": [8,208], "src": [224,128], "f": 0, "t": 540, "d": [1249], "a": 1 }, + { "px": [16,208], "src": [224,128], "f": 0, "t": 540, "d": [1250], "a": 1 }, + { "px": [24,208], "src": [224,128], "f": 0, "t": 540, "d": [1251], "a": 1 }, + { "px": [32,208], "src": [224,128], "f": 0, "t": 540, "d": [1252], "a": 1 }, + { "px": [40,208], "src": [224,128], "f": 0, "t": 540, "d": [1253], "a": 1 }, + { "px": [48,208], "src": [224,128], "f": 0, "t": 540, "d": [1254], "a": 1 }, + { "px": [56,208], "src": [224,128], "f": 0, "t": 540, "d": [1255], "a": 1 }, + { "px": [64,208], "src": [224,128], "f": 0, "t": 540, "d": [1256], "a": 1 }, + { "px": [72,208], "src": [224,128], "f": 0, "t": 540, "d": [1257], "a": 1 }, + { "px": [80,208], "src": [224,128], "f": 0, "t": 540, "d": [1258], "a": 1 }, + { "px": [88,208], "src": [224,128], "f": 0, "t": 540, "d": [1259], "a": 1 }, + { "px": [96,208], "src": [224,128], "f": 0, "t": 540, "d": [1260], "a": 1 }, + { "px": [104,208], "src": [224,128], "f": 0, "t": 540, "d": [1261], "a": 1 }, + { "px": [112,208], "src": [224,128], "f": 0, "t": 540, "d": [1262], "a": 1 }, + { "px": [120,208], "src": [224,128], "f": 0, "t": 540, "d": [1263], "a": 1 }, + { "px": [128,208], "src": [224,128], "f": 0, "t": 540, "d": [1264], "a": 1 }, + { "px": [136,208], "src": [224,128], "f": 0, "t": 540, "d": [1265], "a": 1 }, + { "px": [144,208], "src": [224,128], "f": 0, "t": 540, "d": [1266], "a": 1 }, + { "px": [152,208], "src": [224,128], "f": 0, "t": 540, "d": [1267], "a": 1 }, + { "px": [160,208], "src": [224,128], "f": 0, "t": 540, "d": [1268], "a": 1 }, + { "px": [168,208], "src": [224,128], "f": 0, "t": 540, "d": [1269], "a": 1 }, + { "px": [176,208], "src": [224,128], "f": 0, "t": 540, "d": [1270], "a": 1 }, + { "px": [184,208], "src": [224,128], "f": 0, "t": 540, "d": [1271], "a": 1 }, + { "px": [192,208], "src": [224,128], "f": 0, "t": 540, "d": [1272], "a": 1 }, + { "px": [200,208], "src": [224,128], "f": 0, "t": 540, "d": [1273], "a": 1 }, + { "px": [208,208], "src": [224,128], "f": 0, "t": 540, "d": [1274], "a": 1 }, + { "px": [216,208], "src": [224,128], "f": 0, "t": 540, "d": [1275], "a": 1 }, + { "px": [224,208], "src": [224,128], "f": 0, "t": 540, "d": [1276], "a": 1 }, + { "px": [232,208], "src": [224,128], "f": 0, "t": 540, "d": [1277], "a": 1 }, + { "px": [240,208], "src": [224,128], "f": 0, "t": 540, "d": [1278], "a": 1 }, + { "px": [248,208], "src": [224,128], "f": 0, "t": 540, "d": [1279], "a": 1 }, + { "px": [256,208], "src": [224,128], "f": 0, "t": 540, "d": [1280], "a": 1 }, + { "px": [264,208], "src": [224,128], "f": 0, "t": 540, "d": [1281], "a": 1 }, + { "px": [272,208], "src": [224,128], "f": 0, "t": 540, "d": [1282], "a": 1 }, + { "px": [280,208], "src": [224,128], "f": 0, "t": 540, "d": [1283], "a": 1 }, + { "px": [288,208], "src": [224,128], "f": 0, "t": 540, "d": [1284], "a": 1 }, + { "px": [296,208], "src": [224,128], "f": 0, "t": 540, "d": [1285], "a": 1 }, + { "px": [304,208], "src": [224,128], "f": 0, "t": 540, "d": [1286], "a": 1 }, + { "px": [312,208], "src": [224,128], "f": 0, "t": 540, "d": [1287], "a": 1 }, + { "px": [320,208], "src": [224,128], "f": 0, "t": 540, "d": [1288], "a": 1 }, + { "px": [328,208], "src": [224,128], "f": 0, "t": 540, "d": [1289], "a": 1 }, + { "px": [336,208], "src": [224,128], "f": 0, "t": 540, "d": [1290], "a": 1 }, + { "px": [344,208], "src": [224,128], "f": 0, "t": 540, "d": [1291], "a": 1 }, + { "px": [352,208], "src": [224,128], "f": 0, "t": 540, "d": [1292], "a": 1 }, + { "px": [360,208], "src": [224,128], "f": 0, "t": 540, "d": [1293], "a": 1 }, + { "px": [368,208], "src": [224,128], "f": 0, "t": 540, "d": [1294], "a": 1 }, + { "px": [376,208], "src": [232,128], "f": 0, "t": 541, "d": [1295], "a": 1 }, + { "px": [0,216], "src": [216,128], "f": 0, "t": 539, "d": [1296], "a": 1 }, + { "px": [8,216], "src": [224,128], "f": 0, "t": 540, "d": [1297], "a": 1 }, + { "px": [16,216], "src": [224,128], "f": 0, "t": 540, "d": [1298], "a": 1 }, + { "px": [24,216], "src": [224,128], "f": 0, "t": 540, "d": [1299], "a": 1 }, + { "px": [32,216], "src": [224,128], "f": 0, "t": 540, "d": [1300], "a": 1 }, + { "px": [40,216], "src": [224,128], "f": 0, "t": 540, "d": [1301], "a": 1 }, + { "px": [48,216], "src": [224,128], "f": 0, "t": 540, "d": [1302], "a": 1 }, + { "px": [56,216], "src": [224,128], "f": 0, "t": 540, "d": [1303], "a": 1 }, + { "px": [64,216], "src": [224,128], "f": 0, "t": 540, "d": [1304], "a": 1 }, + { "px": [72,216], "src": [224,128], "f": 0, "t": 540, "d": [1305], "a": 1 }, + { "px": [80,216], "src": [224,128], "f": 0, "t": 540, "d": [1306], "a": 1 }, + { "px": [88,216], "src": [224,128], "f": 0, "t": 540, "d": [1307], "a": 1 }, + { "px": [96,216], "src": [224,128], "f": 0, "t": 540, "d": [1308], "a": 1 }, + { "px": [104,216], "src": [224,128], "f": 0, "t": 540, "d": [1309], "a": 1 }, + { "px": [112,216], "src": [224,128], "f": 0, "t": 540, "d": [1310], "a": 1 }, + { "px": [120,216], "src": [224,128], "f": 0, "t": 540, "d": [1311], "a": 1 }, + { "px": [128,216], "src": [224,128], "f": 0, "t": 540, "d": [1312], "a": 1 }, + { "px": [136,216], "src": [224,128], "f": 0, "t": 540, "d": [1313], "a": 1 }, + { "px": [144,216], "src": [224,128], "f": 0, "t": 540, "d": [1314], "a": 1 }, + { "px": [152,216], "src": [224,128], "f": 0, "t": 540, "d": [1315], "a": 1 }, + { "px": [160,216], "src": [224,128], "f": 0, "t": 540, "d": [1316], "a": 1 }, + { "px": [168,216], "src": [224,128], "f": 0, "t": 540, "d": [1317], "a": 1 }, + { "px": [176,216], "src": [224,128], "f": 0, "t": 540, "d": [1318], "a": 1 }, + { "px": [184,216], "src": [224,128], "f": 0, "t": 540, "d": [1319], "a": 1 }, + { "px": [192,216], "src": [224,128], "f": 0, "t": 540, "d": [1320], "a": 1 }, + { "px": [200,216], "src": [224,128], "f": 0, "t": 540, "d": [1321], "a": 1 }, + { "px": [208,216], "src": [224,128], "f": 0, "t": 540, "d": [1322], "a": 1 }, + { "px": [216,216], "src": [224,128], "f": 0, "t": 540, "d": [1323], "a": 1 }, + { "px": [224,216], "src": [224,128], "f": 0, "t": 540, "d": [1324], "a": 1 }, + { "px": [232,216], "src": [224,128], "f": 0, "t": 540, "d": [1325], "a": 1 }, + { "px": [240,216], "src": [224,128], "f": 0, "t": 540, "d": [1326], "a": 1 }, + { "px": [248,216], "src": [224,128], "f": 0, "t": 540, "d": [1327], "a": 1 }, + { "px": [256,216], "src": [224,128], "f": 0, "t": 540, "d": [1328], "a": 1 }, + { "px": [264,216], "src": [224,128], "f": 0, "t": 540, "d": [1329], "a": 1 }, + { "px": [272,216], "src": [224,128], "f": 0, "t": 540, "d": [1330], "a": 1 }, + { "px": [280,216], "src": [224,128], "f": 0, "t": 540, "d": [1331], "a": 1 }, + { "px": [288,216], "src": [224,128], "f": 0, "t": 540, "d": [1332], "a": 1 }, + { "px": [296,216], "src": [224,128], "f": 0, "t": 540, "d": [1333], "a": 1 }, + { "px": [304,216], "src": [224,128], "f": 0, "t": 540, "d": [1334], "a": 1 }, + { "px": [312,216], "src": [224,128], "f": 0, "t": 540, "d": [1335], "a": 1 }, + { "px": [320,216], "src": [224,128], "f": 0, "t": 540, "d": [1336], "a": 1 }, + { "px": [328,216], "src": [224,128], "f": 0, "t": 540, "d": [1337], "a": 1 }, + { "px": [336,216], "src": [224,128], "f": 0, "t": 540, "d": [1338], "a": 1 }, + { "px": [344,216], "src": [224,128], "f": 0, "t": 540, "d": [1339], "a": 1 }, + { "px": [352,216], "src": [224,128], "f": 0, "t": 540, "d": [1340], "a": 1 }, + { "px": [360,216], "src": [224,128], "f": 0, "t": 540, "d": [1341], "a": 1 }, + { "px": [368,216], "src": [224,128], "f": 0, "t": 540, "d": [1342], "a": 1 }, + { "px": [376,216], "src": [232,128], "f": 0, "t": 541, "d": [1343], "a": 1 }, + { "px": [0,224], "src": [216,128], "f": 0, "t": 539, "d": [1344], "a": 1 }, + { "px": [8,224], "src": [224,128], "f": 0, "t": 540, "d": [1345], "a": 1 }, + { "px": [16,224], "src": [224,128], "f": 0, "t": 540, "d": [1346], "a": 1 }, + { "px": [24,224], "src": [224,128], "f": 0, "t": 540, "d": [1347], "a": 1 }, + { "px": [32,224], "src": [224,128], "f": 0, "t": 540, "d": [1348], "a": 1 }, + { "px": [40,224], "src": [224,128], "f": 0, "t": 540, "d": [1349], "a": 1 }, + { "px": [48,224], "src": [224,128], "f": 0, "t": 540, "d": [1350], "a": 1 }, + { "px": [56,224], "src": [224,128], "f": 0, "t": 540, "d": [1351], "a": 1 }, + { "px": [64,224], "src": [224,128], "f": 0, "t": 540, "d": [1352], "a": 1 }, + { "px": [72,224], "src": [224,128], "f": 0, "t": 540, "d": [1353], "a": 1 }, + { "px": [80,224], "src": [224,128], "f": 0, "t": 540, "d": [1354], "a": 1 }, + { "px": [88,224], "src": [224,128], "f": 0, "t": 540, "d": [1355], "a": 1 }, + { "px": [96,224], "src": [224,128], "f": 0, "t": 540, "d": [1356], "a": 1 }, + { "px": [104,224], "src": [224,128], "f": 0, "t": 540, "d": [1357], "a": 1 }, + { "px": [112,224], "src": [224,128], "f": 0, "t": 540, "d": [1358], "a": 1 }, + { "px": [120,224], "src": [224,128], "f": 0, "t": 540, "d": [1359], "a": 1 }, + { "px": [128,224], "src": [224,128], "f": 0, "t": 540, "d": [1360], "a": 1 }, + { "px": [136,224], "src": [224,128], "f": 0, "t": 540, "d": [1361], "a": 1 }, + { "px": [144,224], "src": [224,128], "f": 0, "t": 540, "d": [1362], "a": 1 }, + { "px": [152,224], "src": [224,128], "f": 0, "t": 540, "d": [1363], "a": 1 }, + { "px": [160,224], "src": [224,128], "f": 0, "t": 540, "d": [1364], "a": 1 }, + { "px": [168,224], "src": [224,128], "f": 0, "t": 540, "d": [1365], "a": 1 }, + { "px": [176,224], "src": [224,128], "f": 0, "t": 540, "d": [1366], "a": 1 }, + { "px": [184,224], "src": [224,128], "f": 0, "t": 540, "d": [1367], "a": 1 }, + { "px": [192,224], "src": [224,128], "f": 0, "t": 540, "d": [1368], "a": 1 }, + { "px": [200,224], "src": [224,128], "f": 0, "t": 540, "d": [1369], "a": 1 }, + { "px": [208,224], "src": [224,128], "f": 0, "t": 540, "d": [1370], "a": 1 }, + { "px": [216,224], "src": [224,128], "f": 0, "t": 540, "d": [1371], "a": 1 }, + { "px": [224,224], "src": [224,128], "f": 0, "t": 540, "d": [1372], "a": 1 }, + { "px": [232,224], "src": [224,128], "f": 0, "t": 540, "d": [1373], "a": 1 }, + { "px": [240,224], "src": [224,128], "f": 0, "t": 540, "d": [1374], "a": 1 }, + { "px": [248,224], "src": [224,128], "f": 0, "t": 540, "d": [1375], "a": 1 }, + { "px": [256,224], "src": [224,128], "f": 0, "t": 540, "d": [1376], "a": 1 }, + { "px": [264,224], "src": [224,128], "f": 0, "t": 540, "d": [1377], "a": 1 }, + { "px": [272,224], "src": [224,128], "f": 0, "t": 540, "d": [1378], "a": 1 }, + { "px": [280,224], "src": [224,128], "f": 0, "t": 540, "d": [1379], "a": 1 }, + { "px": [288,224], "src": [224,128], "f": 0, "t": 540, "d": [1380], "a": 1 }, + { "px": [296,224], "src": [224,128], "f": 0, "t": 540, "d": [1381], "a": 1 }, + { "px": [304,224], "src": [224,128], "f": 0, "t": 540, "d": [1382], "a": 1 }, + { "px": [312,224], "src": [224,128], "f": 0, "t": 540, "d": [1383], "a": 1 }, + { "px": [320,224], "src": [224,128], "f": 0, "t": 540, "d": [1384], "a": 1 }, + { "px": [328,224], "src": [224,128], "f": 0, "t": 540, "d": [1385], "a": 1 }, + { "px": [336,224], "src": [224,128], "f": 0, "t": 540, "d": [1386], "a": 1 }, + { "px": [344,224], "src": [224,128], "f": 0, "t": 540, "d": [1387], "a": 1 }, + { "px": [352,224], "src": [224,128], "f": 0, "t": 540, "d": [1388], "a": 1 }, + { "px": [360,224], "src": [224,128], "f": 0, "t": 540, "d": [1389], "a": 1 }, + { "px": [368,224], "src": [224,128], "f": 0, "t": 540, "d": [1390], "a": 1 }, + { "px": [376,224], "src": [232,128], "f": 0, "t": 541, "d": [1391], "a": 1 }, + { "px": [0,232], "src": [216,128], "f": 0, "t": 539, "d": [1392], "a": 1 }, + { "px": [8,232], "src": [224,128], "f": 0, "t": 540, "d": [1393], "a": 1 }, + { "px": [16,232], "src": [224,128], "f": 0, "t": 540, "d": [1394], "a": 1 }, + { "px": [24,232], "src": [224,128], "f": 0, "t": 540, "d": [1395], "a": 1 }, + { "px": [32,232], "src": [224,128], "f": 0, "t": 540, "d": [1396], "a": 1 }, + { "px": [40,232], "src": [224,128], "f": 0, "t": 540, "d": [1397], "a": 1 }, + { "px": [48,232], "src": [224,128], "f": 0, "t": 540, "d": [1398], "a": 1 }, + { "px": [56,232], "src": [224,128], "f": 0, "t": 540, "d": [1399], "a": 1 }, + { "px": [64,232], "src": [224,128], "f": 0, "t": 540, "d": [1400], "a": 1 }, + { "px": [72,232], "src": [224,128], "f": 0, "t": 540, "d": [1401], "a": 1 }, + { "px": [80,232], "src": [224,128], "f": 0, "t": 540, "d": [1402], "a": 1 }, + { "px": [88,232], "src": [224,128], "f": 0, "t": 540, "d": [1403], "a": 1 }, + { "px": [96,232], "src": [224,128], "f": 0, "t": 540, "d": [1404], "a": 1 }, + { "px": [104,232], "src": [224,128], "f": 0, "t": 540, "d": [1405], "a": 1 }, + { "px": [112,232], "src": [224,128], "f": 0, "t": 540, "d": [1406], "a": 1 }, + { "px": [120,232], "src": [224,128], "f": 0, "t": 540, "d": [1407], "a": 1 }, + { "px": [128,232], "src": [224,128], "f": 0, "t": 540, "d": [1408], "a": 1 }, + { "px": [136,232], "src": [224,128], "f": 0, "t": 540, "d": [1409], "a": 1 }, + { "px": [144,232], "src": [224,128], "f": 0, "t": 540, "d": [1410], "a": 1 }, + { "px": [152,232], "src": [224,128], "f": 0, "t": 540, "d": [1411], "a": 1 }, + { "px": [160,232], "src": [224,128], "f": 0, "t": 540, "d": [1412], "a": 1 }, + { "px": [168,232], "src": [224,128], "f": 0, "t": 540, "d": [1413], "a": 1 }, + { "px": [176,232], "src": [224,128], "f": 0, "t": 540, "d": [1414], "a": 1 }, + { "px": [184,232], "src": [224,128], "f": 0, "t": 540, "d": [1415], "a": 1 }, + { "px": [192,232], "src": [224,128], "f": 0, "t": 540, "d": [1416], "a": 1 }, + { "px": [200,232], "src": [224,128], "f": 0, "t": 540, "d": [1417], "a": 1 }, + { "px": [208,232], "src": [224,128], "f": 0, "t": 540, "d": [1418], "a": 1 }, + { "px": [216,232], "src": [224,128], "f": 0, "t": 540, "d": [1419], "a": 1 }, + { "px": [224,232], "src": [224,128], "f": 0, "t": 540, "d": [1420], "a": 1 }, + { "px": [232,232], "src": [224,128], "f": 0, "t": 540, "d": [1421], "a": 1 }, + { "px": [240,232], "src": [224,128], "f": 0, "t": 540, "d": [1422], "a": 1 }, + { "px": [248,232], "src": [224,128], "f": 0, "t": 540, "d": [1423], "a": 1 }, + { "px": [256,232], "src": [224,128], "f": 0, "t": 540, "d": [1424], "a": 1 }, + { "px": [264,232], "src": [224,128], "f": 0, "t": 540, "d": [1425], "a": 1 }, + { "px": [272,232], "src": [224,128], "f": 0, "t": 540, "d": [1426], "a": 1 }, + { "px": [280,232], "src": [224,128], "f": 0, "t": 540, "d": [1427], "a": 1 }, + { "px": [288,232], "src": [224,128], "f": 0, "t": 540, "d": [1428], "a": 1 }, + { "px": [296,232], "src": [224,128], "f": 0, "t": 540, "d": [1429], "a": 1 }, + { "px": [304,232], "src": [224,128], "f": 0, "t": 540, "d": [1430], "a": 1 }, + { "px": [312,232], "src": [224,128], "f": 0, "t": 540, "d": [1431], "a": 1 }, + { "px": [320,232], "src": [224,128], "f": 0, "t": 540, "d": [1432], "a": 1 }, + { "px": [328,232], "src": [224,128], "f": 0, "t": 540, "d": [1433], "a": 1 }, + { "px": [336,232], "src": [224,128], "f": 0, "t": 540, "d": [1434], "a": 1 }, + { "px": [344,232], "src": [224,128], "f": 0, "t": 540, "d": [1435], "a": 1 }, + { "px": [352,232], "src": [224,128], "f": 0, "t": 540, "d": [1436], "a": 1 }, + { "px": [360,232], "src": [224,128], "f": 0, "t": 540, "d": [1437], "a": 1 }, + { "px": [368,232], "src": [224,128], "f": 0, "t": 540, "d": [1438], "a": 1 }, + { "px": [376,232], "src": [232,128], "f": 0, "t": 541, "d": [1439], "a": 1 }, + { "px": [0,240], "src": [216,128], "f": 0, "t": 539, "d": [1440], "a": 1 }, + { "px": [8,240], "src": [224,128], "f": 0, "t": 540, "d": [1441], "a": 1 }, + { "px": [16,240], "src": [224,128], "f": 0, "t": 540, "d": [1442], "a": 1 }, + { "px": [24,240], "src": [224,128], "f": 0, "t": 540, "d": [1443], "a": 1 }, + { "px": [32,240], "src": [224,128], "f": 0, "t": 540, "d": [1444], "a": 1 }, + { "px": [40,240], "src": [224,128], "f": 0, "t": 540, "d": [1445], "a": 1 }, + { "px": [48,240], "src": [224,128], "f": 0, "t": 540, "d": [1446], "a": 1 }, + { "px": [56,240], "src": [224,128], "f": 0, "t": 540, "d": [1447], "a": 1 }, + { "px": [64,240], "src": [224,128], "f": 0, "t": 540, "d": [1448], "a": 1 }, + { "px": [72,240], "src": [224,128], "f": 0, "t": 540, "d": [1449], "a": 1 }, + { "px": [80,240], "src": [224,128], "f": 0, "t": 540, "d": [1450], "a": 1 }, + { "px": [88,240], "src": [224,128], "f": 0, "t": 540, "d": [1451], "a": 1 }, + { "px": [96,240], "src": [224,128], "f": 0, "t": 540, "d": [1452], "a": 1 }, + { "px": [104,240], "src": [224,128], "f": 0, "t": 540, "d": [1453], "a": 1 }, + { "px": [112,240], "src": [224,128], "f": 0, "t": 540, "d": [1454], "a": 1 }, + { "px": [120,240], "src": [224,128], "f": 0, "t": 540, "d": [1455], "a": 1 }, + { "px": [128,240], "src": [224,128], "f": 0, "t": 540, "d": [1456], "a": 1 }, + { "px": [136,240], "src": [224,128], "f": 0, "t": 540, "d": [1457], "a": 1 }, + { "px": [144,240], "src": [224,128], "f": 0, "t": 540, "d": [1458], "a": 1 }, + { "px": [152,240], "src": [224,128], "f": 0, "t": 540, "d": [1459], "a": 1 }, + { "px": [160,240], "src": [224,128], "f": 0, "t": 540, "d": [1460], "a": 1 }, + { "px": [168,240], "src": [224,128], "f": 0, "t": 540, "d": [1461], "a": 1 }, + { "px": [176,240], "src": [224,128], "f": 0, "t": 540, "d": [1462], "a": 1 }, + { "px": [184,240], "src": [224,128], "f": 0, "t": 540, "d": [1463], "a": 1 }, + { "px": [192,240], "src": [224,128], "f": 0, "t": 540, "d": [1464], "a": 1 }, + { "px": [200,240], "src": [224,128], "f": 0, "t": 540, "d": [1465], "a": 1 }, + { "px": [208,240], "src": [224,128], "f": 0, "t": 540, "d": [1466], "a": 1 }, + { "px": [216,240], "src": [224,128], "f": 0, "t": 540, "d": [1467], "a": 1 }, + { "px": [224,240], "src": [224,128], "f": 0, "t": 540, "d": [1468], "a": 1 }, + { "px": [232,240], "src": [224,128], "f": 0, "t": 540, "d": [1469], "a": 1 }, + { "px": [240,240], "src": [224,128], "f": 0, "t": 540, "d": [1470], "a": 1 }, + { "px": [248,240], "src": [224,128], "f": 0, "t": 540, "d": [1471], "a": 1 }, + { "px": [256,240], "src": [224,128], "f": 0, "t": 540, "d": [1472], "a": 1 }, + { "px": [264,240], "src": [224,128], "f": 0, "t": 540, "d": [1473], "a": 1 }, + { "px": [272,240], "src": [224,128], "f": 0, "t": 540, "d": [1474], "a": 1 }, + { "px": [280,240], "src": [224,128], "f": 0, "t": 540, "d": [1475], "a": 1 }, + { "px": [288,240], "src": [224,128], "f": 0, "t": 540, "d": [1476], "a": 1 }, + { "px": [296,240], "src": [224,128], "f": 0, "t": 540, "d": [1477], "a": 1 }, + { "px": [304,240], "src": [224,128], "f": 0, "t": 540, "d": [1478], "a": 1 }, + { "px": [312,240], "src": [224,128], "f": 0, "t": 540, "d": [1479], "a": 1 }, + { "px": [320,240], "src": [224,128], "f": 0, "t": 540, "d": [1480], "a": 1 }, + { "px": [328,240], "src": [224,128], "f": 0, "t": 540, "d": [1481], "a": 1 }, + { "px": [336,240], "src": [224,128], "f": 0, "t": 540, "d": [1482], "a": 1 }, + { "px": [344,240], "src": [224,128], "f": 0, "t": 540, "d": [1483], "a": 1 }, + { "px": [352,240], "src": [224,128], "f": 0, "t": 540, "d": [1484], "a": 1 }, + { "px": [360,240], "src": [224,128], "f": 0, "t": 540, "d": [1485], "a": 1 }, + { "px": [368,240], "src": [224,128], "f": 0, "t": 540, "d": [1486], "a": 1 }, + { "px": [376,240], "src": [232,128], "f": 0, "t": 541, "d": [1487], "a": 1 }, + { "px": [0,248], "src": [216,136], "f": 0, "t": 571, "d": [1488], "a": 1 }, + { "px": [8,248], "src": [224,136], "f": 0, "t": 572, "d": [1489], "a": 1 }, + { "px": [16,248], "src": [224,136], "f": 0, "t": 572, "d": [1490], "a": 1 }, + { "px": [24,248], "src": [224,136], "f": 0, "t": 572, "d": [1491], "a": 1 }, + { "px": [32,248], "src": [224,136], "f": 0, "t": 572, "d": [1492], "a": 1 }, + { "px": [40,248], "src": [224,136], "f": 0, "t": 572, "d": [1493], "a": 1 }, + { "px": [48,248], "src": [224,136], "f": 0, "t": 572, "d": [1494], "a": 1 }, + { "px": [56,248], "src": [224,136], "f": 0, "t": 572, "d": [1495], "a": 1 }, + { "px": [64,248], "src": [224,136], "f": 0, "t": 572, "d": [1496], "a": 1 }, + { "px": [72,248], "src": [224,136], "f": 0, "t": 572, "d": [1497], "a": 1 }, + { "px": [80,248], "src": [224,136], "f": 0, "t": 572, "d": [1498], "a": 1 }, + { "px": [88,248], "src": [224,136], "f": 0, "t": 572, "d": [1499], "a": 1 }, + { "px": [96,248], "src": [224,136], "f": 0, "t": 572, "d": [1500], "a": 1 }, + { "px": [104,248], "src": [224,136], "f": 0, "t": 572, "d": [1501], "a": 1 }, + { "px": [112,248], "src": [224,136], "f": 0, "t": 572, "d": [1502], "a": 1 }, + { "px": [120,248], "src": [224,136], "f": 0, "t": 572, "d": [1503], "a": 1 }, + { "px": [128,248], "src": [224,136], "f": 0, "t": 572, "d": [1504], "a": 1 }, + { "px": [136,248], "src": [224,136], "f": 0, "t": 572, "d": [1505], "a": 1 }, + { "px": [144,248], "src": [224,136], "f": 0, "t": 572, "d": [1506], "a": 1 }, + { "px": [152,248], "src": [224,136], "f": 0, "t": 572, "d": [1507], "a": 1 }, + { "px": [160,248], "src": [224,136], "f": 0, "t": 572, "d": [1508], "a": 1 }, + { "px": [168,248], "src": [224,136], "f": 0, "t": 572, "d": [1509], "a": 1 }, + { "px": [176,248], "src": [224,136], "f": 0, "t": 572, "d": [1510], "a": 1 }, + { "px": [184,248], "src": [224,136], "f": 0, "t": 572, "d": [1511], "a": 1 }, + { "px": [192,248], "src": [224,136], "f": 0, "t": 572, "d": [1512], "a": 1 }, + { "px": [200,248], "src": [224,136], "f": 0, "t": 572, "d": [1513], "a": 1 }, + { "px": [208,248], "src": [224,136], "f": 0, "t": 572, "d": [1514], "a": 1 }, + { "px": [216,248], "src": [224,136], "f": 0, "t": 572, "d": [1515], "a": 1 }, + { "px": [224,248], "src": [224,136], "f": 0, "t": 572, "d": [1516], "a": 1 }, + { "px": [232,248], "src": [224,136], "f": 0, "t": 572, "d": [1517], "a": 1 }, + { "px": [240,248], "src": [224,136], "f": 0, "t": 572, "d": [1518], "a": 1 }, + { "px": [248,248], "src": [224,136], "f": 0, "t": 572, "d": [1519], "a": 1 }, + { "px": [256,248], "src": [224,136], "f": 0, "t": 572, "d": [1520], "a": 1 }, + { "px": [264,248], "src": [224,136], "f": 0, "t": 572, "d": [1521], "a": 1 }, + { "px": [272,248], "src": [224,136], "f": 0, "t": 572, "d": [1522], "a": 1 }, + { "px": [280,248], "src": [224,136], "f": 0, "t": 572, "d": [1523], "a": 1 }, + { "px": [288,248], "src": [224,136], "f": 0, "t": 572, "d": [1524], "a": 1 }, + { "px": [296,248], "src": [224,136], "f": 0, "t": 572, "d": [1525], "a": 1 }, + { "px": [304,248], "src": [224,136], "f": 0, "t": 572, "d": [1526], "a": 1 }, + { "px": [312,248], "src": [224,136], "f": 0, "t": 572, "d": [1527], "a": 1 }, + { "px": [320,248], "src": [224,136], "f": 0, "t": 572, "d": [1528], "a": 1 }, + { "px": [328,248], "src": [224,136], "f": 0, "t": 572, "d": [1529], "a": 1 }, + { "px": [336,248], "src": [224,136], "f": 0, "t": 572, "d": [1530], "a": 1 }, + { "px": [344,248], "src": [224,136], "f": 0, "t": 572, "d": [1531], "a": 1 }, + { "px": [352,248], "src": [224,136], "f": 0, "t": 572, "d": [1532], "a": 1 }, + { "px": [360,248], "src": [224,136], "f": 0, "t": 572, "d": [1533], "a": 1 }, + { "px": [368,248], "src": [224,136], "f": 0, "t": 572, "d": [1534], "a": 1 }, + { "px": [376,248], "src": [232,136], "f": 0, "t": 573, "d": [1535], "a": 1 } + ], + "entityInstances": [] + } + ], + "__neighbours": [] + } + ], + "worlds": [], + "dummyWorldIid": "4cf47921-fa90-11f0-ad09-8d23e32977ff" +} \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/tiny-music-editor.lua b/tiny-cli/src/main/resources/sfx/tiny-music-editor.lua new file mode 100644 index 00000000..97ec246a --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/tiny-music-editor.lua @@ -0,0 +1,603 @@ +local widgets = require("widgets") +local wire = require("wire") +local EditorBase = require("editor-base") +local music_templates = require("music-templates") +local icons = require("widgets.icons") + +local all_widgets = {} +local modals_by_name = {} +local speaker_widgets = {} +local overlay_widget = nil + +local save_state = nil +local save_button_ref = nil +local selector_dd_ref = nil +local play_button_ref = nil + +local state = { + seq = nil, + seq_index = 0, +} + +local playing = false +local play_handler = nil +local config_dirty = true + +local config = { + root = "C", + scale_name = "Major", + progression_name = "Classic", + lead_style = "Stepwise", + drum_pattern = "Rock", + chord_instrument = 0, + bass_instrument = 1, + lead_instrument = 2, + drum_instrument = 3, + chord_volume = 0.3, + bass_volume = 0.4, + lead_volume = 0.25, + drum_volume = 0.35, + bpm = 120, +} + +local themes = { + { name = "Adventurous", scale_name = "Major", progression_name = "Classic", lead_style = "Stepwise", drum_pattern = "Rock", bpm = 120 }, + { name = "Jumper", scale_name = "Penta Maj", progression_name = "Upbeat", lead_style = "Bouncy", drum_pattern = "Dance", bpm = 140 }, + { name = "Mystery", scale_name = "Dorian", progression_name = "Tense", lead_style = "Sparse", drum_pattern = "Sparse", bpm = 90 }, + { name = "Sadness", scale_name = "Minor", progression_name = "Melancholy", lead_style = "Stepwise", drum_pattern = "Halftime", bpm = 100 }, + { name = "Industrial", scale_name = "Mixolydian", progression_name = "Tense", lead_style = "Arpeggiated", drum_pattern = "Funky", bpm = 130 }, + { name = "Dreamy", scale_name = "Penta Min", progression_name = "Dreamy", lead_style = "Arpeggiated", drum_pattern = "Halftime", bpm = 80 }, + { name = "March", scale_name = "Major", progression_name = "Upbeat", lead_style = "Stepwise", drum_pattern = "March", bpm = 110 }, +} + +local function wrap_dropdown_overlay(dropdown) + local original_update = dropdown._update + dropdown._update = function(self) + local was_open = self.open + original_update(self) + if self.open and not was_open then + overlay_widget = self + elseif not self.open and was_open then + if overlay_widget == self then + overlay_widget = nil + end + end + end +end + +local function find_index(list, value) + for i, v in ipairs(list) do + if v == value then return i end + end + return 1 +end + +local function build_instrument_options() + local options = {} + for i = 0, 7 do + local inst = sfx.instrument(i) + local name = (inst and inst.name) or ("Instrument " .. i) + table.insert(options, "[" .. i .. "] " .. name) + end + return options +end + +local function populate_dropdown(dd, options, default_index) + if not dd then return end + dd.options = options + dd.selected = default_index or 1 + dd:_init() +end + +local function build_seq_label(index) + local seq = sfx.sequence(index) + local name = seq and seq.name + if name and name ~= "" then + return "[" .. index .. "] " .. name + end + return "Seq " .. index +end + +local function mark_config_dirty() + config_dirty = true +end + +local function _init_music_generator(widget_entities) + local generators = widget_entities["MusicGenerator"] + if not generators then return end + + local gen = nil + for g in all(generators) do + gen = g + break + end + if not gen then return end + + -- Find referenced widgets + local drum_pattern_dd = wire.find_widget(all_widgets, gen.fields.DrumPattern) + local drum_volume_fader = wire.find_widget(all_widgets, gen.fields.DrumVolume) + local theme_dd = wire.find_widget(all_widgets, gen.fields.MusicTheme) + local scale_dd = wire.find_widget(all_widgets, gen.fields.MusicScale) + local lead_inst_dd = wire.find_widget(all_widgets, gen.fields.LeadInstrument) + local lead_volume_fader = wire.find_widget(all_widgets, gen.fields.LeadVolume) + local bass_inst_dd = wire.find_widget(all_widgets, gen.fields.BassInstrument) + local bass_volume_fader = wire.find_widget(all_widgets, gen.fields.BassVolume) + local chord_volume_fader = wire.find_widget(all_widgets, gen.fields.RythmVolume) + local progression_dd = wire.find_widget(all_widgets, gen.fields.RythmChordProgression) + local chord_inst_dd = wire.find_widget(all_widgets, gen.fields.RythmInstrument) + local play_button = wire.find_widget(all_widgets, gen.fields.Play) + local master_volume_fader = wire.find_widget(all_widgets, gen.fields.Volume) + local selector_dd = wire.find_widget(all_widgets, gen.fields.Selector) + local export_button = wire.find_widget(all_widgets, gen.fields.Export) + + selector_dd_ref = selector_dd + play_button_ref = play_button + + -- Find unreferenced dropdown for Lead Style by position (21, 72) + local lead_style_dd = nil + local referenced_iids = {} + for _, field_name in ipairs({ + "DrumPattern", "DrumVolume", "MusicTheme", "MusicScale", + "LeadInstrument", "LeadVolume", "BassInstrument", "BassVolume", + "RythmVolume", "RythmChordProgression", "RythmInstrument", + "Play", "Volume", "Selector", "Export", + }) do + local ref = gen.fields[field_name] + if ref then + referenced_iids[ref.entityIid] = true + end + end + for w in all(all_widgets) do + if w.options and not referenced_iids[w.iid] + and w.x >= 18 and w.x <= 24 + and w.y >= 69 and w.y <= 75 then + lead_style_dd = w + break + end + end + + -- Build option lists + local theme_names = {} + for _, t in ipairs(themes) do + table.insert(theme_names, t.name) + end + + local inst_options = build_instrument_options() + + local seq_options = {} + for i = 0, 7 do + table.insert(seq_options, build_seq_label(i)) + end + + -- Populate dropdowns with options + populate_dropdown(theme_dd, theme_names, 1) + populate_dropdown(scale_dd, music_templates.scale_names, find_index(music_templates.scale_names, config.scale_name)) + populate_dropdown(progression_dd, music_templates.progression_names, find_index(music_templates.progression_names, config.progression_name)) + populate_dropdown(drum_pattern_dd, music_templates.drum_pattern_names, find_index(music_templates.drum_pattern_names, config.drum_pattern)) + populate_dropdown(lead_style_dd, music_templates.lead_styles, find_index(music_templates.lead_styles, config.lead_style)) + populate_dropdown(chord_inst_dd, inst_options, config.chord_instrument + 1) + populate_dropdown(bass_inst_dd, inst_options, config.bass_instrument + 1) + populate_dropdown(lead_inst_dd, inst_options, config.lead_instrument + 1) + populate_dropdown(selector_dd, seq_options, state.seq_index + 1) + + -- Set initial fader values + if chord_volume_fader then chord_volume_fader.value = config.chord_volume end + if bass_volume_fader then bass_volume_fader.value = config.bass_volume end + if lead_volume_fader then lead_volume_fader.value = config.lead_volume end + if drum_volume_fader then drum_volume_fader.value = config.drum_volume end + if master_volume_fader then master_volume_fader.value = 1.0 end + + -- Dropdown callbacks: update config on change + if scale_dd then + scale_dd.on_change = function(self) + config.scale_name = music_templates.scale_names[self.selected] + mark_config_dirty() + end + end + + if progression_dd then + progression_dd.on_change = function(self) + config.progression_name = music_templates.progression_names[self.selected] + mark_config_dirty() + end + end + + if drum_pattern_dd then + drum_pattern_dd.on_change = function(self) + config.drum_pattern = music_templates.drum_pattern_names[self.selected] + mark_config_dirty() + end + end + + if lead_style_dd then + lead_style_dd.on_change = function(self) + config.lead_style = music_templates.lead_styles[self.selected] + mark_config_dirty() + end + end + + if chord_inst_dd then + chord_inst_dd.on_change = function(self) + config.chord_instrument = self.selected - 1 + mark_config_dirty() + end + end + + if bass_inst_dd then + bass_inst_dd.on_change = function(self) + config.bass_instrument = self.selected - 1 + mark_config_dirty() + end + end + + if lead_inst_dd then + lead_inst_dd.on_change = function(self) + config.lead_instrument = self.selected - 1 + mark_config_dirty() + end + end + + -- Fader callbacks: update config on change + if chord_volume_fader then + chord_volume_fader.on_change = function(self) + config.chord_volume = self.value + mark_config_dirty() + end + end + + if bass_volume_fader then + bass_volume_fader.on_change = function(self) + config.bass_volume = self.value + mark_config_dirty() + end + end + + if lead_volume_fader then + lead_volume_fader.on_change = function(self) + config.lead_volume = self.value + mark_config_dirty() + end + end + + if drum_volume_fader then + drum_volume_fader.on_change = function(self) + config.drum_volume = self.value + mark_config_dirty() + end + end + + -- Master volume: apply multiplier to all track volumes + if master_volume_fader then + master_volume_fader.on_change = function(self) + local vol = self.value + local base_volumes = { config.chord_volume, config.bass_volume, config.lead_volume, config.drum_volume } + for i = 0, 3 do + local track = state.seq.track(i) + if track then + track.volume = base_volumes[i + 1] * vol + end + end + -- Master volume changes need audio re-render but not note regeneration + state.seq.invalidate() + end + end + + -- Theme dropdown: updates ALL config fields + syncs all other dropdowns + if theme_dd then + theme_dd.on_change = function(self) + local theme = themes[self.selected] + if not theme then return end + + config.scale_name = theme.scale_name + config.progression_name = theme.progression_name + config.lead_style = theme.lead_style + config.drum_pattern = theme.drum_pattern + config.bpm = theme.bpm + + if scale_dd then + scale_dd:set_selected(find_index(music_templates.scale_names, theme.scale_name)) + end + if progression_dd then + progression_dd:set_selected(find_index(music_templates.progression_names, theme.progression_name)) + end + if drum_pattern_dd then + drum_pattern_dd:set_selected(find_index(music_templates.drum_pattern_names, theme.drum_pattern)) + end + if lead_style_dd then + lead_style_dd:set_selected(find_index(music_templates.lead_styles, theme.lead_style)) + end + + mark_config_dirty() + end + end + + -- Play button: toggle generate + play / stop + if play_button then + play_button.on_change = function() + if playing then + if play_handler then + play_handler.stop() + end + playing = false + play_handler = nil + play_button.overlay = icons.Play + for s in all(speaker_widgets) do + s.playing = false + end + else + if config_dirty then + config.seed = math.random(1, 2147483647) + state.seq.generate(config) + config_dirty = false + end + play_handler = state.seq.play() + playing = true + play_button.overlay = icons.Stop + for s in all(speaker_widgets) do + s.playing = true + end + end + end + end + + -- Export button: export current sequence as wav + if export_button then + export_button.on_change = function() + if config_dirty then + config.seed = math.random(1, 2147483647) + state.seq.generate(config) + config_dirty = false + end + state.seq.export() + end + end + + -- Selector: switch active sequence + if selector_dd then + selector_dd.on_change = function(self) + if playing and play_handler then + play_handler.stop() + playing = false + play_handler = nil + if play_button then + play_button.overlay = icons.Play + end + for s in all(speaker_widgets) do + s.playing = false + end + end + state.seq_index = self.selected - 1 + state.seq = sfx.sequence(state.seq_index) + + -- Load config from sequence if available + local saved_config = state.seq.config + if saved_config then + config.root = saved_config.root or config.root + config.scale_name = saved_config.scale_name or config.scale_name + config.progression_name = saved_config.progression_name or config.progression_name + config.lead_style = saved_config.lead_style or config.lead_style + config.drum_pattern = saved_config.drum_pattern or config.drum_pattern + config.chord_instrument = saved_config.chord_instrument or config.chord_instrument + config.bass_instrument = saved_config.bass_instrument or config.bass_instrument + config.lead_instrument = saved_config.lead_instrument or config.lead_instrument + config.drum_instrument = saved_config.drum_instrument or config.drum_instrument + config.chord_volume = saved_config.chord_volume or config.chord_volume + config.bass_volume = saved_config.bass_volume or config.bass_volume + config.lead_volume = saved_config.lead_volume or config.lead_volume + config.drum_volume = saved_config.drum_volume or config.drum_volume + config.bpm = saved_config.bpm or config.bpm + config.seed = saved_config.seed or config.seed + + -- Update UI controls to match loaded config + if scale_dd then + scale_dd:set_selected(find_index(music_templates.scale_names, config.scale_name)) + end + if progression_dd then + progression_dd:set_selected(find_index(music_templates.progression_names, config.progression_name)) + end + if drum_pattern_dd then + drum_pattern_dd:set_selected(find_index(music_templates.drum_pattern_names, config.drum_pattern)) + end + if lead_style_dd then + lead_style_dd:set_selected(find_index(music_templates.lead_styles, config.lead_style)) + end + if chord_inst_dd then + chord_inst_dd:set_selected(config.chord_instrument + 1) + end + if bass_inst_dd then + bass_inst_dd:set_selected(config.bass_instrument + 1) + end + if lead_inst_dd then + lead_inst_dd:set_selected(config.lead_instrument + 1) + end + if chord_volume_fader then + chord_volume_fader.value = config.chord_volume + end + if bass_volume_fader then + bass_volume_fader.value = config.bass_volume + end + if lead_volume_fader then + lead_volume_fader.value = config.lead_volume + end + if drum_volume_fader then + drum_volume_fader.value = config.drum_volume + end + end + + config_dirty = true + end + end + + -- Load config for initial sequence if available + local saved_config = state.seq.config + if saved_config then + config.root = saved_config.root or config.root + config.scale_name = saved_config.scale_name or config.scale_name + config.progression_name = saved_config.progression_name or config.progression_name + config.lead_style = saved_config.lead_style or config.lead_style + config.drum_pattern = saved_config.drum_pattern or config.drum_pattern + config.chord_instrument = saved_config.chord_instrument or config.chord_instrument + config.bass_instrument = saved_config.bass_instrument or config.bass_instrument + config.lead_instrument = saved_config.lead_instrument or config.lead_instrument + config.drum_instrument = saved_config.drum_instrument or config.drum_instrument + config.chord_volume = saved_config.chord_volume or config.chord_volume + config.bass_volume = saved_config.bass_volume or config.bass_volume + config.lead_volume = saved_config.lead_volume or config.lead_volume + config.drum_volume = saved_config.drum_volume or config.drum_volume + config.bpm = saved_config.bpm or config.bpm + config.seed = saved_config.seed or config.seed + + if scale_dd then + scale_dd:set_selected(find_index(music_templates.scale_names, config.scale_name)) + end + if progression_dd then + progression_dd:set_selected(find_index(music_templates.progression_names, config.progression_name)) + end + if drum_pattern_dd then + drum_pattern_dd:set_selected(find_index(music_templates.drum_pattern_names, config.drum_pattern)) + end + if lead_style_dd then + lead_style_dd:set_selected(find_index(music_templates.lead_styles, config.lead_style)) + end + if chord_inst_dd then + chord_inst_dd:set_selected(config.chord_instrument + 1) + end + if bass_inst_dd then + bass_inst_dd:set_selected(config.bass_instrument + 1) + end + if lead_inst_dd then + lead_inst_dd:set_selected(config.lead_instrument + 1) + end + if chord_volume_fader then + chord_volume_fader.value = config.chord_volume + end + if bass_volume_fader then + bass_volume_fader.value = config.bass_volume + end + if lead_volume_fader then + lead_volume_fader.value = config.lead_volume + end + if drum_volume_fader then + drum_volume_fader.value = config.drum_volume + end + + config_dirty = false + end +end + +function _init_fader(entities) + for f in all(entities["Fader"]) do + local fader = widgets:create_fader(f) + table.insert(all_widgets, fader) + end +end + +function _init_counter(entities) + for c in all(entities["Counter"]) do + local counter = widgets:create_counter(c) + table.insert(all_widgets, counter) + end +end + +function _init() + all_widgets = {} + modals_by_name = {} + speaker_widgets = {} + overlay_widget = nil + save_state = nil + save_button_ref = nil + selector_dd_ref = nil + play_button_ref = nil + playing = false + play_handler = nil + config_dirty = true + + map.level("MusicEditor") + + state.seq_index = 0 + state.seq = sfx.sequence(0) + + -- Panels first (drawn behind everything) + local panel_entities = map.entities("Panels") + EditorBase.init_panels(panel_entities, all_widgets) + + -- Then all interactive widgets + local widget_entities = map.entities("Widgets") + + local buttons_by_action = EditorBase.init_text_buttons(widget_entities, all_widgets) + save_button_ref = buttons_by_action["Save"] + + EditorBase.init_speakers(widget_entities, all_widgets, speaker_widgets) + + + modals_by_name = EditorBase.init_buttons(widget_entities, all_widgets, { + on_open = function() + return state.seq.name or "" + end, + on_name_validate = function(value) + if value and state.seq then + state.seq.name = value + if selector_dd_ref then + selector_dd_ref.options[state.seq_index + 1] = build_seq_label(state.seq_index) + selector_dd_ref:_init() + end + end + end, + }) + + -- Create all dropdowns and wrap with overlay + for d in all(widget_entities["Dropdown"]) do + local dropdown = widgets:create_dropdown(d) + wrap_dropdown_overlay(dropdown) + table.insert(all_widgets, dropdown) + end + + _init_fader(widget_entities) + _init_counter(widget_entities) + + -- Wire music generator widgets BEFORE save reminder + _init_music_generator(widget_entities) + + save_state = EditorBase.init_save_reminder(all_widgets, save_button_ref, modals_by_name) +end + +function _update() + -- Auto-stop: detect when playback finishes + if playing and play_handler then + if not play_handler.playing then + playing = false + play_handler = nil + if play_button_ref then + play_button_ref.overlay = icons.Play + end + for s in all(speaker_widgets) do + s.playing = false + end + end + end + + EditorBase.update(modals_by_name, function() + if overlay_widget then + overlay_widget:_update() + else + for w in all(all_widgets) do + w:_update() + end + end + end) + + EditorBase.update_save_reminder(save_button_ref, save_state) +end + +function _draw() + EditorBase.draw(function() + for w in all(all_widgets) do + if w ~= overlay_widget then + w:_draw() + end + end + if overlay_widget then + overlay_widget:_draw() + end + end, modals_by_name) +end diff --git a/tiny-cli/src/main/resources/sfx/tiny-sfx-editor.lua b/tiny-cli/src/main/resources/sfx/tiny-sfx-editor.lua new file mode 100644 index 00000000..98e750d7 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/tiny-sfx-editor.lua @@ -0,0 +1,642 @@ +local widgets = require("widgets") +local wire = require("wire") +local sfx_templates = require("sfx-templates") +local EditorBase = require("editor-base") +local icons = require("widgets.icons") + +local all_widgets = {} +local modals_by_name = {} +local dropdown_widget = nil +local speaker_widgets = {} +local sfx_editor_ref = nil +local octave_counter_ref = nil +local overlay_widget = nil + +-- colors +local yellow = 9 +local orange = 8 +local light_green = 6 +local dark_green = 13 +local pink = 7 +local purple = 4 + + +local state = { + sfx = nil, +} + +function roundToHalf(num) + local rounded_step = math.floor(num * 2) + local final_rounded = rounded_step / 2 + return final_rounded +end + +-- VelocityEditor widget: volume/velocity curve editor per beat +local VelocityEditor = { + values = {}, + current_beat = nil, + playing = false, +} + +VelocityEditor._update = function(self) + local p = ctrl.touch() + if ctrl.touching(0) and inside_widget(self, p.x, p.y) then + local local_x = p.x - self.x + local local_y = p.y - self.y + local beat = roundToHalf((local_x) / 16.0) + + if local_y > self.height - 8 then + self:set_value(beat, 0) + else + local volume = math.clamp(0, 1.0 - (local_y / (self.height - 8)), 1.0) + self:set_value(beat, volume) + end + end +end + +VelocityEditor.set_value = function(self, beat, volume) + if self.on_change then + self:on_change({ beat = beat, volume = volume }) + end +end + +VelocityEditor._draw = function(self) + -- background + shape.rectf(self.x, self.y, self.width, self.height, 2) + shape.rect(self.x, self.y, self.width, self.height, 1) + + local low = self.y + self.height + + -- Pass 1: Group outlines at tip level + for note in all(self.values) do + if note.duration > 0.5 then + local volume_height = note.volume * (self.height - 8) + local tip_y = (volume_height > 0) and (low - volume_height - 2) or (low - 2) + local bg_x = self.x + note.beat * 16 + local bg_width = note.duration * 16 + shape.rect(bg_x, tip_y, bg_width, 2, 3) + end + end + + -- Pass 2: Fader bars per half-beat + for note in all(self.values) do + local num_half_beats = math.floor(note.duration / 0.5) + local is_active = self.playing and self.current_beat and note.beat <= self.current_beat and self.current_beat < note.beat + note.duration + + for i = 0, num_half_beats - 1 do + local cell_x = self.x + (note.beat + i * 0.5) * 16 + local volume_height = note.volume * (self.height - 8) + + local fill_color = is_active and light_green or pink + local tip_color = is_active and light_green or purple + + if volume_height > 0 then + shape.rectf(cell_x + 1, low - volume_height, 6, volume_height, fill_color) + end + + local tip_y = (volume_height > 0) and (low - volume_height - 2) or (low - 2) + shape.rectf(cell_x + 1, tip_y, 6, 2, tip_color) + end + end +end + +-- Player widget: playback controller (playhead, play/stop, BPM timing) +local Player = { + beat = 0, + step_x = 16, + bpm = 0, + time = 0, + play = false, +} + +Player._update = function(self) + if ctrl.pressed(keys.space) then + self:playSfx() + end + + if self.play then + self.time = self.time + tiny.dt + self.beat = self.time * (state.sfx.bpm / 60) + + local finished = false + if self.handler and not self.handler.playing then + finished = true + elseif self.beat >= 32 then + finished = true + end + + if finished then + self.play = false + self.beat = 0 + self.time = 0 + if self.playButton then + self.playButton.overlay = icons.Play + end + end + end + + self.beat = math.clamp(0, self.beat, 15.5) + + self:set_value(self.beat) +end + +Player._draw = function(self) end + +Player.playSfx = function(self) + self.beat = 0 + self.play = not self.play + self.time = 0 + + if self.handler then + self.handler.stop() + end + + if self.play then + self.handler = state.sfx.play() + end +end + +Player.set_value = function(self, value) + if self.on_change then + self:on_change(value) + end +end + +-- SfxEditor widget: main note editing grid +local SfxEditor = { + octave = 2, + note = "C2", + current_beat = nil, + playing = false, + values = {}, + y_factor = 1, + note_y_factor = 1, +} + +SfxEditor._init = function(self) + self.y_factor = 192 / self.height + self.note_y_factor = self.height / 24 +end + +SfxEditor._update = function(self) + local p = ctrl.touch() + + if ctrl.touching(0) ~= nil and self.current_edit ~= nil and ctrl.pressing(keys.shift) then + local local_x = p.x - self.x + local added_duration = math.max(0, (roundToHalf((local_x + 4) / 16) - self.current_edit.beat)) + local duration = math.max(0.5, added_duration) + self.current_edit.duration = duration + elseif inside_widget(self, p.x, p.y) and ctrl.touching(0) ~= nil and self.current_edit ~= nil then + local local_x = p.x - self.x + + local value = { + beat = self.current_edit.beat, + note = self.note, + duration = 0.5, + unique = true + } + + local current_beat = roundToHalf((local_x - 0.5) / 16.0) + + if self.current_edit.beat ~= current_beat then + self:set_value(value) + + self.current_edit = { + beat = roundToHalf((local_x) / 16.0), + note = self.note, + duration = 0.5 + } + end + elseif inside_widget(self, p.x, p.y) and ctrl.touched(0) ~= nil and self.current_edit == nil then + local local_x = p.x - self.x + + self.current_edit = { + beat = roundToHalf((local_x) / 16.0), + note = self.note, + duration = 0.5 + } + elseif ctrl.touched(0) == nil and self.current_edit ~= nil then + local value = { + duration = self.current_edit.duration, + beat = self.current_edit.beat, + note = self.current_edit.note, + unique = true + } + + self:set_value(value) + + self.current_edit = nil + elseif inside_widget(self, p.x, p.y) and ctrl.touching(1) ~= nil then + local local_x = p.x - self.x + + local value = { + beat = roundToHalf((local_x) / 16.0), + note = self.note, + } + + if self.remove_value then + self:remove_value(value) + end + end + + local local_y = math.clamp(0, p.y - self.y, self.height) + + local prev = spr.sheet(2) + local color = spr.pget(164, (64 + local_y * self.y_factor)) + spr.sheet(prev) + self.yy = color + + local color_to_note = { + [1] = "C", + [2] = "Cs", + [5] = "D", + [10] = "Ds", + [12] = "E", + [4] = "F", + [6] = "Fs", + [9] = "G", + [3] = "Gs", + [7] = "A", + [8] = "As", + [13] = "B" + } + + local note = color_to_note[color] + local octave = (local_y <= self.height * 0.5 and 1) or 0 + if note then + self.note = note .. (self.octave + octave) + end +end + +SfxEditor.set_value = function(self, value) + if self.on_change then + self:on_change(value) + end +end + +SfxEditor._draw = function(self) + -- background + shape.rectf(self.x, self.y, self.width, self.height, 2) + shape.rect(self.x, self.y, self.width, self.height, 1) + + local bottom = self.y + self.height + + -- Pass 1: Group outlines at tip level + for note in all(self.values) do + if note.duration > 0.5 then + local note_y = self.y + (23 - (note.notei - self.octave * 12)) * 8 + local tip_y = math.max(self.y, note_y - 2) + local bg_x = self.x + note.beat * 16 + local bg_width = note.duration * 16 + shape.rect(bg_x, tip_y, bg_width, 2, 3) + end + end + + -- Pass 2: Fader bars from bottom up to pitch level + for note in all(self.values) do + -- octave_offset should be equal to 0 or 1 + local octave_offset = (note.octave - self.octave) + if(octave_offset > 1) then + console.log("octave_offset > 1 = ", octave_offset) + end + -- notei is the index of the note in an octave (0 < notei < 12 + local notei = note.notei - note.octave * 12 + if notei > 12 then + console.log("notei is an invalid note index as > 12", notei) + end + + local note_height = (notei + octave_offset * 12) * self.note_y_factor + + local note_y = self.y + self.height - note_height + local fader_height = bottom - note_y + local num_half_beats = math.floor(note.duration / 0.5) + local is_active = self.playing and self.current_beat and note.beat <= self.current_beat and self.current_beat < note.beat + note.duration + + for i = 0, num_half_beats - 1 do + local cell_x = self.x + (note.beat + i * 0.5) * 16 + + local fill_color = is_active and light_green or yellow + local tip_color = is_active and light_green or orange + + if fader_height > 0 then + shape.rectf(cell_x + 1, note_y, 6, fader_height, fill_color) + end + + local tip_y = math.max(self.y, note_y - 2) + shape.rectf(cell_x + 1, tip_y, 6, 2, tip_color) + end + end +end + + +local function shift_notes(old_octave, new_octave) + local delta = new_octave - old_octave + local notes = state.sfx.notes + local saved = {} + for note in all(notes) do + table.insert(saved, { + beat = note.beat, + note = note.note, + octave = note.octave, + duration = note.duration, + volume = note.volume, + }) + end + for _, note in ipairs(saved) do + state.sfx.remove_note({ beat = note.beat, note = note.note }) + end + for _, note in ipairs(saved) do + local pitch_class = string.sub(note.note, 1, #note.note - 1) + local new_note_name = pitch_class .. (note.octave + delta) + state.sfx.set_note({ + beat = note.beat, + note = new_note_name, + duration = note.duration, + }) + state.sfx.set_volume({ beat = note.beat, volume = note.volume }) + end +end + +local function wrap_dropdown_overlay(dropdown) + local original_update = dropdown._update + dropdown._update = function(self) + local was_open = self.open + original_update(self) + if self.open and not was_open then + overlay_widget = self + elseif not self.open and was_open then + if overlay_widget == self then + overlay_widget = nil + end + end + end +end + +function _init_knob(entities) + for k in all(entities["Knob"]) do + local knob = widgets:create_knob(k) + table.insert(all_widgets, knob) + end +end + +function _init_fader(entities) + for f in all(entities["Fader"]) do + local fader = widgets:create_fader(f) + table.insert(all_widgets, fader) + end +end + +function _init_counter_entities(entities) + for c in all(entities["Counter"]) do + local counter = widgets:create_counter(c) + table.insert(all_widgets, counter) + end +end + +function _init_velocity_editor(entities) + for volume in all(entities["VelocityEditor"]) do + local widget = new(VelocityEditor, volume) + wire.sync(state, "sfx.notes", widget, "values") + widget.on_change = function(self, value) + state.sfx.set_volume(value) + end + table.insert(all_widgets, widget) + end +end + +function _init_sfx_editor(entities) + for editor in all(entities["SfxEditor"]) do + local widget = new(SfxEditor, editor) + widget:_init() + local bpm = wire.find_widget(all_widgets, widget.fields.BPM) + + local transform = { + to_widget = function(to, from, value) + return (value - 60) / 520 + end, + + from_widget = function(to, from, value) + return 60 + value * 520 + end + } + wire.bind(state, "sfx.bpm", bpm, "value", transform) + + local volume_knob = wire.find_widget(all_widgets, widget.fields.Volume) + if volume_knob then + wire.bind(state, "sfx.volume", volume_knob, "value") + end + + local octave_counter = wire.find_widget(all_widgets, widget.fields.Octave) + if octave_counter then + octave_counter.min = 0 + octave_counter.max = 7 + octave_counter.value = widget.octave + octave_counter.on_change = function(self) + local old_octave = widget.octave + local new_octave = self.value + if old_octave ~= new_octave then + shift_notes(old_octave, new_octave) + widget.octave = new_octave + end + end + octave_counter_ref = octave_counter + end + + wire.sync(state, "sfx.notes", widget, "values") + widget.on_change = function(self, value) + state.sfx.set_note(value) + end + widget.remove_value = function(self, value) + state.sfx.remove_note(value) + end + + -- Instrument dropdown + local instrument_dropdown = wire.find_widget(all_widgets, widget.fields.Instrument) + if #instrument_dropdown.options == 0 then + for i = 0, 7 do + local inst = sfx.instrument(i) + local name = (inst and inst.name) or ("Instrument " .. i) + table.insert(instrument_dropdown.options, "[" .. i .. "] " .. name) + end + instrument_dropdown:_init() + end + + wrap_dropdown_overlay(instrument_dropdown) + + wire.sync(state, "sfx.instrument", instrument_dropdown, "selected", function(_, _, value) + return value + 1 + end) + instrument_dropdown.on_change = function(self) + state.sfx.set_instrument(self.selected - 1) + end + + table.insert(all_widgets, widget) + sfx_editor_ref = widget + return widget + end +end + +function _init_player(entities) + for p in all(entities["Player"]) do + local widget = new(Player, p) + local sfxEditor = wire.find_widget(all_widgets, widget.fields.SfxEditor) + widget.editor = sfxEditor + local velocityEditor = wire.find_widget(all_widgets, widget.fields.VelocityEditor) + local bpm = wire.find_widget(all_widgets, widget.fields.BPM) + + wire.sync(widget, "beat", sfxEditor, "current_beat") + wire.sync(widget, "beat", velocityEditor, "current_beat") + wire.sync(widget, "play", sfxEditor, "playing") + wire.sync(widget, "play", velocityEditor, "playing") + wire.sync(widget, "bpm", bpm, "value") + + for _, s in ipairs(speaker_widgets) do + wire.sync(widget, "play", s, "playing") + end + + if widget.fields.SfxSelector then + local sfxSelector = wire.find_widget(all_widgets, widget.fields.SfxSelector) + widget.sfxSelector = sfxSelector + end + + local playButton = wire.find_widget(all_widgets, widget.fields.PlayButton) + widget.playButton = playButton + playButton.on_change = function() + widget:playSfx() + if widget.play then + playButton.overlay = icons.Stop + else + playButton.overlay = icons.Play + end + end + + local saveButton = wire.find_widget(all_widgets, widget.fields.SaveButton) + saveButton.on_change = function() sfx.save() end + + local exportButton = wire.find_widget(all_widgets, widget.fields.ExportButton) + exportButton.on_change = function() state.sfx.export() end + + table.insert(all_widgets, widget) + end +end + +function _init() + all_widgets = {} + modals_by_name = {} + dropdown_widget = nil + speaker_widgets = {} + sfx_editor_ref = nil + octave_counter_ref = nil + overlay_widget = nil + + map.level("SfxEditor") + + state.sfx = sfx.sfx(0) + + -- Panels first (drawn behind everything) + local panel_entities = map.entities("Panels") + EditorBase.init_panels(panel_entities, all_widgets) + + -- Then all interactive widgets + local widget_entities = map.entities("Widgets") + EditorBase.init_text_buttons(widget_entities, all_widgets) + EditorBase.init_speakers(widget_entities, all_widgets, speaker_widgets) + dropdown_widget = EditorBase.init_entity_dropdown(widget_entities, all_widgets, { + count = 32, + fetch = function(i) return sfx.sfx(i) end, + label = "SFX", + min_width = 150, + on_select = function(index) + state.sfx = sfx.sfx(index) + local octave = 2 + local notes = state.sfx.notes + if #notes > 0 then + local min_octave = 7 + for note in all(notes) do + if note.octave < min_octave then + min_octave = note.octave + end + end + octave = min_octave + end + if sfx_editor_ref then + sfx_editor_ref.octave = octave + end + if octave_counter_ref then + octave_counter_ref.value = octave + end + end, + }) + + if dropdown_widget then + wrap_dropdown_overlay(dropdown_widget) + end + + modals_by_name = EditorBase.init_buttons(widget_entities, all_widgets, { + modal_sizes = { + NameModal = { x = 96, y = 64, width = 192, height = 128 }, + RandomSfxModal = { x = 72, y = 68, width = 240, height = 120 }, + }, + on_open = function() return state.sfx.name end, + on_name_validate = function(value) + if value and state.sfx then + state.sfx.name = value + EditorBase.update_dropdown_name(dropdown_widget, value) + end + end, + }) + + -- Wire RandomSfxModal + local random_modal = modals_by_name["RandomSfxModal"] + if random_modal then + if random_modal.dropdown then + random_modal.dropdown.options = sfx_templates.list + random_modal.dropdown:_init() + end + + random_modal.on_validate = function(self, value, dropdown_index) + if dropdown_index and state.sfx then + local template_name = sfx_templates.list[dropdown_index] + if template_name then + local lowest_octave = sfx_templates.generate(state.sfx, template_name) + if lowest_octave and sfx_editor_ref and octave_counter_ref then + sfx_editor_ref.octave = lowest_octave + octave_counter_ref.value = math.clamp(octave_counter_ref.min, lowest_octave, octave_counter_ref.max) + end + end + end + end + end + + _init_knob(widget_entities) + _init_fader(widget_entities) + _init_counter_entities(widget_entities) + _init_velocity_editor(widget_entities) + _init_sfx_editor(widget_entities) + _init_player(widget_entities) +end + +function _update() + EditorBase.update(modals_by_name, function() + if overlay_widget then + overlay_widget:_update() + else + for w in all(all_widgets) do + w:_update() + end + end + end) +end + +function _draw() + EditorBase.draw(function() + for w in all(all_widgets) do + if w ~= overlay_widget then + w:_draw() + end + end + if overlay_widget then + overlay_widget:_draw() + end + end, modals_by_name) +end diff --git a/tiny-cli/src/main/resources/sfx/widgets.lua b/tiny-cli/src/main/resources/sfx/widgets.lua index aadbcd9c..3b72b9a9 100644 --- a/tiny-cli/src/main/resources/sfx/widgets.lua +++ b/tiny-cli/src/main/resources/sfx/widgets.lua @@ -1,41 +1,12 @@ -local on_update = function(self, listener) - table.insert(self.listeners, listener) -end - -local fire_on_update = function(self, value) - for l in all(self.listeners) do - l(self, value) - end -end - -local set_value = function(self, value) - self.value = value - if (self.on_change) then - self:on_change() - end - self:fire_on_update(value) -end - - - - - +local utils = require("widgets.utils") +local icons = require("widgets.icons") local factory = { } -function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +-- Expose inside_widget as global for widgets that depend on it (e.g., ModeSwitch) +inside_widget = utils.inside_widget @@ -61,22 +32,19 @@ end -local ModeSwitch = require("widgets.ModeSwitch") local Envelop = require("widgets.Envelop") local Knob = require("widgets.Knob") local Checkbox = require("widgets.Checkbox") local Fader = require("widgets.Fader") -local MenuItemModule = require("widgets.MenuItem") -local MenuItem = MenuItemModule.MenuItem -local menuItems = MenuItemModule.menuItems local Keyboard = require("widgets.Keyboard") -local Help = require("widgets.Help") local Button = require("widgets.Button") - -factory.create_mode_switch_component = function(self, value) - local result = new(ModeSwitch, value) - return result -end +local Dropdown = require("widgets.Dropdown") +local Modal = require("widgets.Modal") +local TextInput = require("widgets.TextInput") +local Panel = require("widgets.Panel") +local TextButton = require("widgets.TextButton") +local Speaker = require("widgets.Speaker") +local Counter = require("widgets.Counter") factory.create_envelop = function(self, data) local result = new(Envelop, data) @@ -107,24 +75,30 @@ factory.create_fader = function(self, value) local result = new(Fader, value) result.help = result.fields.Help result.label = result.fields.Label - result.hitbox = { - x = result.x, - y = result.y, - width = result.width, - height = result.height + 4 - } return result end factory.create_button = function(self, value) local result = new(Button, value) result.help = result.fields.Help + if result.fields.IconName then + result.overlay = icons[result.fields.IconName] + end + return result +end + +factory.create_dropdown = function(self, value) + local result = new(Dropdown, value) + result.options = result.fields.Options or {} + result.help = result.fields.Help + result:_init() return result end -factory.create_help = function(self, data) - local help = new(Help, data) - return help +factory.create_modal = function(self, data) + local result = new(Modal, data) + result:_init(self) + return result end factory.create_keyboard = function(self, data) @@ -132,35 +106,45 @@ factory.create_keyboard = function(self, data) return keyboard end -factory.create_menu_item = function(self, data) - local menu = new(MenuItem, data) - - local item = data.fields.Item - if item == "Wave" then - menu.spr = 14 - menu.hold = true - elseif item == "Fx" then - menu.spr = 15 - menu.hold = true - elseif item == "Music" then - menu.spr = 16 - menu.hold = true - elseif item == "Save" then - menu.spr = 17 - elseif item == "Prev" then - menu.spr = 21 - elseif item == "Next" then - menu.spr = 22 - elseif item == "NewFile" then - menu.spr = 13 +factory.create_text_input = function(self, data) + local result = new(TextInput, data) + result.help = result.fields.Help + result.label = result.fields.Label + result:_init() + return result +end + +factory.create_panel = function(self, value) + local result = new(Panel, value) + result.label = result.fields.Label + result.variant = utils.variant_mapping[result.fields.Variant] or 0 + return result +end + +factory.create_text_button = function(self, value) + local result = new(TextButton, value) + result.label = result.fields.Label or "" + result.is_active = result.fields.IsActive or false + result.variant = utils.variant_mapping[result.fields.Variant] or 0 + if result.fields.TinyExit then + result.on_change = function(self) + tiny.exit(self.fields.TinyExit) + end end - menu.item = item - menu.help = data.fields.Help + return result +end - table.insert(menuItems, menu) - return menu +factory.create_speaker = function(self, value) + local result = new(Speaker, value) + return result end +factory.create_counter = function(self, data) + local result = new(Counter, data) + return result +end + + factory._draw = function(self) for w in all(self.widgets) do w:_draw() diff --git a/tiny-cli/src/main/resources/sfx/widgets/Button.lua b/tiny-cli/src/main/resources/sfx/widgets/Button.lua index 4c016efe..f7af3261 100644 --- a/tiny-cli/src/main/resources/sfx/widgets/Button.lua +++ b/tiny-cli/src/main/resources/sfx/widgets/Button.lua @@ -1,30 +1,11 @@ -local on_update = function(self, listener) - table.insert(self.listeners, listener) -end - -local fire_on_update = function(self, value) - for l in all(self.listeners) do - l(self, value) - end -end - -local function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget local Button = { x = 0, y = 0, - width = 16, - height = 16, + width = 13, + height = 13, enabled = true, grouped = true, status = 0, -- 0 : idle ; 1 : over ; 2 : active @@ -32,8 +13,8 @@ local Button = { on_active_button = function(current, prec) end, listeners = {}, - on_update = on_update, - fire_on_update = fire_on_update, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, } Button._update = function(self) @@ -58,16 +39,20 @@ Button._update = function(self) end Button._draw = function(self) - local background = 0 + local prev = spr.sheet(2) + + local sy = 56 if self.status > 0 then - background = 16 + sy = 72 end - spr.sdraw(self.x, self.y, 80 + background, 0, self.width, self.height) + spr.sdraw(self.x, self.y, 0, sy, self.width, self.height) if self.overlay ~= nil then spr.sdraw(self.x, self.y, self.overlay.x, self.overlay.y, self.width, self.height) end + + spr.sheet(prev) end return Button \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/widgets/Checkbox.lua b/tiny-cli/src/main/resources/sfx/widgets/Checkbox.lua index 08856477..5c004409 100644 --- a/tiny-cli/src/main/resources/sfx/widgets/Checkbox.lua +++ b/tiny-cli/src/main/resources/sfx/widgets/Checkbox.lua @@ -1,14 +1,5 @@ -local function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget local Checkbox = { label = "", diff --git a/tiny-cli/src/main/resources/sfx/widgets/Counter.lua b/tiny-cli/src/main/resources/sfx/widgets/Counter.lua new file mode 100644 index 00000000..bd3a39a3 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/Counter.lua @@ -0,0 +1,61 @@ +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget + +local Counter = { + x = 0, + y = 0, + width = 40, + height = 16, + value = 2, + min = 0, + max = 7, + label = "", + status = 0, + listeners = {}, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, +} + +Counter._update = function(self) + local pos = ctrl.touched(0) + if pos ~= nil and inside_widget(self, pos.x, pos.y) then + local local_x = pos.x - self.x + if local_x < 12 then + self:set_value(self.value - 1) + elseif local_x > self.width - 12 then + self:set_value(self.value + 1) + end + end +end + +Counter.set_value = function(self, value) + local clamped = math.clamp(self.min, value, self.max) + if clamped ~= self.value then + self.value = clamped + if self.on_change then + self:on_change() + end + self:fire_on_update(clamped) + end +end + +Counter._draw = function(self) + shape.rectf(self.x, self.y, self.width, self.height, 2) + shape.rect(self.x, self.y, self.width, self.height, 1) + + text.font("monogram") + + local left_color = (self.value > self.min) and 1 or 3 + text.print("<", self.x + 3, self.y + 2, left_color) + + local right_color = (self.value < self.max) and 1 or 3 + text.print(">", self.x + self.width - 9, self.y + 2, right_color) + + local val_str = tostring(self.value) + local val_x = self.x + math.floor(self.width / 2) - math.floor(#val_str * 3) + text.print(val_str, val_x, self.y + 2, 1) + + text.font() +end + +return Counter diff --git a/tiny-cli/src/main/resources/sfx/widgets/Dropdown.lua b/tiny-cli/src/main/resources/sfx/widgets/Dropdown.lua new file mode 100644 index 00000000..eb9dc135 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/Dropdown.lua @@ -0,0 +1,253 @@ +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget +local inside_rect = utils.inside_rect + +local draw_9patch = function(x, y, width, height, sx, sy) + local corner = 5 + local edge = 14 + local inner_w = width - corner * 2 + local inner_h = height - corner * 2 + + -- Corners + spr.sdraw(x, y, sx, sy, corner, corner) + spr.sdraw(x + width - corner, y, sx + 19, sy, corner, corner) + spr.sdraw(x, y + height - corner, sx, sy + 19, corner, corner) + spr.sdraw(x + width - corner, y + height - corner, sx + 19, sy + 19, corner, corner) + + -- Top edge + local cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(x + corner + cx, y, sx + 5, sy, tw, corner) + cx = cx + tw + end + + -- Bottom edge + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(x + corner + cx, y + height - corner, sx + 5, sy + 19, tw, corner) + cx = cx + tw + end + + -- Left edge + local cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(x, y + corner + cy, sx, sy + 5, corner, th) + cy = cy + th + end + + -- Right edge + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(x + width - corner, y + corner + cy, sx + 19, sy + 5, corner, th) + cy = cy + th + end + + -- Center fill + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(x + corner + cx, y + corner + cy, sx + 5, sy + 5, tw, th) + cx = cx + tw + end + cy = cy + th + end +end + +local draw_headless_9patch = function(x, y, width, height, sx, sy) + local corner = 5 + local edge = 14 + local inner_w = width - corner * 2 + local inner_h = height - corner + + -- Bottom corners + spr.sdraw(x, y + height - corner, sx, sy + 19, corner, corner) + spr.sdraw(x + width - corner, y + height - corner, sx + 19, sy + 19, corner, corner) + + -- Bottom edge + local cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(x + corner + cx, y + height - corner, sx + 5, sy + 19, tw, corner) + cx = cx + tw + end + + -- Left edge + local cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(x, y + cy, sx, sy + 5, corner, th) + cy = cy + th + end + + -- Right edge + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(x + width - corner, y + cy, sx + 19, sy + 5, corner, th) + cy = cy + th + end + + -- Center fill + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(x + corner + cx, y + cy, sx + 5, sy + 5, tw, th) + cx = cx + tw + end + cy = cy + th + end +end + +local set_value = function(self, value) + for i, opt in ipairs(self.options) do + if opt == value then + self.selected = i + self.value = opt + if self.on_change then + self:on_change() + end + self:fire_on_update(self.value) + return + end + end +end + +local set_selected = function(self, index) + if index >= 1 and index <= #self.options then + self.selected = index + self.value = self.options[index] + if self.on_change then + self:on_change() + end + self:fire_on_update(self.value) + end +end + +local Dropdown = { + x = 0, + y = 0, + width = 64, + height = 16, + options = {}, + selected = 1, + value = "", + open = false, + hovered = nil, + item_height = 16, + listeners = {}, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, + set_value = set_value, + set_selected = set_selected, +} + +Dropdown._init = function(self) + self.height = math.max(self.height, 16) + self.item_height = self.height + if #self.options > 0 then + self.value = self.options[self.selected] or self.options[1] + end +end + +Dropdown._update = function(self) + local pos = ctrl.touched(0) + if pos == nil then + return + end + + if self.open then + -- Check if an option row was clicked + for i = 1, #self.options do + local oy = self.y + self.height + (i - 1) * self.item_height + if inside_rect(pos.x, pos.y, self.x, oy, self.width, self.item_height) then + self.selected = i + self.value = self.options[i] + self.open = false + self.hovered = nil + if self.on_change then + self:on_change() + end + self:fire_on_update(self.value) + return + end + end + + -- Check if the closed box was clicked (toggle close) + if inside_widget(self, pos.x, pos.y) then + self.open = false + self.hovered = nil + return + end + + -- Clicked outside — close + self.open = false + self.hovered = nil + else + -- Closed: click on the widget opens it + if inside_widget(self, pos.x, pos.y) then + self.open = true + end + end +end + +Dropdown._draw = function(self) + local prev = spr.sheet(2) + + -- Closed box: White variant + local sx = 1 * 24 + local sy = 0 + draw_9patch(self.x, self.y, self.width, self.height, sx, sy) + + -- Dropdown icon + local icon_x = self.x + self.width - 8 - 4 + local icon_y = self.y + math.floor((self.height - 8) / 2) + spr.sdraw(icon_x, icon_y, 8, 32, 8, 8) + + spr.sheet(prev) + + -- Selected value with monogram font + text.font("monogram") + local ty = self.y + 2 + text.print(self.value, self.x + 4, ty, 1) + text.font() + + -- Open state: draw options list below + if self.open then + local mouse = ctrl.touch() + self.hovered = nil + + prev = spr.sheet(2) + + for i = 1, #self.options do + local oy = self.y + self.height + (i - 1) * self.item_height + + if inside_rect(mouse.x, mouse.y, self.x, oy, self.width, self.item_height) then + self.hovered = i + draw_headless_9patch(self.x, oy, self.width, self.item_height, 2 * 24, sy) + else + draw_headless_9patch(self.x, oy, self.width, self.item_height, 1 * 24, sy) + end + end + + spr.sheet(prev) + + text.font("monogram") + for i = 1, #self.options do + local oy = self.y + self.height + (i - 1) * self.item_height + text.print(self.options[i], self.x + 4, oy + 2, 1) + end + text.font() + end +end + +return Dropdown diff --git a/tiny-cli/src/main/resources/sfx/widgets/Envelop.lua b/tiny-cli/src/main/resources/sfx/widgets/Envelop.lua index fe2237a7..4d5675ba 100644 --- a/tiny-cli/src/main/resources/sfx/widgets/Envelop.lua +++ b/tiny-cli/src/main/resources/sfx/widgets/Envelop.lua @@ -47,12 +47,16 @@ Envelop._update = function(self) end local blue = 9 -local green = 13 -local purple = 7 -local red = 5 +local green = 6 +local purple = 8 +local red = 3 Envelop._draw = function(self) + -- background + shape.rectf(self.x, self.y, self.width, self.height, 2) + shape.rect(self.x, self.y, self.width, self.height, 1) + -- attack shape.line( self.padded_x, self.padded_y + self.padded_height, diff --git a/tiny-cli/src/main/resources/sfx/widgets/Fader.lua b/tiny-cli/src/main/resources/sfx/widgets/Fader.lua index cb772e46..c90e0028 100644 --- a/tiny-cli/src/main/resources/sfx/widgets/Fader.lua +++ b/tiny-cli/src/main/resources/sfx/widgets/Fader.lua @@ -1,86 +1,105 @@ -local on_update = function(self, listener) - table.insert(self.listeners, listener) -end - -local fire_on_update = function(self, value) - for l in all(self.listeners) do - l(self, value) - end -end - -local set_value = function(self, value) - self.value = value - if (self.on_change) then - self:on_change() - end - self:fire_on_update(value) -end - -local function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget local Fader = { x = 0, y = 0, - width = 11, - height = 80, + width = 8, + height = 40, enabled = true, - min_value = 0, - max_value = 10, value = 0, - tip_color = 9, - disabled_color = 7, label = "", type = "fader", data = nil, index = 0, - on_value_update = function(fader, value) - end, + focused = false, listeners = {}, - on_update = on_update, - fire_on_update = fire_on_update, - set_value = set_value, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, + set_value = utils.set_value, } Fader._update = function(self) + local touching = ctrl.touching(0) + + if touching ~= nil then + local pos = ctrl.touch() + if inside_widget(self, pos.x, pos.y) then + if not self.focused then + self.focused = true + if self.on_press then + self:on_press() + end + end + local track_top = self.y + local track_bottom = self.y + self.height + local track_height = track_bottom - track_top + local clamped_y = math.max(track_top, math.min(track_bottom, pos.y)) + local percent = 1.0 - ((clamped_y - track_top) / track_height) + local value = percent * percent + self:set_value(value) + else + -- Mouse held but moved outside: unfocus so a new fader can take focus + if self.focused then + self.focused = false + if self.on_release then + self:on_release() + end + end + end + else + if self.focused then + self.focused = false + if self.on_release then + self:on_release() + end + end + end + local pos = ctrl.touch() - if inside_widget(self.hitbox, pos.x, pos.y) then + if inside_widget(self, pos.x, pos.y) then if self.on_hover ~= nil then self:on_hover() end + end +end - if ctrl.touching(0) then - local percent = math.max(0.0, 1.0 - ((pos.y - self.y) / self.height)) - self.value = percent +Fader._draw = function(self) + local prev = spr.sheet(2) - -- todo: to be removed as fire_on_update should be used instead - if self.on_value_update then - self:on_value_update(self.value) - end + -- Head sprite (top of track) + spr.sdraw(self.x, self.y, 0, 24, 8, 8) - self:fire_on_update(self.value) - end + -- Body sprites (fill the middle) + local body_start = self.y + 8 + local body_end = self.y + self.height - 8 + local y = body_start + while y < body_end do + spr.sdraw(self.x, y, 0, 32, 8, 8) + y = y + 8 end -end -Fader._draw = function(self) - local color = self.disabled_color + -- Bottom sprite + spr.sdraw(self.x, self.y + self.height - 8, 0, 40, 8, 8) - if self.value ~= nil and self.value > 0 then - color = self.tip_color + -- Compute cursor Y from value (reverse the logarithmic mapping) + local percent = self.value + local track_top = self.y + 1 + local track_bottom = self.y + self.height - 3 + local track_height = track_bottom - track_top + local cursor_y = math.ceil(track_top + (1.0 - percent) * track_height) + + -- Yellow fill (palette index 8) between cursor bottom and fader bottom + local fill_top = cursor_y + 2 + local fill_bottom = self.y + self.height - 1 + if fill_top < fill_bottom then + shape.rectf(self.x + 1, fill_top, 6, fill_bottom - fill_top, 9) end - local y = self.height - self.value * self.height - local tipy = self.y + y - shape.rectf(self.x + 1, tipy, self.width - 2, 2, self.tip_color) + + -- Cursor sprite (movable handle, 3px tall within 8x8 cell) + spr.sdraw(self.x, cursor_y - 1, 8, 24, 8, 8) + + spr.sheet(prev) end -return Fader \ No newline at end of file +return Fader diff --git a/tiny-cli/src/main/resources/sfx/widgets/Help.lua b/tiny-cli/src/main/resources/sfx/widgets/Help.lua deleted file mode 100644 index d5688b54..00000000 --- a/tiny-cli/src/main/resources/sfx/widgets/Help.lua +++ /dev/null @@ -1,15 +0,0 @@ -local Help = { - _type = "Help", - label = "" -} - -Help._update = function(self) - -end - -Help._draw = function(self) - print(self.label, self.x, self.y + 2) - shape.rect(self.x, self.y, self.width, self.height) -end - -return Help \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/widgets/Keyboard.lua b/tiny-cli/src/main/resources/sfx/widgets/Keyboard.lua index 83433842..1804b1ae 100644 --- a/tiny-cli/src/main/resources/sfx/widgets/Keyboard.lua +++ b/tiny-cli/src/main/resources/sfx/widgets/Keyboard.lua @@ -1,14 +1,5 @@ -local function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget local Keyboard = { value = nil, @@ -21,18 +12,34 @@ Keyboard._update = function(self) local color_to_note = { [1] = "C4", - [3] = "Cs4", - [4] = "D4", - [5] = "Ds4", - [8] = "E4", - [13] = "F4", - [12] = "Fs4", - [15] = "G4", - [11] = "Gs4", - [17] = "A4", - [7] = "As4", - [10] = "B4" + [2] = "Cs4", + [5] = "D4", + [10] = "Ds4", + [12] = "E4", + [4] = "F4", + [6] = "Fs4", + [9] = "G4", + [3] = "Gs4", + [7] = "A4", + [8] = "As4", + [13] = "B4", + } + + local key_to_note = { + a = "C4", + w = "Cs4", + s = "D4", + e = "Ds4", + d = "E4", + f = "F4", + t = "Fs4", + g = "G4", + y = "Gs4", + h = "A4", + u = "As4", + j = "B4" } + local pos = ctrl.touch() local value @@ -40,10 +47,25 @@ Keyboard._update = function(self) local relative_x = pos.x - self.x local relative_y = pos.y - self.y + local prev = spr.sheet(2) local color = spr.pget(relative_x + spr_x, relative_y + spr_y) + spr.sheet(prev) value = color_to_note[color] else - value = nil + -- No mouse/touch input: check physical keyboard + for k, note in pairs(key_to_note) do + if ctrl.pressing(keys[k]) then + value = note + self._held_key = k + break + end + end + -- Detect key release + if value == nil and self._held_key then + if not ctrl.pressing(keys[self._held_key]) then + self._held_key = nil + end + end end -- There is a value change. @@ -56,7 +78,9 @@ Keyboard._update = function(self) end Keyboard._draw = function(self) + local prev = spr.sheet(2) spr.sdraw(self.x, self.y, 0, 192, self.width, self.height) + spr.sheet(prev) end return Keyboard \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/widgets/Knob.lua b/tiny-cli/src/main/resources/sfx/widgets/Knob.lua index efbd30c6..894bad39 100644 --- a/tiny-cli/src/main/resources/sfx/widgets/Knob.lua +++ b/tiny-cli/src/main/resources/sfx/widgets/Knob.lua @@ -1,14 +1,5 @@ -local function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget local Knob = { label = "", @@ -23,22 +14,25 @@ local Knob = { Knob._init = function(self) local color_mapping = { - Blue = 0, + Red = 0, Green = 1, - Purple = 2, - Red = 3 + Yellow = 2, + Orange = 3, + Blue = 4 } self.color = color_mapping[self.fields.Color] or 0 self.label = self.fields.Label end Knob._draw = function(self) + local prev = spr.sheet(2) local i = math.floor(self.value * 6) - spr.sdraw(self.x, self.y, 16 + self.color * 16, 48, 16, 16) - spr.sdraw(self.x, self.y, 16 + i * 16, 64, 16, 16) + spr.sdraw(self.x, self.y, 24 + self.color * 16, 24, 16, 16) + spr.sdraw(self.x, self.y, 24 + i * 16, 56, 16, 16) print(self.label, self.x, self.y + self.height + 2) + spr.sheet(prev) end Knob._update = function(self) diff --git a/tiny-cli/src/main/resources/sfx/widgets/MatrixSelector.lua b/tiny-cli/src/main/resources/sfx/widgets/MatrixSelector.lua deleted file mode 100644 index c03da21a..00000000 --- a/tiny-cli/src/main/resources/sfx/widgets/MatrixSelector.lua +++ /dev/null @@ -1,113 +0,0 @@ -local MatrixSelector = { - hover_index = nil, - value = nil, - size = 9, - label = "", - active_indices = {} -} - -MatrixSelector._init = function(self) - self.label = self.fields.Label -end - -MatrixSelector._update = function(self) - local p = ctrl.touch() - - local cols = math.ceil(math.sqrt(self.size)) - local rows = math.ceil(self.size / cols) - local cell_width = self.width / cols - local cell_height = self.height / rows - - if inside_widget(self, p.x, p.y) then - local x = p.x - self.x - local y = p.y - self.y - - local col = math.floor(x / cell_width) - local row = math.floor(y / cell_height) - local index = col + row * cols - - if index < self.size then - self.hover_index = index - else - self.hover_index = nil - end - else - self.hover_index = nil - end - - if (self.hover_index and ctrl.touched(0)) then - self.value = self.hover_index - if (self.on_change) then - self:on_change() - end - end -end - -local active_index = 0 -local inactive_index = 5 -local hover_index = 1 -local selected_index = 3 - -MatrixSelector._draw = function(self) - local cols = math.ceil(math.sqrt(self.size)) - local rows = math.ceil(self.size / cols) - local cell_width = self.width / cols - local cell_height = self.height / rows - - local index = 0 - - for row = 0, rows - 1 do - for col = 0, cols - 1 do - if index < self.size then - local x = self.x + col * cell_width - local y = self.y + row * cell_height - local is_active = self:is_active(index) - - if (self.value == index) then - -- Selected index: filled with color 3 - spr.sdraw(x, y, 16 + selected_index * 16, 96, 16, 8) - elseif (self.hover_index == index) then - -- Hovered index: border with color 3 - spr.sdraw(x, y, 16 + hover_index * 16, 96, 16, 8) - elseif is_active then - -- Active index (not selected, not hovered): filled with color 8 - spr.sdraw(x, y, 16 + active_index * 16, 96, 16, 8) - else - -- Inactive index: border with color 4 - spr.sdraw(x, y, 16 + inactive_index * 16, 96, 16, 8) - end - print(index, x + 2, y + 2) - end - index = index + 1 - end - end - print(self.label, self.x, self.y - 6) -end - --- Function to toggle the active state of an index -MatrixSelector.toggle_active = function(self, index) - if self:is_active(index) then - -- Remove from active list - for i = #self.active_indices, 1, -1 do - if self.active_indices[i] == index then - table.remove(self.active_indices, i) - break - end - end - else - -- Add to active list - table.insert(self.active_indices, index) - end -end - --- Function to check if an index is active -MatrixSelector.is_active = function(self, index) - for i = 1, #self.active_indices do - if self.active_indices[i] == index then - return true - end - end - return false -end - -return MatrixSelector \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/widgets/MenuItem.lua b/tiny-cli/src/main/resources/sfx/widgets/MenuItem.lua deleted file mode 100644 index 695b2805..00000000 --- a/tiny-cli/src/main/resources/sfx/widgets/MenuItem.lua +++ /dev/null @@ -1,90 +0,0 @@ -local on_update = function(self, listener) - table.insert(self.listeners, listener) -end - -local fire_on_update = function(self, value) - for l in all(self.listeners) do - l(self, value) - end -end - -local set_value = function(self, value) - self.value = value - if (self.on_change) then - self:on_change() - end - self:fire_on_update(value) -end - -local function inside_widget(w, x, y, offset) - local off = 0 - if (offset) then - off = offset - end - - return w.x - off <= x and - x <= w.x + w.width + off and - w.y - off <= y and - y <= w.y + w.height + off -end - -local MenuItem = { - _type = "MenuItem", - spr = nil, - hold = false, - status = 0, - active = 0, - help = "", - on_click = function() - end, - on_hover = function() - end, - listeners = {}, - on_update = on_update, - fire_on_update = fire_on_update, - set_value = set_value, -} - -local menuItems = {} - -MenuItem._update = function(self) - local pos = ctrl.touch() - if not self.hold then - self.active = 0 - end - - if inside_widget(self, pos.x, pos.y) then - if self.active == 0 then - self.status = 1 - end - if ctrl.touched(0) then - self:fire_on_update(self.status) - if(self.on_change) then - self:on_change() - end - if self.hold then - for i in all(menuItems) do - i.active = 0 - end - end - self.active = 1 - self.status = 0 - end - self:on_hover() - else - self.status = 0 - end - -end - -MenuItem._draw = function(self) - if self.spr ~= nil then - spr.draw(self.spr + self.status * 128 + self.active * (128 + 32), self.x, self.y) - end - - if self.label ~= nil then - print(self.value, self.x + 5, self.y + 2) - end -end - -return { MenuItem = MenuItem, menuItems = menuItems } \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/widgets/Modal.lua b/tiny-cli/src/main/resources/sfx/widgets/Modal.lua new file mode 100644 index 00000000..f0f7430b --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/Modal.lua @@ -0,0 +1,189 @@ +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget + +local widget_factories = { + Knob = "create_knob", + Button = "create_button", + Fader = "create_fader", + Checkbox = "create_checkbox", + Envelop = "create_envelop", + Keyboard = "create_keyboard", + Dropdown = "create_dropdown", + TextInput = "create_text_input", + TextButton = "create_text_button", +} + +local Modal = { + x = 0, + y = 0, + width = 128, + height = 128, + level_name = nil, + visible = false, + widgets = {}, + close_button = { x = 0, y = 0, width = 7, height = 7 }, + border_color = 18, + background_color = 1, + listeners = {}, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, +} + +Modal._init = function(self, widget_factory) + self.close_button = { + x = self.x + self.width - 11, + y = self.y + 4, + width = 7, + height = 7, + } + + if self.level_name and widget_factory then + self:_load_widgets(widget_factory) + end +end + +Modal._load_widgets = function(self, widget_factory) + self.widgets = {} + self.text_input = nil + self.dropdown = nil + local buttons = {} + + local previous_level = map.level() + map.level(self.level_name) + local entities = map.entities() + + for entity_type, factory_method in pairs(widget_factories) do + if entities[entity_type] then + for entity in all(entities[entity_type]) do + if widget_factory[factory_method] then + local widget = widget_factory[factory_method](widget_factory, entity) + widget.x = widget.x + self.x + widget.y = widget.y + self.y + table.insert(self.widgets, widget) + + if entity_type == "TextInput" and not self.text_input then + self.text_input = widget + elseif entity_type == "Dropdown" and not self.dropdown then + self.dropdown = widget + elseif entity_type == "Button" then + table.insert(buttons, widget) + elseif entity_type == "TextButton" then + table.insert(buttons, widget) + end + end + end + end + end + + -- Wire buttons in the modal to validate + for _, button in ipairs(buttons) do + button.on_change = function() + self:validate() + end + end + + map.level(previous_level) +end + +Modal.open = function(self, initial_value) + self.visible = true + if initial_value and self.text_input then + self.text_input:set_value(initial_value) + self.text_input.focused = true + end +end + +Modal.close = function(self) + self.visible = false + if self.on_cancel then + self:on_cancel() + end +end + +Modal.validate = function(self) + self.visible = false + local value = nil + if self.text_input then + value = self.text_input.value + end + local dropdown_value = nil + if self.dropdown then + dropdown_value = self.dropdown.selected + end + if self.on_validate then + self:on_validate(value, dropdown_value) + end + self:fire_on_update(value) +end + +Modal._update = function(self) + if not self.visible then + return + end + + -- Update child widgets + for w in all(self.widgets) do + w:_update() + end + + local pos = ctrl.touched(0) + if pos == nil then + return + end + + -- Close button click + if inside_widget(self.close_button, pos.x, pos.y) then + self:close() + return + end + + -- Click outside modal → close (unless dropdown is open) + if not inside_widget(self, pos.x, pos.y) then + if self.dropdown and self.dropdown.open then + -- Let dropdown handle the click + else + self:close() + return + end + end +end + +Modal._draw = function(self) + if not self.visible then + return + end + + -- Draw the modal level as background + if self.level_name then + local previous_level = map.level() + map.level(self.level_name) + gfx.camera(-self.x, -self.y) + map.draw() + gfx.camera() + map.level(previous_level) + end + + -- Draw child widgets (open dropdown drawn last so it renders on top) + local open_dropdown = nil + for w in all(self.widgets) do + if w == self.dropdown and self.dropdown.open then + open_dropdown = w + else + w:_draw() + end + end + if open_dropdown then + open_dropdown:_draw() + end + + -- Close button (X) in top-right corner + local bx = self.close_button.x + local by = self.close_button.y + local bw = self.close_button.width + local bh = self.close_button.height + shape.rect(bx, by, bw, bh, self.border_color) + shape.line(bx + 2, by + 2, bx + bw - 2, by + bh - 2, self.border_color) + shape.line(bx + bw - 2, by + 2, bx + 2, by + bh - 2, self.border_color) +end + +return Modal diff --git a/tiny-cli/src/main/resources/sfx/widgets/ModeSwitch.lua b/tiny-cli/src/main/resources/sfx/widgets/ModeSwitch.lua deleted file mode 100644 index 95d00d0f..00000000 --- a/tiny-cli/src/main/resources/sfx/widgets/ModeSwitch.lua +++ /dev/null @@ -1,78 +0,0 @@ -local ModeSwitch = { - hover_index = nil, - selected_index = 0, - button_width = 16, - button_height = 16, - button_margin_right = 8, - buttons = { - Instrument = { - overlay = { x = 16, y = 80 }, - on_change = function() - tiny.exit("instrument-editor.lua") - end, - help = "Instrument Editor" - }, - Sfx = { - overlay = { x = 32, y = 80 }, - on_change = function() - tiny.exit("sfx-editor.lua") - end, - help = "SFX Editor" - }, - Music = { - overlay = { x = 32, y = 9 * 16 }, - on_change = function() - tiny.exit("music-editor.lua") - end, - help = "Music Editor" - }, - }, - background_unselected = { x = 112, y = 0 }, - background_hover = { x = 120, y = 0 }, - background_selected = { x = 112, y = 8 } -} - -ModeSwitch._update = function(self) - local pos = ctrl.touch() - - if inside_widget(self, pos.x, pos.y) then - self.hover = true - if ctrl.touched(0) then - local button = self.buttons[self.fields.ModeType] - button.on_change() - end - else - self.hover = false - end - -end - -ModeSwitch._draw = function(self) - - local button = self.buttons[self.fields.ModeType] - - if self.fields.IsSelected then - for i = 0, 8, 8 do - for j = 0, 8, 8 do - spr.sdraw(self.x + i, self.y + j, self.background_selected.x, self.background_selected.y, 8, 8) - end - end - elseif self.hover then - for i = 0, 8, 8 do - for j = 0, 8, 8 do - spr.sdraw(self.x + i, self.y + j, self.background_hover.x, self.background_hover.y, 8, 8) - end - end - else - for i = 0, 8, 8 do - for j = 0, 8, 8 do - spr.sdraw(self.x + i, self.y + j, self.background_unselected.x, self.background_unselected.y, 8, 8) - end - end - end - - spr.sdraw(self.x, self.y, button.overlay.x, button.overlay.y, self.width, self.height) - -end - -return ModeSwitch \ No newline at end of file diff --git a/tiny-cli/src/main/resources/sfx/widgets/Panel.lua b/tiny-cli/src/main/resources/sfx/widgets/Panel.lua new file mode 100644 index 00000000..882afa50 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/Panel.lua @@ -0,0 +1,85 @@ +local Panel = { + x = 0, + y = 0, + width = 48, + height = 48, + variant = 0, +} + +Panel._update = function(self) + -- purely visual, no interaction +end + +Panel._draw = function(self) + local prev = spr.sheet(2) + + local sx = self.variant * 24 + local sy = 0 + local corner = 5 + local edge = 14 + + local inner_w = self.width - corner * 2 + local inner_h = self.height - corner * 2 + + -- Corners + spr.sdraw(self.x, self.y, sx, sy, corner, corner) + spr.sdraw(self.x + self.width - corner, self.y, sx + 19, sy, corner, corner) + spr.sdraw(self.x, self.y + self.height - corner, sx, sy + 19, corner, corner) + spr.sdraw(self.x + self.width - corner, self.y + self.height - corner, sx + 19, sy + 19, corner, corner) + + -- Top edge + local cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y, sx + 5, sy, tw, corner) + cx = cx + tw + end + + -- Bottom edge + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y + self.height - corner, sx + 5, sy + 19, tw, corner) + cx = cx + tw + end + + -- Left edge + local cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(self.x, self.y + corner + cy, sx, sy + 5, corner, th) + cy = cy + th + end + + -- Right edge + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(self.x + self.width - corner, self.y + corner + cy, sx + 19, sy + 5, corner, th) + cy = cy + th + end + + -- Center fill + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y + corner + cy, sx + 5, sy + 5, tw, th) + cx = cx + tw + end + cy = cy + th + end + + spr.sheet(prev) + + -- Render optional label + if self.label and #self.label > 0 then + text.font("monogram") + text.print(self.label, self.x + 4, self.y + 4, 1) + text.font() + end +end + +return Panel diff --git a/tiny-cli/src/main/resources/sfx/widgets/Speaker.lua b/tiny-cli/src/main/resources/sfx/widgets/Speaker.lua new file mode 100644 index 00000000..2fcc74ae --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/Speaker.lua @@ -0,0 +1,73 @@ +local Speaker = { x = 0, y = 0, width = 98, height = 96 } + +local NOTE_SPR_X = 56 +local NOTE_SPR_Y = 40 +local NOTE_SIZE = 16 +local NOTE_LIFETIME = 1.0 +local NOTE_SPAWN_INTERVAL = 0.12 + +Speaker._update = function(self) + self.particles = self.particles or {} + self.spawn_timer = self.spawn_timer or 0 + + if self.playing then + self.spawn_timer = self.spawn_timer - tiny.dt + if self.spawn_timer <= 0 then + self.spawn_timer = NOTE_SPAWN_INTERVAL + + -- spawn from near the center of the speaker + local side = math.random() > 0.5 and 1 or -1 + local cx = self.x + self.width / 2 + local cy = self.y + self.height / 2 + + table.insert(self.particles, { + x = cx + side * (4 + math.random() * 6), + y = cy + (math.random() - 0.5) * 8, + vx = side * (40 + math.random() * 30), + vy = -(30 + math.random() * 35), + life = NOTE_LIFETIME, + }) + end + end + + -- update particles + local alive = {} + for _, p in ipairs(self.particles) do + p.life = p.life - tiny.dt + if p.life > 0 then + p.vy = p.vy + 80 * tiny.dt + p.x = p.x + p.vx * tiny.dt + p.y = p.y + p.vy * tiny.dt + table.insert(alive, p) + end + end + self.particles = alive +end + +Speaker._draw = function(self) + local prev = spr.sheet(2) + + -- draw speaker (shake when playing) + local sx, sy = self.x, self.y + if self.playing then + sx = sx + math.random(-1, 1) + sy = sy + math.random(-1, 1) + end + spr.sdraw(sx, sy, 208, 0, 48, 96) + spr.sdraw(sx + 48, sy, 208, 0, 48, 96, true, false) + + -- draw note particles + if self.particles then + for _, p in ipairs(self.particles) do + local alpha = p.life / NOTE_LIFETIME + -- fade out: skip drawing if too faint + if alpha > 0.1 then + spr.sdraw(p.x - NOTE_SIZE / 2, p.y - NOTE_SIZE / 2, NOTE_SPR_X, NOTE_SPR_Y, NOTE_SIZE, NOTE_SIZE) + end + end + end + + spr.sheet(prev) +end + +return Speaker diff --git a/tiny-cli/src/main/resources/sfx/widgets/TextButton.lua b/tiny-cli/src/main/resources/sfx/widgets/TextButton.lua new file mode 100644 index 00000000..e8729bf9 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/TextButton.lua @@ -0,0 +1,140 @@ +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget + +local TextButton = { + x = 0, + y = 0, + width = 48, + height = 24, + label = "", + is_active = false, + enabled = true, + variant = 0, + status = 0, -- 0 : idle ; 1 : over ; 2 : active + listeners = {}, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, + shake_timer = 0, + shake_offset = 0, +} + +TextButton.shake = function(self) + self.shake_timer = 0.4 +end + +TextButton._update = function(self) + if self.shake_timer > 0 then + self.shake_timer = self.shake_timer - tiny.dt + self.shake_offset = math.sin(self.shake_timer * 30) * 2 + if self.shake_timer <= 0 then + self.shake_timer = 0 + self.shake_offset = 0 + end + end + + if self.status == 2 or self.is_active then + return + end + + local pos = ctrl.touch() + + if inside_widget(self, pos.x, pos.y) then + self.status = 1 + local touched = ctrl.touched(0) + if touched then + if (self.on_change) then + self:on_change() + end + end + else + self.status = 0 + end +end + +TextButton._draw = function(self) + local real_x = self.x + self.x = self.x + (self.shake_offset or 0) + + local prev = spr.sheet(2) + + local draw_variant + if self.is_active or self.status > 0 then + draw_variant = self.variant + else + draw_variant = 3 -- HardBlue as idle color + end + + local sx = draw_variant * 24 + local sy = 0 + local corner = 5 + local edge = 14 + + local inner_w = self.width - corner * 2 + local inner_h = self.height - corner * 2 + + -- Corners + spr.sdraw(self.x, self.y, sx, sy, corner, corner) + spr.sdraw(self.x + self.width - corner, self.y, sx + 19, sy, corner, corner) + spr.sdraw(self.x, self.y + self.height - corner, sx, sy + 19, corner, corner) + spr.sdraw(self.x + self.width - corner, self.y + self.height - corner, sx + 19, sy + 19, corner, corner) + + -- Top edge + local cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y, sx + 5, sy, tw, corner) + cx = cx + tw + end + + -- Bottom edge + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y + self.height - corner, sx + 5, sy + 19, tw, corner) + cx = cx + tw + end + + -- Left edge + local cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(self.x, self.y + corner + cy, sx, sy + 5, corner, th) + cy = cy + th + end + + -- Right edge + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(self.x + self.width - corner, self.y + corner + cy, sx + 19, sy + 5, corner, th) + cy = cy + th + end + + -- Center fill + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y + corner + cy, sx + 5, sy + 5, tw, th) + cx = cx + tw + end + cy = cy + th + end + + spr.sheet(prev) + + -- Render label centered + if self.label and #self.label > 0 then + text.font("monogram") + local tx = self.x + 4 + local ty = self.y + 2 + text.print(self.label, tx, ty, 1) + text.font() + end + + self.x = real_x +end + +return TextButton diff --git a/tiny-cli/src/main/resources/sfx/widgets/TextInput.lua b/tiny-cli/src/main/resources/sfx/widgets/TextInput.lua new file mode 100644 index 00000000..95d186ca --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/TextInput.lua @@ -0,0 +1,193 @@ +local utils = require("widgets.utils") +local inside_widget = utils.inside_widget + +local PADDING = 5 +local CURSOR_SX = 72 +local CURSOR_SY = 40 +local CURSOR_W = 4 +local CURSOR_H = 12 + +local TextInput = { + x = 0, + y = 0, + width = 32, + height = 32, + value = "", + focused = false, + cursor = 0, + enabled = true, + listeners = {}, + on_update = utils.on_update, + fire_on_update = utils.fire_on_update, + cursor_blink = 0, +} + +TextInput._init = function(self) + self.cursor = #self.value +end + +TextInput._update = function(self) + -- Handle focus on click + local touched = ctrl.touched(0) + if touched then + if inside_widget(self, touched.x, touched.y) then + self.focused = true + else + self.focused = false + end + end + + if not self.focused then + return + end + + -- Blink cursor + self.cursor_blink = self.cursor_blink + 1 + if self.cursor_blink > 60 then + self.cursor_blink = 0 + end + + local changed = false + + local pressed_keys = ctrl.pressed() + if pressed_keys then + for k in all(pressed_keys) do + if k >= keys.a and k <= keys.z then + -- Letter key: convert key ordinal to character + local ch = string.char(string.byte("a") + (k - keys.a)) + local before = string.sub(self.value, 1, self.cursor) + local after = string.sub(self.value, self.cursor + 1) + self.value = before .. ch .. after + self.cursor = self.cursor + 1 + changed = true + elseif k >= keys["0"] and k <= keys["9"] then + -- Number key: convert key ordinal to digit character + local ch = string.char(string.byte("0") + (k - keys["0"])) + local before = string.sub(self.value, 1, self.cursor) + local after = string.sub(self.value, self.cursor + 1) + self.value = before .. ch .. after + self.cursor = self.cursor + 1 + changed = true + elseif k == keys.space then + local before = string.sub(self.value, 1, self.cursor) + local after = string.sub(self.value, self.cursor + 1) + self.value = before .. " " .. after + self.cursor = self.cursor + 1 + changed = true + elseif k == keys.delete then + if self.cursor > 0 then + local before = string.sub(self.value, 1, self.cursor - 1) + local after = string.sub(self.value, self.cursor + 1) + self.value = before .. after + self.cursor = self.cursor - 1 + changed = true + end + elseif k == keys.left then + self.cursor = math.max(0, self.cursor - 1) + elseif k == keys.right then + self.cursor = math.min(#self.value, self.cursor + 1) + end + end + end + + if changed then + if self.on_change then + self:on_change() + end + self:fire_on_update(self.value) + end +end + +TextInput._draw = function(self) + local prev = spr.sheet(2) + + -- 9-patch background (White variant, flipped on both axes) + local sx = 24 -- White variant = 1 * 24 + local sy = 0 + local corner = 5 + local edge = 14 + + local inner_w = self.width - corner * 2 + local inner_h = self.height - corner * 2 + + -- Corners (flipped: source positions swap) + spr.sdraw(self.x, self.y, sx + 19, sy + 19, corner, corner, true, true) + spr.sdraw(self.x + self.width - corner, self.y, sx, sy + 19, corner, corner, true, true) + spr.sdraw(self.x, self.y + self.height - corner, sx + 19, sy, corner, corner, true, true) + spr.sdraw(self.x + self.width - corner, self.y + self.height - corner, sx, sy, corner, corner, true, true) + + -- Top edge (from bottom edge source, flipped) + local cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y, sx + 5, sy + 19, tw, corner, true, true) + cx = cx + tw + end + + -- Bottom edge (from top edge source, flipped) + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y + self.height - corner, sx + 5, sy, tw, corner, true, true) + cx = cx + tw + end + + -- Left edge (from right edge source, flipped) + local cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(self.x, self.y + corner + cy, sx + 19, sy + 5, corner, th, true, true) + cy = cy + th + end + + -- Right edge (from left edge source, flipped) + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + spr.sdraw(self.x + self.width - corner, self.y + corner + cy, sx, sy + 5, corner, th, true, true) + cy = cy + th + end + + -- Center fill (flipped) + cy = 0 + while cy < inner_h do + local th = math.min(edge, inner_h - cy) + cx = 0 + while cx < inner_w do + local tw = math.min(edge, inner_w - cx) + spr.sdraw(self.x + corner + cx, self.y + corner + cy, sx + 5, sy + 5, tw, th, true, true) + cx = cx + tw + end + cy = cy + th + end + + -- Draw text with monogram font + local text_x = self.x + PADDING + local text_y = self.y + PADDING + text.font("monogram") + text.print(self.value, text_x, text_y, 1) + + -- Draw sprite cursor (blinking) + if self.focused and self.cursor_blink < 40 then + local before_cursor = string.sub(self.value, 1, self.cursor) + local cursor_x = text_x + text.width(before_cursor) + spr.sdraw(cursor_x - 2, self.y + PADDING, CURSOR_SX, CURSOR_SY, CURSOR_W, CURSOR_H) + end + + text.font() + spr.sheet(prev) +end + +TextInput.set_value = function(self, value) + if value == self.value then + return + end + self.value = value + self.cursor = #value + if self.on_change then + self:on_change() + end + self:fire_on_update(value) +end + +return TextInput diff --git a/tiny-cli/src/main/resources/sfx/widgets/icons.lua b/tiny-cli/src/main/resources/sfx/widgets/icons.lua new file mode 100644 index 00000000..49651c45 --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/icons.lua @@ -0,0 +1,26 @@ +local icons = { + -- Waveform icons (spritesheet 2, 16px apart, row y=72) + Sine = { x = 24, y = 72 }, + Pulse = { x = 40, y = 72 }, + Noise = { x = 56, y = 72 }, + Sawtooth = { x = 72, y = 72 }, + Triangle = { x = 88, y = 72 }, + Square = { x = 104, y = 72 }, + Drum = { x = 120, y = 72 }, + -- Editor mode icons (16x16, row y=80) + Instrument = { x = 16, y = 80 }, + Sfx = { x = 32, y = 80 }, + Music = { x = 48, y = 80 }, + -- Action icons (16x16, row y=48) + Play = { x = 40, y = 88 }, + Stop = { x = 56, y = 88 }, + Save = { x = 128, y = 48 }, + Export = { x = 144, y = 48 }, + Gear = { x = 136, y = 72 }, + Random = { x = 24, y = 88 }, + Envelope = { x = 80, y = 80 }, + Harmonics = { x = 96, y = 80 }, + Modulations = { x = 112, y = 80 }, +} + +return icons diff --git a/tiny-cli/src/main/resources/sfx/widgets/utils.lua b/tiny-cli/src/main/resources/sfx/widgets/utils.lua new file mode 100644 index 00000000..a72e0f9b --- /dev/null +++ b/tiny-cli/src/main/resources/sfx/widgets/utils.lua @@ -0,0 +1,49 @@ +local utils = {} + +utils.inside_widget = function(w, x, y, offset) + local off = 0 + if (offset) then + off = offset + end + + return w.x - off <= x and + x <= w.x + w.width + off and + w.y - off <= y and + y <= w.y + w.height + off +end + +utils.inside_rect = function(x, y, rx, ry, rw, rh) + return rx <= x and x <= rx + rw and + ry <= y and y <= ry + rh +end + +utils.on_update = function(self, listener) + table.insert(self.listeners, listener) +end + +utils.fire_on_update = function(self, value) + for l in all(self.listeners) do + l(self, value) + end +end + +utils.set_value = function(self, value) + self.value = value + if (self.on_change) then + self:on_change() + end + self:fire_on_update(value) +end + +utils.variant_mapping = { + LigthBlue = 0, + White = 1, + Yellow = 2, + HardBlue = 3, + Red = 4, + Green = 5, + Orange = 6, + Purple = 7, +} + +return utils diff --git a/tiny-cli/src/main/resources/sfx/wire.lua b/tiny-cli/src/main/resources/sfx/wire.lua index 4159c0b4..fb9e2acc 100644 --- a/tiny-cli/src/main/resources/sfx/wire.lua +++ b/tiny-cli/src/main/resources/sfx/wire.lua @@ -20,12 +20,12 @@ local function get_value(obj, path) for i = 1, #parts do local key = parts[i] if type(current) ~= "table" then - debug.console("ERROR: get_value - attempt to index a " .. type(current) .. " value") - debug.console(" path:", path) - debug.console(" i:", i) - debug.console(" key:", key) - debug.console(" current:", current) - debug.console(" parts:", parts) + console.log("ERROR: get_value - attempt to index a " .. type(current) .. " value") + console.log(" path:", path) + console.log(" i:", i) + console.log(" key:", key) + console.log(" current:", current) + console.log(" parts:", parts) return nil end current = current[key] @@ -39,7 +39,7 @@ end -- Set a nested value in an object using a path local function set_value(obj, path, value) - -- debug.console("set_value", obj, path, value) + -- console.log("set_value", obj, path, value) local parts = parse_path(path) local current = obj @@ -94,6 +94,7 @@ function guessMode(target) return "change" end end + --- Sync data from source to target -- Updates target whenever source changes (via on_change) or continuously (via _update) -- @param source Source object diff --git a/tiny-debugger/build.gradle.kts b/tiny-debugger/build.gradle.kts new file mode 100644 index 00000000..d5e1945b --- /dev/null +++ b/tiny-debugger/build.gradle.kts @@ -0,0 +1,36 @@ +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + alias(libs.plugins.minigdx.mpp) + alias(libs.plugins.kotlin.serialization) + + id("io.github.turansky.seskar") version "4.27.0" + id("org.jetbrains.kotlin.plugin.js-plain-objects") version "2.2.20" + id("io.github.turansky.kfc.application") version "14.12.0" +} + +configurations.create("tinyDebugger") { + isCanBeResolved = false + isCanBeConsumed = true +} + +dependencies { + commonTestImplementation(kotlin("test")) + + commonMainImplementation(libs.kotlin.serialization.json) + + jsMainImplementation(libs.kotlin.coroutines) + jsMainImplementation("org.jetbrains.kotlin:kotlinx-atomicfu-runtime:2.1.20") + ?.because("https://youtrack.jetbrains.com/issue/KT-57235") +} + +val tinyDebugger = tasks.register("tinyDebugger", Jar::class) { + group = "tiny" + from(tasks.named("jsBundleProduction")) + this.rename { "tiny-debugger.zip" } + this.destinationDirectory.set(project.layout.buildDirectory.dir("tiny-dist")) + this.archiveVersion.set("") +} + +artifacts { + add("tinyDebugger", tinyDebugger) +} diff --git a/tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/FileInfo.kt b/tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/FileInfo.kt new file mode 100644 index 00000000..14fb0b03 --- /dev/null +++ b/tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/FileInfo.kt @@ -0,0 +1,6 @@ +package com.github.minigdx.tiny.cli.debug + +import kotlinx.serialization.Serializable + +@Serializable +data class FileInfo(val name: String, val content: String) diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/LuaValue.kt b/tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/LuaValue.kt similarity index 100% rename from tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/LuaValue.kt rename to tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/LuaValue.kt diff --git a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/RemoteCommand.kt b/tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/RemoteCommand.kt similarity index 67% rename from tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/RemoteCommand.kt rename to tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/RemoteCommand.kt index 65534c67..d8e32f05 100644 --- a/tiny-cli/src/main/kotlin/com/github/minigdx/tiny/cli/debug/RemoteCommand.kt +++ b/tiny-debugger/src/commonMain/kotlin/com/github/minigdx/tiny/cli/debug/RemoteCommand.kt @@ -23,6 +23,15 @@ sealed interface EngineRemoteCommand : RemoteCommand @Serializable data class Reload(val script: String) : EngineRemoteCommand +@Serializable +data class AllFiles(val files: List) : EngineRemoteCommand + +@Serializable +data class FileChanged(val file: FileInfo) : EngineRemoteCommand + +@Serializable +data class GameMetadata(val gameId: String, val gameName: String) : EngineRemoteCommand + /** * Toggle a breakpoint in the game engine. * @@ -34,11 +43,27 @@ data class Reload(val script: String) : EngineRemoteCommand @Serializable data class ToggleBreakpoint(val script: String, val line: Int, val enabled: Boolean, val condition: String? = null) : DebugRemoteCommand +/** + * Delete a breakpoint from the game engine. + * + * @param script the name of the script where the breakpoint is. + * @param line the line number of the breakpoint. + */ +@Serializable +data class DeleteBreakpoint(val script: String, val line: Int) : DebugRemoteCommand + +@Serializable +enum class ResumeMode { + RESUME, + STEP_INTO, + STEP_OVER, +} + /** * Resume game execution. */ @Serializable -data class ResumeExecution(val advanceByStep: Boolean = false) : DebugRemoteCommand +data class ResumeExecution(val mode: ResumeMode = ResumeMode.RESUME) : DebugRemoteCommand /** * Resume game execution. @@ -94,3 +119,20 @@ data class BreakpointInfo( val enabled: Boolean, val condition: String? = null, ) + +/** + * Evaluate a Lua expression in the current execution context. + * + * @param expression the Lua expression to evaluate. + */ +@Serializable +data class EvaluateExpression(val expression: String) : DebugRemoteCommand + +/** + * Result of evaluating a Lua expression. + * + * @param result the string representation of the result. + * @param error an error message if the evaluation failed. + */ +@Serializable +data class EvaluationResult(val result: String, val error: String? = null) : EngineRemoteCommand diff --git a/tiny-debugger/src/jsMain/kotlin/BreakpointPanel.kt b/tiny-debugger/src/jsMain/kotlin/BreakpointPanel.kt new file mode 100644 index 00000000..4ede864c --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/BreakpointPanel.kt @@ -0,0 +1,127 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.document +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLInputElement +import org.w3c.dom.HTMLSpanElement + +class BreakpointPanel( + private val container: HTMLDivElement, + private val onToggleBreakpoint: (String, Int, Boolean) -> Unit, + private val onRemoveBreakpoint: (String, Int) -> Unit, + private val onNavigateBreakpoint: (String, Int) -> Unit, + private val onRemoveAll: () -> Unit, +) { + fun update( + breakpoints: Map>, + conditions: Map>, + disabledBreakpoints: Map> = emptyMap(), + ) { + container.innerHTML = "" + + val allBps = mutableListOf>() + breakpoints.forEach { (script, lines) -> + lines.sorted().forEach { line -> + val condition = conditions[script]?.get(line) + allBps.add(Triple(script, line, condition)) + } + } + + val allDisabled = mutableListOf>() + disabledBreakpoints.forEach { (script, lines) -> + lines.sorted().forEach { line -> + val condition = conditions[script]?.get(line) + allDisabled.add(Triple(script, line, condition)) + } + } + + val totalCount = allBps.size + allDisabled.size + + if (totalCount == 0) { + val empty = document.createElement("div") as HTMLDivElement + empty.className = "bp-empty" + empty.textContent = "No breakpoints set" + container.appendChild(empty) + return + } + + // Master row with count and remove-all button + val masterRow = document.createElement("div") as HTMLDivElement + masterRow.className = "bp-master-row" + + val masterLabel = document.createElement("span") as HTMLSpanElement + masterLabel.className = "bp-info" + masterLabel.textContent = "$totalCount breakpoint${if (totalCount != 1) "s" else ""}" + masterRow.appendChild(masterLabel) + + val removeAllBtn = document.createElement("span") as HTMLElement + removeAllBtn.className = "bp-remove-all" + removeAllBtn.innerHTML = LucideIcons.trash2 + removeAllBtn.title = "Remove all breakpoints" + removeAllBtn.onclick = { + onRemoveAll() + } + masterRow.appendChild(removeAllBtn) + + container.appendChild(masterRow) + + // Enabled breakpoint rows + allBps.forEach { (script, line, condition) -> + container.appendChild(createBpRow(script, line, condition, enabled = true)) + } + + // Disabled breakpoint rows + allDisabled.forEach { (script, line, condition) -> + container.appendChild(createBpRow(script, line, condition, enabled = false)) + } + } + + private fun createBpRow( + script: String, + line: Int, + condition: String?, + enabled: Boolean, + ): HTMLDivElement { + val row = document.createElement("div") as HTMLDivElement + row.className = "bp-row" + + val checkbox = document.createElement("input") as HTMLInputElement + checkbox.type = "checkbox" + checkbox.className = "bp-toggle" + checkbox.checked = enabled + checkbox.onchange = { + onToggleBreakpoint(script, line, checkbox.checked) + } + row.appendChild(checkbox) + + val info = document.createElement("span") as HTMLSpanElement + info.className = "bp-info" + info.textContent = "$script:$line" + info.title = "Click to navigate" + info.onclick = { + onNavigateBreakpoint(script, line) + } + row.appendChild(info) + + if (condition != null) { + val badge = document.createElement("span") as HTMLSpanElement + badge.className = "bp-condition-badge" + badge.innerHTML = LucideIcons.circleEllipsis + badge.title = "Condition: $condition" + row.appendChild(badge) + } + + val removeBtn = document.createElement("span") as HTMLElement + removeBtn.className = "bp-remove" + removeBtn.innerHTML = LucideIcons.trash2 + removeBtn.title = "Remove breakpoint" + removeBtn.onclick = { e -> + e.stopPropagation() + onRemoveBreakpoint(script, line) + } + row.appendChild(removeBtn) + + return row + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/BreakpointStorage.kt b/tiny-debugger/src/jsMain/kotlin/BreakpointStorage.kt new file mode 100644 index 00000000..abc3c682 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/BreakpointStorage.kt @@ -0,0 +1,50 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.window +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@Serializable +data class StoredBreakpoint( + val script: String, + val line: Int, + val enabled: Boolean, + val condition: String? = null, +) + +object BreakpointStorage { + private val json = Json { ignoreUnknownKeys = true } + + fun save( + gameId: String, + breakpoints: Map>, + conditions: Map>, + disabledBreakpoints: Map> = emptyMap(), + ) { + val stored = mutableListOf() + breakpoints.forEach { (script, lines) -> + lines.forEach { line -> + val condition = conditions[script]?.get(line) + stored.add(StoredBreakpoint(script, line, true, condition)) + } + } + disabledBreakpoints.forEach { (script, lines) -> + lines.forEach { line -> + val condition = conditions[script]?.get(line) + stored.add(StoredBreakpoint(script, line, false, condition)) + } + } + val data = json.encodeToString(stored) + window.localStorage.setItem("tiny-debugger-bp-$gameId", data) + } + + fun load(gameId: String): List { + val data = window.localStorage.getItem("tiny-debugger-bp-$gameId") ?: return emptyList() + return try { + json.decodeFromString(data) + } catch (_: Exception) { + emptyList() + } + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/BrowserNotification.kt b/tiny-debugger/src/jsMain/kotlin/BrowserNotification.kt new file mode 100644 index 00000000..7f991010 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/BrowserNotification.kt @@ -0,0 +1,12 @@ +package com.github.minigdx.tiny.debugger + +import kotlin.js.Promise + +@JsName("Notification") +external class BrowserNotification(title: String, options: dynamic = definedExternally) { + companion object { + val permission: String + + fun requestPermission(): Promise + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/CodeEditor.kt b/tiny-debugger/src/jsMain/kotlin/CodeEditor.kt new file mode 100644 index 00000000..243db9f5 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/CodeEditor.kt @@ -0,0 +1,175 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.document +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLElement + +data class BreakpointMarker( + val line: Int, + val hasCondition: Boolean = false, +) + +class CodeEditor( + private val gutterContainer: HTMLDivElement, + private val codeContainer: HTMLDivElement, + private val onToggleBreakpoint: (Int) -> Unit, + private val onConditionRequest: (Int) -> Unit, +) { + private var content: String = "" + private var lines: List = emptyList() + private var breakpoints = mutableSetOf() + private var disabledBreakpoints = mutableSetOf() + private var conditions = mutableMapOf() + private var highlightedLine: Int? = null + + init { + codeContainer.addEventListener("scroll", { + gutterContainer.scrollTop = codeContainer.scrollTop + }) + } + + fun setContent(code: String) { + content = code + lines = code.split("\n") + render() + codeContainer.scrollTop = 0.0 + gutterContainer.scrollTop = 0.0 + } + + fun setBreakpoints( + bps: Set, + conds: Map, + disabled: Set = emptySet(), + ) { + breakpoints = bps.toMutableSet() + conditions = conds.toMutableMap() + disabledBreakpoints = disabled.toMutableSet() + renderGutter() + } + + fun highlightLine(line: Int?) { + highlightedLine = line + renderCode() + renderGutter() + if (line != null) { + scrollToLine(line) + } + } + + fun toggleBreakpoint(line: Int) { + if (breakpoints.contains(line)) { + breakpoints.remove(line) + conditions.remove(line) + } else { + breakpoints.add(line) + } + renderGutter() + } + + fun setCondition( + line: Int, + condition: String?, + ) { + if (condition != null) { + conditions[line] = condition + } else { + conditions.remove(line) + } + renderGutter() + } + + fun hasBreakpoint(line: Int): Boolean = breakpoints.contains(line) + + fun getCondition(line: Int): String? = conditions[line] + + private fun render() { + renderGutter() + renderCode() + } + + private fun renderGutter() { + gutterContainer.innerHTML = "" + for (i in lines.indices) { + val lineNum = i + 1 + val gutterLine = document.createElement("div") as HTMLDivElement + gutterLine.className = "gutter-line" + + if (highlightedLine == lineNum) { + gutterLine.classList.add("gutter-hit") + } + + if (breakpoints.contains(lineNum)) { + val marker = document.createElement("span") as HTMLElement + marker.className = "breakpoint-marker" + marker.innerHTML = + if (conditions.containsKey(lineNum)) { + LucideIcons.circleEllipsis + } else { + LucideIcons.circleDot + } + marker.title = + if (conditions.containsKey(lineNum)) { + "Conditional: ${conditions[lineNum]}" + } else { + "Breakpoint" + } + gutterLine.appendChild(marker) + } else if (disabledBreakpoints.contains(lineNum)) { + val marker = document.createElement("span") as HTMLElement + marker.className = "breakpoint-marker breakpoint-disabled" + marker.innerHTML = LucideIcons.circleSlash2 + marker.title = "Breakpoint (disabled)" + gutterLine.appendChild(marker) + } + + val lineNumber = document.createElement("span") as HTMLElement + lineNumber.className = "line-number" + lineNumber.textContent = "$lineNum" + gutterLine.appendChild(lineNumber) + + gutterLine.onclick = { e -> + onToggleBreakpoint(lineNum) + } + + gutterLine.oncontextmenu = { e -> + e.preventDefault() + onConditionRequest(lineNum) + } + + gutterContainer.appendChild(gutterLine) + } + } + + private fun renderCode() { + codeContainer.innerHTML = "" + + val highlighted = highlight(content) + val codeLines = highlighted.split("\n") + + codeLines.forEachIndexed { index, line -> + val lineDiv = document.createElement("div") as HTMLDivElement + lineDiv.className = "code-line" + lineDiv.innerHTML = line + + val lineNum = index + 1 + if (highlightedLine == lineNum) { + lineDiv.classList.add("code-line-hit") + } else { + lineDiv.classList.remove("code-line-hit") + } + + codeContainer.appendChild(lineDiv) + } + } + + fun scrollToLine(line: Int) { + val lineElements = codeContainer.children + val index = line - 1 + if (index >= 0 && index < lineElements.length) { + val element = lineElements.item(index) as? HTMLElement + element?.scrollIntoView( + js("({behavior: 'smooth', block: 'center'})"), + ) + } + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/ConditionModal.kt b/tiny-debugger/src/jsMain/kotlin/ConditionModal.kt new file mode 100644 index 00000000..e62e1e53 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/ConditionModal.kt @@ -0,0 +1,75 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.document +import org.w3c.dom.HTMLButtonElement +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLTextAreaElement + +class ConditionModal( + private val overlay: HTMLDivElement, +) { + private var onSave: ((String?) -> Unit)? = null + + fun show( + currentCondition: String?, + callback: (String?) -> Unit, + ) { + onSave = callback + overlay.innerHTML = "" + overlay.style.display = "flex" + + val modal = document.createElement("div") as HTMLDivElement + modal.className = "modal" + + val title = document.createElement("div") as HTMLDivElement + title.className = "modal-title" + title.textContent = "Breakpoint Condition" + modal.appendChild(title) + + val desc = document.createElement("div") as HTMLDivElement + desc.className = "modal-desc" + desc.textContent = "Enter a Lua expression. The breakpoint will only trigger when it evaluates to true." + modal.appendChild(desc) + + val textarea = document.createElement("textarea") as HTMLTextAreaElement + textarea.className = "modal-textarea" + textarea.value = currentCondition ?: "" + textarea.placeholder = "e.g. x > 10" + modal.appendChild(textarea) + + val buttons = document.createElement("div") as HTMLDivElement + buttons.className = "modal-buttons" + + val clearBtn = document.createElement("button") as HTMLButtonElement + clearBtn.className = "modal-btn modal-btn-clear" + clearBtn.textContent = "Clear" + clearBtn.onclick = { + overlay.style.display = "none" + onSave?.invoke(null) + } + buttons.appendChild(clearBtn) + + val cancelBtn = document.createElement("button") as HTMLButtonElement + cancelBtn.className = "modal-btn modal-btn-cancel" + cancelBtn.textContent = "Cancel" + cancelBtn.onclick = { + overlay.style.display = "none" + } + buttons.appendChild(cancelBtn) + + val saveBtn = document.createElement("button") as HTMLButtonElement + saveBtn.className = "modal-btn modal-btn-save" + saveBtn.textContent = "Save" + saveBtn.onclick = { + overlay.style.display = "none" + val value = textarea.value.trim() + onSave?.invoke(value.ifEmpty { null }) + } + buttons.appendChild(saveBtn) + + modal.appendChild(buttons) + overlay.appendChild(modal) + + textarea.focus() + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/DebuggerApp.kt b/tiny-debugger/src/jsMain/kotlin/DebuggerApp.kt new file mode 100644 index 00000000..36202f92 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/DebuggerApp.kt @@ -0,0 +1,409 @@ +package com.github.minigdx.tiny.debugger + +import com.github.minigdx.tiny.cli.debug.BreakpointHit +import com.github.minigdx.tiny.cli.debug.CurrentBreakpoints +import com.github.minigdx.tiny.cli.debug.FileInfo +import com.github.minigdx.tiny.cli.debug.GameMetadata +import com.github.minigdx.tiny.cli.debug.Reload +import kotlinx.browser.document +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLInputElement + +class DebuggerApp { + private val scripts = mutableMapOf() + private val breakpoints = mutableMapOf>() + private val disabledBreakpoints = mutableMapOf>() + private val conditions = mutableMapOf>() + private var currentScript: String? = null + private var gameId: String? = null + private var isPaused = false + + private lateinit var scriptList: ScriptList + private lateinit var codeEditor: CodeEditor + private lateinit var variableInspector: VariableInspector + private lateinit var toolbar: Toolbar + private lateinit var conditionModal: ConditionModal + private lateinit var evaluateModal: EvaluateModal + private lateinit var engineSocket: EngineDebugSocket + private lateinit var breakpointPanel: BreakpointPanel + private lateinit var toggleAllCheckbox: HTMLInputElement + + fun init() { + val scriptListContainer = document.getElementById("script-list") as HTMLDivElement + val gutterContainer = document.getElementById("gutter") as HTMLDivElement + val codeContainer = document.getElementById("code") as HTMLDivElement + val variablesContainer = document.getElementById("variables") as HTMLDivElement + val toolbarContainer = document.getElementById("toolbar") as HTMLDivElement + val modalOverlay = document.getElementById("modal-overlay") as HTMLDivElement + val breakpointPanelContainer = document.getElementById("breakpoint-panel") as HTMLDivElement + toggleAllCheckbox = document.getElementById("toggle-all-breakpoints") as HTMLInputElement + + conditionModal = ConditionModal(modalOverlay) + evaluateModal = EvaluateModal(modalOverlay) + + codeEditor = CodeEditor( + gutterContainer, + codeContainer, + onToggleBreakpoint = { line -> handleToggleBreakpoint(line) }, + onConditionRequest = { line -> handleConditionRequest(line) }, + ) + + variableInspector = VariableInspector(variablesContainer) + variableInspector.clear() + + scriptList = ScriptList(scriptListContainer) { name -> + selectScript(name) + } + + breakpointPanel = BreakpointPanel( + breakpointPanelContainer, + onToggleBreakpoint = { script, line, enabled -> + if (enabled) { + disabledBreakpoints[script]?.remove(line) + val scriptBps = breakpoints.getOrPut(script) { mutableSetOf() } + scriptBps.add(line) + } else { + breakpoints[script]?.remove(line) + val scriptDisabled = disabledBreakpoints.getOrPut(script) { mutableSetOf() } + scriptDisabled.add(line) + } + val condition = conditions[script]?.get(line) + engineSocket.toggleBreakpoint(script, line, enabled, condition) + refreshEditorBreakpoints() + updateToggleAllCheckbox() + saveBreakpointsToStorage() + }, + onRemoveBreakpoint = { script, line -> + breakpoints[script]?.remove(line) + disabledBreakpoints[script]?.remove(line) + conditions[script]?.remove(line) + engineSocket.deleteBreakpoint(script, line) + refreshEditorBreakpoints() + refreshBreakpointPanel() + saveBreakpointsToStorage() + }, + onNavigateBreakpoint = { script, line -> + selectScript(script) + codeEditor.scrollToLine(line) + }, + onRemoveAll = { + breakpoints.forEach { (script, lines) -> + lines.forEach { line -> + engineSocket.deleteBreakpoint(script, line) + } + } + disabledBreakpoints.forEach { (script, lines) -> + lines.forEach { line -> + engineSocket.deleteBreakpoint(script, line) + } + } + breakpoints.clear() + disabledBreakpoints.clear() + conditions.clear() + refreshEditorBreakpoints() + refreshBreakpointPanel() + saveBreakpointsToStorage() + }, + ) + + toolbar = Toolbar( + toolbarContainer, + onResume = { + isPaused = false + toolbar.setPaused(false) + codeEditor.highlightLine(null) + evaluateModal.hide() + engineSocket.resume() + }, + onStep = { engineSocket.step() }, + onStepOver = { engineSocket.stepOver() }, + onEvaluate = { + evaluateModal.show { expression -> + engineSocket.evaluateExpression(expression) + } + }, + onDisconnect = { engineSocket.disconnect() }, + onConnect = { engineSocket.connect() }, + ) + + engineSocket = EngineDebugSocket( + onBreakpointHit = { hit -> handleBreakpointHit(hit) }, + onCurrentBreakpoints = { bp -> handleCurrentBreakpoints(bp) }, + onReload = { reload -> handleReload(reload) }, + onAllFiles = { msg -> handleFiles(msg.files) }, + onFileChanged = { msg -> handleFileChanged(msg.file) }, + onGameMetadata = { meta -> handleGameMetadata(meta) }, + onEvaluationResult = { result -> evaluateModal.showResult(result.result, result.error) }, + onConnected = { toolbar.setConnected(true) }, + onDisconnected = { + toolbar.setConnected(false) + isPaused = false + toolbar.setPaused(false) + codeEditor.highlightLine(null) + variableInspector.clear() + evaluateModal.hide() + }, + ) + + toggleAllCheckbox.onchange = { + handleToggleAllBreakpoints(toggleAllCheckbox.checked) + } + + initDragHandle() + engineSocket.connect() + + if (BrowserNotification.permission == "default") { + BrowserNotification.requestPermission() + } + } + + private fun handleGameMetadata(meta: GameMetadata) { + gameId = meta.gameId + restoreBreakpointsFromStorage() + engineSocket.requestBreakpoints() + } + + private fun handleFiles(files: List) { + scripts.clear() + files.forEach { file -> + scripts[file.name] = file.content + } + scriptList.update(scripts.keys.toList()) + if (currentScript == null && scripts.isNotEmpty()) { + selectScript(scripts.keys.first()) + } + } + + private fun handleFileChanged(file: FileInfo) { + scripts[file.name] = file.content + scriptList.update(scripts.keys.toList()) + if (file.name == currentScript) { + codeEditor.setContent(file.content) + refreshEditorBreakpoints() + } + } + + private fun handleBreakpointHit(hit: BreakpointHit) { + isPaused = true + toolbar.setPaused(true) + selectScript(hit.script) + codeEditor.highlightLine(hit.line) + variableInspector.update(hit.locals, hit.upValues) + + if (BrowserNotification.permission == "granted") { + val options: dynamic = js("{}") + options.body = "${hit.script}:${hit.line}" + BrowserNotification("Breakpoint hit", options) + } + } + + private fun handleCurrentBreakpoints(bp: CurrentBreakpoints) { + breakpoints.clear() + disabledBreakpoints.clear() + conditions.clear() + bp.breakpoints.forEach { info -> + if (info.enabled) { + val scriptBps = breakpoints.getOrPut(info.script) { mutableSetOf() } + scriptBps.add(info.line) + } else { + val scriptDisabled = disabledBreakpoints.getOrPut(info.script) { mutableSetOf() } + scriptDisabled.add(info.line) + } + if (info.condition != null) { + val scriptConds = conditions.getOrPut(info.script) { mutableMapOf() } + scriptConds[info.line] = info.condition + } + } + refreshEditorBreakpoints() + refreshBreakpointPanel() + } + + private fun handleReload(reload: Reload) { + isPaused = false + toolbar.setPaused(false) + codeEditor.highlightLine(null) + variableInspector.clear() + } + + private fun selectScript(name: String) { + if (currentScript == name) return + currentScript = name + val content = scripts[name] ?: return + codeEditor.setContent(content) + scriptList.setActive(name, scripts.keys.toList()) + refreshEditorBreakpoints() + codeEditor.highlightLine(null) + } + + private fun refreshEditorBreakpoints() { + val script = currentScript ?: return + val bps = breakpoints[script] ?: emptySet() + val conds = conditions[script] ?: emptyMap() + val disabled = disabledBreakpoints[script] ?: emptySet() + codeEditor.setBreakpoints(bps, conds, disabled) + } + + private fun refreshBreakpointPanel() { + breakpointPanel.update(breakpoints, conditions, disabledBreakpoints) + updateToggleAllCheckbox() + } + + private fun handleToggleBreakpoint(line: Int) { + val script = currentScript ?: return + val scriptBps = breakpoints.getOrPut(script) { mutableSetOf() } + val scriptDisabled = disabledBreakpoints.getOrPut(script) { mutableSetOf() } + if (scriptBps.contains(line)) { + // The line contains a breakpoint: let's remove it + scriptBps.remove(line) + conditions[script]?.remove(line) + engineSocket.deleteBreakpoint(script, line) + } else if (scriptDisabled.contains(line)) { + // The line contains a disabled breakpoint. Let's toggle it. + scriptDisabled.remove(line) + scriptBps.add(line) + val condition = conditions[script]?.get(line) + engineSocket.toggleBreakpoint(script, line, true, condition) + } else { + // The line contains nothing. Let's add a breakpoint. + scriptBps.add(line) + val condition = conditions[script]?.get(line) + engineSocket.toggleBreakpoint(script, line, true, condition) + } + refreshEditorBreakpoints() + refreshBreakpointPanel() + saveBreakpointsToStorage() + } + + private fun handleConditionRequest(line: Int) { + val script = currentScript ?: return + val scriptBps = breakpoints.getOrPut(script) { mutableSetOf() } + val scriptConds = conditions.getOrPut(script) { mutableMapOf() } + val currentCondition = scriptConds[line] + + conditionModal.show(currentCondition) { newCondition -> + if (newCondition != null) { + scriptConds[line] = newCondition + if (!scriptBps.contains(line)) { + scriptBps.add(line) + disabledBreakpoints[script]?.remove(line) + codeEditor.toggleBreakpoint(line) + } + } else { + scriptConds.remove(line) + } + codeEditor.setCondition(line, newCondition) + val enabled = scriptBps.contains(line) + engineSocket.toggleBreakpoint(script, line, enabled, newCondition) + refreshBreakpointPanel() + saveBreakpointsToStorage() + } + } + + private fun handleToggleAllBreakpoints(enabled: Boolean) { + if (enabled) { + disabledBreakpoints.forEach { (script, lines) -> + val scriptBps = breakpoints.getOrPut(script) { mutableSetOf() } + lines.forEach { line -> + scriptBps.add(line) + val condition = conditions[script]?.get(line) + engineSocket.toggleBreakpoint(script, line, true, condition) + } + } + disabledBreakpoints.clear() + } else { + breakpoints.forEach { (script, lines) -> + val scriptDisabled = disabledBreakpoints.getOrPut(script) { mutableSetOf() } + lines.forEach { line -> + scriptDisabled.add(line) + val condition = conditions[script]?.get(line) + engineSocket.toggleBreakpoint(script, line, false, condition) + } + } + breakpoints.clear() + } + refreshEditorBreakpoints() + refreshBreakpointPanel() + saveBreakpointsToStorage() + } + + private fun updateToggleAllCheckbox() { + val totalEnabled = breakpoints.values.sumOf { it.size } + val totalDisabled = disabledBreakpoints.values.sumOf { it.size } + val total = totalEnabled + totalDisabled + if (total == 0) { + toggleAllCheckbox.checked = true + toggleAllCheckbox.indeterminate = false + } else if (totalDisabled == 0) { + toggleAllCheckbox.checked = true + toggleAllCheckbox.indeterminate = false + } else if (totalEnabled == 0) { + toggleAllCheckbox.checked = false + toggleAllCheckbox.indeterminate = false + } else { + toggleAllCheckbox.checked = false + toggleAllCheckbox.indeterminate = true + } + } + + private fun restoreBreakpointsFromStorage() { + val id = gameId ?: return + val stored = BreakpointStorage.load(id) + stored.forEach { sb -> + if (sb.enabled) { + val scriptBps = breakpoints.getOrPut(sb.script) { mutableSetOf() } + scriptBps.add(sb.line) + } else { + val scriptDisabled = disabledBreakpoints.getOrPut(sb.script) { mutableSetOf() } + scriptDisabled.add(sb.line) + } + if (sb.condition != null) { + val scriptConds = conditions.getOrPut(sb.script) { mutableMapOf() } + scriptConds[sb.line] = sb.condition + } + engineSocket.toggleBreakpoint(sb.script, sb.line, sb.enabled, sb.condition) + } + refreshEditorBreakpoints() + refreshBreakpointPanel() + } + + private fun saveBreakpointsToStorage() { + val id = gameId ?: return + BreakpointStorage.save(id, breakpoints, conditions, disabledBreakpoints) + } + + private fun initDragHandle() { + val dragHandle = document.getElementById("drag-handle") ?: return + val variablesPanel = document.getElementById("variables-panel") ?: return + var dragging = false + + dragHandle.addEventListener( + "mousedown", + { e: org.w3c.dom.events.Event -> + dragging = true + e.preventDefault() + }, + ) + + document.addEventListener( + "mousemove", + { e: org.w3c.dom.events.Event -> + if (dragging) { + val mouseEvent = e.asDynamic() + val parentRect = variablesPanel.parentElement?.getBoundingClientRect() + if (parentRect != null) { + val newHeight = parentRect.bottom - (mouseEvent.clientY as Double) + val clamped = newHeight.coerceIn(60.0, 500.0) + variablesPanel.asDynamic().style.height = "${clamped}px" + } + } + }, + ) + + document.addEventListener( + "mouseup", + { _: org.w3c.dom.events.Event -> + dragging = false + }, + ) + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/EngineDebugSocket.kt b/tiny-debugger/src/jsMain/kotlin/EngineDebugSocket.kt new file mode 100644 index 00000000..5d7a906d --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/EngineDebugSocket.kt @@ -0,0 +1,127 @@ +package com.github.minigdx.tiny.debugger + +import com.github.minigdx.tiny.cli.debug.AllFiles +import com.github.minigdx.tiny.cli.debug.BreakpointHit +import com.github.minigdx.tiny.cli.debug.CurrentBreakpoints +import com.github.minigdx.tiny.cli.debug.DebugRemoteCommand +import com.github.minigdx.tiny.cli.debug.DeleteBreakpoint +import com.github.minigdx.tiny.cli.debug.Disconnect +import com.github.minigdx.tiny.cli.debug.EngineRemoteCommand +import com.github.minigdx.tiny.cli.debug.EvaluateExpression +import com.github.minigdx.tiny.cli.debug.EvaluationResult +import com.github.minigdx.tiny.cli.debug.FileChanged +import com.github.minigdx.tiny.cli.debug.GameMetadata +import com.github.minigdx.tiny.cli.debug.Reload +import com.github.minigdx.tiny.cli.debug.RequestBreakpoints +import com.github.minigdx.tiny.cli.debug.ResumeExecution +import com.github.minigdx.tiny.cli.debug.ResumeMode +import com.github.minigdx.tiny.cli.debug.ToggleBreakpoint +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.w3c.dom.WebSocket + +class EngineDebugSocket( + private val onBreakpointHit: (BreakpointHit) -> Unit, + private val onCurrentBreakpoints: (CurrentBreakpoints) -> Unit, + private val onReload: (Reload) -> Unit, + private val onAllFiles: (AllFiles) -> Unit, + private val onFileChanged: (FileChanged) -> Unit, + private val onGameMetadata: (GameMetadata) -> Unit, + private val onEvaluationResult: (EvaluationResult) -> Unit, + private val onConnected: () -> Unit, + private val onDisconnected: () -> Unit, +) { + private var ws: WebSocket? = null + private val json = Json { ignoreUnknownKeys = true } + private var manualDisconnect = false + + fun connect() { + manualDisconnect = false + val host = kotlinx.browser.window.location.host + val protocol = if (kotlinx.browser.window.location.protocol == "https:") "wss" else "ws" + ws = WebSocket("$protocol://$host/debug") + + ws?.onopen = { + console.log("Debug socket connected") + onConnected() + } + + ws?.onmessage = { event -> + val data = event.data as String + try { + val command = json.decodeFromString(data) + when (command) { + is BreakpointHit -> onBreakpointHit(command) + is CurrentBreakpoints -> onCurrentBreakpoints(command) + is Reload -> onReload(command) + is AllFiles -> onAllFiles(command) + is FileChanged -> onFileChanged(command) + is GameMetadata -> onGameMetadata(command) + is EvaluationResult -> onEvaluationResult(command) + } + } catch (e: Exception) { + console.error("Debug socket parse error", e) + } + } + + ws?.onclose = { + console.log("Debug socket disconnected") + onDisconnected() + if (!manualDisconnect) { + console.log("Reconnecting in 2s...") + kotlinx.browser.window.setTimeout({ connect() }, 2000) + } + } + + ws?.onerror = { + console.error("Debug socket error") + } + } + + fun send(command: DebugRemoteCommand) { + val msg = json.encodeToString(command) + ws?.send(msg) + } + + fun toggleBreakpoint( + script: String, + line: Int, + enabled: Boolean, + condition: String? = null, + ) { + send(ToggleBreakpoint(script, line, enabled, condition)) + } + + fun deleteBreakpoint( + script: String, + line: Int, + ) { + send(DeleteBreakpoint(script, line)) + } + + fun resume() { + send(ResumeExecution(mode = ResumeMode.RESUME)) + } + + fun step() { + send(ResumeExecution(mode = ResumeMode.STEP_INTO)) + } + + fun stepOver() { + send(ResumeExecution(mode = ResumeMode.STEP_OVER)) + } + + fun requestBreakpoints() { + send(RequestBreakpoints) + } + + fun evaluateExpression(expression: String) { + send(EvaluateExpression(expression)) + } + + fun disconnect() { + manualDisconnect = true + send(Disconnect) + ws?.close() + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/EvaluateModal.kt b/tiny-debugger/src/jsMain/kotlin/EvaluateModal.kt new file mode 100644 index 00000000..329b1625 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/EvaluateModal.kt @@ -0,0 +1,92 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.document +import org.w3c.dom.HTMLButtonElement +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLTextAreaElement + +class EvaluateModal( + private val overlay: HTMLDivElement, +) { + private var onEvaluate: ((String) -> Unit)? = null + private var resultDiv: HTMLDivElement? = null + + fun show(callback: (String) -> Unit) { + onEvaluate = callback + overlay.innerHTML = "" + overlay.style.display = "flex" + + val modal = document.createElement("div") as HTMLDivElement + modal.className = "modal" + + val title = document.createElement("div") as HTMLDivElement + title.className = "modal-title" + title.textContent = "Evaluate Expression" + modal.appendChild(title) + + val desc = document.createElement("div") as HTMLDivElement + desc.className = "modal-desc" + desc.textContent = "Enter a Lua expression to evaluate in the current execution context." + modal.appendChild(desc) + + val textarea = document.createElement("textarea") as HTMLTextAreaElement + textarea.className = "modal-textarea" + textarea.value = "" + textarea.placeholder = "e.g. 1 + 1" + modal.appendChild(textarea) + + val result = document.createElement("div") as HTMLDivElement + result.className = "modal-result" + result.style.display = "none" + modal.appendChild(result) + resultDiv = result + + val buttons = document.createElement("div") as HTMLDivElement + buttons.className = "modal-buttons" + + val closeBtn = document.createElement("button") as HTMLButtonElement + closeBtn.className = "modal-btn modal-btn-cancel" + closeBtn.textContent = "Close" + closeBtn.onclick = { + hide() + } + buttons.appendChild(closeBtn) + + val evalBtn = document.createElement("button") as HTMLButtonElement + evalBtn.className = "modal-btn modal-btn-save" + evalBtn.textContent = "Evaluate" + evalBtn.onclick = { + val expression = textarea.value.trim() + if (expression.isNotEmpty()) { + onEvaluate?.invoke(expression) + } + } + buttons.appendChild(evalBtn) + + modal.appendChild(buttons) + overlay.appendChild(modal) + + textarea.focus() + } + + fun showResult( + value: String, + error: String?, + ) { + val div = resultDiv ?: return + div.style.display = "block" + if (error != null) { + div.className = "modal-result modal-result-error" + div.textContent = error + } else { + div.className = "modal-result modal-result-success" + div.textContent = value + } + } + + fun hide() { + overlay.style.display = "none" + resultDiv = null + onEvaluate = null + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/Highlight.kt b/tiny-debugger/src/jsMain/kotlin/Highlight.kt new file mode 100644 index 00000000..9844e0a6 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/Highlight.kt @@ -0,0 +1,45 @@ +package com.github.minigdx.tiny.debugger + +/** + * Highlight the code with HTML tags. + * + * - String: + * - Comment: + * - Keyword: + * - Number: + */ +private val KEYWORD_PATTERN = + listOf( + "if", "else", "elif", "end", "while", "for", "in", "of", + "continue", "break", "return", "function", "local", "do", + "then", "repeat", "until", "not", "and", "or", + "true", "false", "nil", + ).joinToString("|") + +private fun escapeHtml(s: String): String = + s + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + +fun highlight(content: String): String { + return content + // Escape HTML entities first to prevent XSS via script content + .let { escapeHtml(it) } + // Create lines + .split("\n").map { "
$it
" }.joinToString("\n") + // Replace \n with
to be selected correctly in the range + .replace("
", "

") + // String (match escaped quotes) + .replace(Regex("(".*?")"), "\$1") + // Comment + .replace(Regex("--(.*)"), """--$1""") + // Keyword + .replace( + Regex("\\b($KEYWORD_PATTERN)\\b"), + """$1""", + ) + // Numbers + .replace(Regex("\\b(\\d+)"), "\$1") +} diff --git a/tiny-debugger/src/jsMain/kotlin/LucideIcons.kt b/tiny-debugger/src/jsMain/kotlin/LucideIcons.kt new file mode 100644 index 00000000..9fd7f363 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/LucideIcons.kt @@ -0,0 +1,43 @@ +package com.github.minigdx.tiny.debugger + +@Suppress("ktlint:standard:max-line-length") +object LucideIcons { + val play = + """""" + + val stepInto = + """""" + + val unplug = + """""" + + val trash2 = + """""" + + val circleDot = + """""" + + val circleEllipsis = + """""" + + val bug = + """""" + + val plug = + """""" + + val pause = + """""" + + val circleSlash2 = + """""" + + val stepOver = + """""" + + val gripHorizontal = + """""" + + val calculator = + """""" +} diff --git a/tiny-debugger/src/jsMain/kotlin/Main.kt b/tiny-debugger/src/jsMain/kotlin/Main.kt new file mode 100644 index 00000000..0db7f749 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/Main.kt @@ -0,0 +1,10 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.window + +fun main() { + val app = DebuggerApp() + window.onload = { + app.init() + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/ScriptList.kt b/tiny-debugger/src/jsMain/kotlin/ScriptList.kt new file mode 100644 index 00000000..18899d57 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/ScriptList.kt @@ -0,0 +1,38 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.document +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLElement + +class ScriptList( + private val container: HTMLDivElement, + private val onSelect: (String) -> Unit, +) { + private var activeScript: String? = null + + fun update(scripts: List) { + container.innerHTML = "" + scripts.forEach { name -> + val item = document.createElement("div") as HTMLElement + item.className = "script-item" + item.textContent = name + if (name == activeScript) { + item.classList.add("active") + } + item.onclick = { + activeScript = name + onSelect(name) + update(scripts) + } + container.appendChild(item) + } + } + + fun setActive( + name: String, + scripts: List, + ) { + activeScript = name + update(scripts) + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/Toolbar.kt b/tiny-debugger/src/jsMain/kotlin/Toolbar.kt new file mode 100644 index 00000000..30e2b419 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/Toolbar.kt @@ -0,0 +1,90 @@ +package com.github.minigdx.tiny.debugger + +import kotlinx.browser.document +import org.w3c.dom.HTMLButtonElement +import org.w3c.dom.HTMLDivElement + +class Toolbar( + private val container: HTMLDivElement, + private val onResume: () -> Unit, + private val onStep: () -> Unit, + private val onStepOver: () -> Unit, + private val onEvaluate: () -> Unit, + private val onDisconnect: () -> Unit, + private val onConnect: () -> Unit, +) { + private val resumeBtn: HTMLButtonElement + private val evaluateBtn: HTMLButtonElement + private val disconnectBtn: HTMLButtonElement + + init { + container.innerHTML = "" + + resumeBtn = document.createElement("button") as HTMLButtonElement + resumeBtn.className = "toolbar-btn" + resumeBtn.innerHTML = LucideIcons.pause + resumeBtn.title = "Running" + container.appendChild(resumeBtn) + + val stepBtn = document.createElement("button") as HTMLButtonElement + stepBtn.className = "toolbar-btn" + stepBtn.innerHTML = "${LucideIcons.stepInto} Step In" + stepBtn.title = "Step to next instruction" + stepBtn.onclick = { onStep() } + container.appendChild(stepBtn) + + val stepOverBtn = document.createElement("button") as HTMLButtonElement + stepOverBtn.className = "toolbar-btn" + stepOverBtn.innerHTML = "${LucideIcons.stepOver} Step Over" + stepOverBtn.title = "Step to next line in current script" + stepOverBtn.onclick = { onStepOver() } + container.appendChild(stepOverBtn) + + evaluateBtn = document.createElement("button") as HTMLButtonElement + evaluateBtn.className = "toolbar-btn" + evaluateBtn.innerHTML = "${LucideIcons.calculator} Eval" + evaluateBtn.title = "Evaluate expression" + container.appendChild(evaluateBtn) + + disconnectBtn = document.createElement("button") as HTMLButtonElement + disconnectBtn.className = "toolbar-btn toolbar-btn-danger" + disconnectBtn.innerHTML = "${LucideIcons.unplug} Disconnect" + disconnectBtn.title = "Disconnect debugger" + disconnectBtn.onclick = { onDisconnect() } + container.appendChild(disconnectBtn) + } + + fun setPaused(paused: Boolean) { + if (paused) { + resumeBtn.className = "toolbar-btn toolbar-btn-paused" + resumeBtn.innerHTML = LucideIcons.play + resumeBtn.title = "Resume execution" + resumeBtn.onclick = { onResume() } + + evaluateBtn.className = "toolbar-btn toolbar-btn-paused" + evaluateBtn.onclick = { onEvaluate() } + } else { + resumeBtn.className = "toolbar-btn" + resumeBtn.innerHTML = LucideIcons.pause + resumeBtn.title = "Running" + resumeBtn.onclick = null + + evaluateBtn.className = "toolbar-btn" + evaluateBtn.onclick = null + } + } + + fun setConnected(connected: Boolean) { + if (connected) { + disconnectBtn.className = "toolbar-btn toolbar-btn-danger" + disconnectBtn.innerHTML = "${LucideIcons.unplug} Disconnect" + disconnectBtn.title = "Disconnect debugger" + disconnectBtn.onclick = { onDisconnect() } + } else { + disconnectBtn.className = "toolbar-btn toolbar-btn-success" + disconnectBtn.innerHTML = "${LucideIcons.plug} Connect" + disconnectBtn.title = "Connect to debugger" + disconnectBtn.onclick = { onConnect() } + } + } +} diff --git a/tiny-debugger/src/jsMain/kotlin/VariableInspector.kt b/tiny-debugger/src/jsMain/kotlin/VariableInspector.kt new file mode 100644 index 00000000..50604631 --- /dev/null +++ b/tiny-debugger/src/jsMain/kotlin/VariableInspector.kt @@ -0,0 +1,137 @@ +package com.github.minigdx.tiny.debugger + +import com.github.minigdx.tiny.cli.debug.LuaValue +import kotlinx.browser.document +import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLTableElement +import org.w3c.dom.HTMLTableSectionElement + +class VariableInspector( + private val container: HTMLDivElement, +) { + fun update( + locals: Map, + upValues: Map, + ) { + container.innerHTML = "" + + if (locals.isNotEmpty()) { + addSection("Locals", locals) + } + + if (upValues.isNotEmpty()) { + addSection("UpValues", upValues) + } + + if (locals.isEmpty() && upValues.isEmpty()) { + val empty = document.createElement("div") as HTMLDivElement + empty.className = "var-empty" + empty.textContent = "No variables to display" + container.appendChild(empty) + } + } + + fun clear() { + container.innerHTML = "" + val empty = document.createElement("div") as HTMLDivElement + empty.className = "var-empty" + empty.textContent = "Hit a breakpoint to inspect variables" + container.appendChild(empty) + } + + private fun addSection( + title: String, + variables: Map, + ) { + val header = document.createElement("div") as HTMLDivElement + header.className = "var-section-header" + header.textContent = title + container.appendChild(header) + + val table = document.createElement("table") as HTMLTableElement + table.className = "var-table" + + val thead = document.createElement("thead") as HTMLElement + thead.innerHTML = "NameValue" + table.appendChild(thead) + + val tbody = document.createElement("tbody") as HTMLTableSectionElement + variables.forEach { (name, value) -> + addVariableRow(tbody, name, value, 0) + } + table.appendChild(tbody) + + container.appendChild(table) + } + + private fun addVariableRow( + tbody: HTMLTableSectionElement, + name: String, + value: LuaValue, + depth: Int, + insertAfterRow: HTMLElement? = null, + ): HTMLElement { + val row = document.createElement("tr") as HTMLElement + row.className = if (depth > 0) "var-row var-child" else "var-row" + row.setAttribute("data-depth", depth.toString()) + + val nameCell = document.createElement("td") as HTMLElement + nameCell.className = "var-name" + nameCell.style.paddingLeft = "${depth * 16 + 8}px" + + val valueCell = document.createElement("td") as HTMLElement + valueCell.className = "var-value" + + when (value) { + is LuaValue.Primitive -> { + nameCell.textContent = name + valueCell.textContent = value.value + } + is LuaValue.Dictionary -> { + val toggle = document.createElement("span") as HTMLElement + toggle.className = "var-toggle" + toggle.textContent = "\u25B6 " + nameCell.appendChild(toggle) + + val nameSpan = document.createElement("span") as HTMLElement + nameSpan.textContent = name + nameCell.appendChild(nameSpan) + + valueCell.textContent = "{${value.entries.size} entries}" + + var expanded = false + + row.onclick = { + expanded = !expanded + toggle.textContent = if (expanded) "\u25BC " else "\u25B6 " + if (expanded) { + var insertAfter = row + value.entries.forEach { (childName, childValue) -> + insertAfter = addVariableRow(tbody, childName, childValue, depth + 1, insertAfter) + } + } else { + // Remove all following rows with depth greater than current + while (true) { + val next = row.nextElementSibling as? HTMLElement ?: break + val nextDepth = next.getAttribute("data-depth")?.toIntOrNull() ?: 0 + if (nextDepth <= depth) break + next.remove() + } + } + } + } + } + + row.appendChild(nameCell) + row.appendChild(valueCell) + + if (insertAfterRow != null) { + insertAfterRow.after(row) + } else { + tbody.appendChild(row) + } + + return row + } +} diff --git a/tiny-debugger/src/jsMain/resources/index.html b/tiny-debugger/src/jsMain/resources/index.html new file mode 100644 index 00000000..ef4e389b --- /dev/null +++ b/tiny-debugger/src/jsMain/resources/index.html @@ -0,0 +1,633 @@ + + + + + + Tiny Debugger + + + + + +
+ +
+
+
+
+
+
+
+
+
Variables
+
+
+
+
+ + + + diff --git a/tiny-debugger/vite.config.mjs b/tiny-debugger/vite.config.mjs new file mode 100644 index 00000000..47ceac08 --- /dev/null +++ b/tiny-debugger/vite.config.mjs @@ -0,0 +1,43 @@ +import { defineConfig } from 'vite' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export default defineConfig({ + base: './', + root: "kotlin", + build: { + sourcemap: true, + rollupOptions: { + input: { + main: resolve(__dirname, 'kotlin/index.html'), + }, + + output: { + entryFileNames: (chunkInfo) => { + if (chunkInfo.name === 'main') { + return 'tiny-debugger.js'; + } + // Default pattern for other entries + return '[name]-[hash].js'; + }, + chunkFileNames: 'chunks/[name]-[hash].js', + manualChunks: (id) => { + // Split vendor code from Kotlin stdlib and coroutines + if (id.includes('node_modules')) { + return 'vendor'; + } + // Split Kotlin standard library + if (id.includes('kotlin-kotlin-stdlib')) { + return 'kotlin-stdlib'; + } + // Split coroutines library + if (id.includes('kotlinx-coroutines-core')) { + return 'kotlin-coroutines'; + } + } + } + } + } +}) diff --git a/tiny-doc-annotations/src/commonMain/kotlin/com/github/mingdx/tiny/doc/TinyAnnotation.kt b/tiny-doc-annotations/src/commonMain/kotlin/com/github/mingdx/tiny/doc/TinyAnnotation.kt index ae750726..2abff674 100644 --- a/tiny-doc-annotations/src/commonMain/kotlin/com/github/mingdx/tiny/doc/TinyAnnotation.kt +++ b/tiny-doc-annotations/src/commonMain/kotlin/com/github/mingdx/tiny/doc/TinyAnnotation.kt @@ -11,6 +11,10 @@ annotation class TinyLib( * Description of the Library. */ val description: String = "", + /** + * Lucide icon name for the library section in documentation. + */ + val icon: String = "", ) @Target(AnnotationTarget.CLASS) diff --git a/tiny-doc/build.gradle.kts b/tiny-doc/build.gradle.kts index dac18896..5dbbf31c 100644 --- a/tiny-doc/build.gradle.kts +++ b/tiny-doc/build.gradle.kts @@ -3,7 +3,7 @@ import org.asciidoctor.gradle.jvm.AsciidoctorTask @Suppress("DSL_SCOPE_VIOLATION") plugins { alias(libs.plugins.asciidoctorj) - alias(libs.plugins.minigdx.developer) + alias(libs.plugins.minigdx.jvm) } val asciidoctorResources by configurations.creating { @@ -11,7 +11,7 @@ val asciidoctorResources by configurations.creating { isCanBeResolved = true } -val asciidoctorDependencies by configurations.creating { +val jsonApiDependencies by configurations.creating { isCanBeConsumed = false isCanBeResolved = true } @@ -28,14 +28,17 @@ dependencies { ) add( - asciidoctorDependencies.name, + jsonApiDependencies.name, project( mapOf( "path" to ":tiny-engine", - "configuration" to "tinyApiAsciidoctor", + "configuration" to "tinyApiJson", ), ), ) + + implementation(libs.pebble) + implementation(libs.kotlin.serialization.json) } val unzipAsciidoctorResources = @@ -46,13 +49,6 @@ val unzipAsciidoctorResources = cp.into(project.layout.buildDirectory.get().asFile.resolve("docs/asciidoc")) } -val copyAsciidoctorDependencies = - tasks.maybeCreate("copy-asciidoctorDependencies", Copy::class).also { cp -> - // I'm bit lazy, I copy the result straight into the source directory :grimace: - cp.into(project.projectDir.resolve("src/docs/asciidoc/dependencies")) - cp.from(asciidoctorDependencies) - } - val copySample = tasks.register("copy-sample", Copy::class) { from(project.projectDir.resolve("src/docs/asciidoc/sample")) @@ -65,14 +61,80 @@ val copyResources = into(project.layout.buildDirectory.get().asFile.resolve("docs/asciidoc/resources")) } +val copyJsonApi = + tasks.maybeCreate("copy-jsonApiDependencies", Copy::class).also { cp -> + cp.into(project.layout.buildDirectory.get().asFile.resolve("docs/asciidoc")) + cp.from(jsonApiDependencies) + } + +val copySeoFiles = tasks.register("copy-seoFiles", Copy::class) { + from(project.projectDir.resolve("src/docs/asciidoc")) { + include("robots.txt") + include("sitemap.xml") + include(".nojekyll") + include("tiny-nav.js") + include("tiny-common.css") + include("showcase.json") + include("tutorials.json") + include("document.json") + } + into(project.layout.buildDirectory.get().asFile.resolve("docs/asciidoc")) +} + +val renderPebbleTemplates = tasks.register("renderPebbleTemplates", JavaExec::class) { + val templateDir = project.projectDir.resolve("src/docs/templates") + val dataDir = project.projectDir.resolve("src/docs/asciidoc") + val outputDir = project.layout.buildDirectory.get().asFile.resolve("docs/asciidoc") + val apiJsonFile = project.layout.buildDirectory.get().asFile.resolve("docs/asciidoc/tiny-api.json") + val cliJsonFile = dataDir.resolve("tiny-cli-commands.json") + + dependsOn( + tasks.named("classes"), + copyJsonApi, + copySeoFiles, + unzipAsciidoctorResources, + copySample, + copyResources, + ) + + classpath = project.the().getByName("main").runtimeClasspath + mainClass.set("com.github.minigdx.tiny.doc.PebbleRendererKt") + args = listOf( + templateDir.absolutePath, + dataDir.absolutePath, + outputDir.absolutePath, + apiJsonFile.absolutePath, + cliJsonFile.absolutePath, + ) + + inputs.dir(templateDir) + inputs.files( + dataDir.resolve("showcase.json"), + dataDir.resolve("tutorials.json"), + dataDir.resolve("document.json"), + cliJsonFile, + ) + inputs.files(apiJsonFile) + outputs.files( + outputDir.resolve("index.html"), + outputDir.resolve("showcase.html"), + outputDir.resolve("documentation.html"), + outputDir.resolve("api.html"), + outputDir.resolve("editor.html"), + outputDir.resolve("tiny-cli.html"), + ) +} + tasks.withType(AsciidoctorTask::class.java).configureEach { this.baseDirFollowsSourceDir() this.notCompatibleWithConfigurationCache("AsciidoctorJ plugin is not compatible with configuration cache") this.dependsOn( unzipAsciidoctorResources.dependsOn(":tiny-web-editor:tinyWebEditor"), - copyAsciidoctorDependencies, + copyJsonApi, copySample, copyResources, + copySeoFiles, + renderPebbleTemplates, ) } diff --git a/tiny-doc/src/docs/asciidoc/.nojekyll b/tiny-doc/src/docs/asciidoc/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/tiny-doc/src/docs/asciidoc/adoc-riak.css b/tiny-doc/src/docs/asciidoc/adoc-riak.css index 43e238ec..38bdaa38 100644 --- a/tiny-doc/src/docs/asciidoc/adoc-riak.css +++ b/tiny-doc/src/docs/asciidoc/adoc-riak.css @@ -1,5 +1,5 @@ @import url(//fonts.googleapis.com/css?family=Titillium+Web:400,700); -@import url(//fonts.googleapis.com/css?family=Noticia+Text:400,400italic); + @import url(//cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.0/css/font-awesome.css); /* Derived from the Riak documentation theme developed by Basho Technologies, Inc. | CC BY 3.0 License | https://docs.basho.org */ /* normalize.css v2.1.1 | MIT License | git.io/normalize */ @@ -124,7 +124,7 @@ table { border-collapse: collapse; border-spacing: 0; } html, body { font-size: 100%; } -body { background: white; color: #222222; padding: 0; margin: 0; font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; } +body { background: white; color: #222222; padding: 56px 0 0 0; margin: 0; font-family: "Titillium Web", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: normal; font-style: normal; line-height: 1; position: relative; cursor: auto; } a:hover { cursor: pointer; } @@ -168,8 +168,8 @@ p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; direction: ltr; } /* Default Link Styles */ -a { color: #2984a9; text-decoration: underline; line-height: inherit; } -a:hover, a:focus { color: #faa94c; } +a { color: #2563eb; text-decoration: underline; line-height: inherit; } +a:hover, a:focus { color: #1d4ed8; } a img { border: none; } /* Default paragraph styles */ @@ -322,7 +322,7 @@ p a > code:hover { color: #444444; } #toctitle { color: #3c3d3f; } @media only screen and (min-width: 1280px) { body.toc2 { padding-left: 20em; } - #toc.toc2 { position: fixed; width: 20em; left: 0; top: 0; border-right: 1px solid #cccccc; border-bottom: 0; z-index: 1000; padding: 1em; height: 100%; overflow: auto; } + #toc.toc2 { position: fixed; width: 20em; left: 0; top: 56px; border-right: 1px solid #cccccc; border-bottom: 0; z-index: 1000; padding: 1em; height: calc(100% - 56px); overflow: auto; } #toc.toc2 #toctitle { margin-top: 0; } #toc.toc2 > ul { font-size: .95em; } #toc.toc2 ul ul { margin-left: 0; padding-left: 1.25em; } @@ -384,7 +384,7 @@ table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p { font-si .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child { margin-bottom: 0; } -.literalblock > .content pre, .listingblock > .content pre { background: url('../images/riak/pre-bg.jpg'); border-width: 0 0 1px 0; border-style: solid; border-color: #f0f0f0; -webkit-border-radius: 4px; border-radius: 4px; padding: 15px; word-wrap: break-word; } +.literalblock > .content pre, .listingblock > .content pre { background: #f8f9fa; border-width: 0 0 1px 0; border-style: solid; border-color: #f0f0f0; -webkit-border-radius: 4px; border-radius: 4px; padding: 15px; word-wrap: break-word; } .literalblock > .content pre.nowrap, .listingblock > .content pre.nowrap { overflow-x: auto; white-space: pre; word-wrap: normal; } .literalblock > .content pre > code, .listingblock > .content pre > code { display: block; } @media only screen { .literalblock > .content pre, .listingblock > .content pre { font-size: 0.64em; } } @@ -616,7 +616,7 @@ div.unbreakable { page-break-inside: avoid; } span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } .admonitionblock td.icon [class^="icon-"]:before { font-size: 2.5em; text-shadow: 0 0 8px white, 1px 1px 2px rgba(0, 0, 0, 0.5); cursor: default; } -.admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #2984a9; color: #1f637f; } +.admonitionblock td.icon .icon-note:before { content: "\f05a"; color: #2563eb; } .admonitionblock td.icon .icon-tip:before { content: "\f0eb"; text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); color: #111; } .admonitionblock td.icon .icon-warning:before { content: "\f071"; color: #bf6900; } .admonitionblock td.icon .icon-caution:before { content: "\f06d"; color: #bf3400; } @@ -628,7 +628,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; } .conum:after { content: attr(data-value); } .conum:not([data-value]):empty { display: none; } -body { background-image: url('../images/riak/body-bg.jpg'); } +body { background-image: none; } ::selection { background-color: #fcc07f; color: #fff; } @@ -638,18 +638,18 @@ body { background-image: url('../images/riak/body-bg.jpg'); } #content ul > li { list-style-type: square; } -p > em { font-family: 'Noticia Text', serif; font-weight: 400; font-size: 95%; } +p > em { font-family: 'Titillium Web', sans-serif; font-weight: 400; font-size: 95%; } -.admonitionblock > table { width: 100%; background-image: url('../images/riak/info-bg.jpg'); border-collapse: separate; border-spacing: 0; -webkit-border-radius: 5px; border-radius: 5px; border: 1px solid #9EC6DF; } +.admonitionblock > table { width: 100%; background-image: none; border-collapse: separate; border-spacing: 0; -webkit-border-radius: 5px; border-radius: 5px; border: 1px solid #9EC6DF; } .admonitionblock > table td.icon { padding: 15px; } .admonitionblock > table td.icon .icon-tip:before { text-shadow: 0 0 20px white, 1px 1px 2px rgba(155, 155, 0, 0.8); } -.admonitionblock > table td.content { font-family: 'Noticia Text', italic; font-size: 90%; font-style: italic; border: 0; padding: 15px; } +.admonitionblock > table td.content { font-family: 'Titillium Web', sans-serif; font-size: 90%; font-style: italic; border: 0; padding: 15px; } -.admonitionblock .literalblock > .content > pre, .admonitionblock .listingblock > .content > pre { background-image: url('../images/riak/info-bg.jpg'); } +.admonitionblock .literalblock > .content > pre, .admonitionblock .listingblock > .content > pre { background-image: none; } .exampleblock > .content { background-color: transparent; border-color: #c9c9c9; } -.sidebarblock { background-image: url('../images/riak/sidebar-bg.jpg'); -webkit-border-radius: 5px; border-radius: 5px; } +.sidebarblock { background-image: none; -webkit-border-radius: 5px; border-radius: 5px; } .sidebarblock > .content > .title { color: #f1f1f1; text-shadow: 0px 2px 2px black; font-size: 1.25em; font-weight: bold; } .sidebarblock > .content ul, .sidebarblock > .content p { color: #f1f1f1; text-shadow: 0px 1px 1px black; } .sidebarblock > .content .title { color: #dfdfdf; } @@ -660,6 +660,6 @@ p > em { font-family: 'Noticia Text', serif; font-weight: 400; font-size: 95%; } table.tableblock.grid-all { -webkit-border-radius: 0; border-radius: 0; -webkit-box-shadow: 0 1px 3px #999999; box-shadow: 0 1px 3px #999999; } -#footer { background-image: url('../images/riak/footer-bg.jpg'); padding: 25px 0; } +#footer { background-image: none; padding: 25px 0; } #footer-text { color: #fff; text-shadow: 1px 1px 1px #333; font-size: 80%; text-align: center; } diff --git a/tiny-doc/src/docs/asciidoc/docinfo-footer.html b/tiny-doc/src/docs/asciidoc/docinfo-footer.html index ed6422a1..5fb786ed 100644 --- a/tiny-doc/src/docs/asciidoc/docinfo-footer.html +++ b/tiny-doc/src/docs/asciidoc/docinfo-footer.html @@ -1 +1,50 @@ + diff --git a/tiny-doc/src/docs/asciidoc/docinfo-header.html b/tiny-doc/src/docs/asciidoc/docinfo-header.html index 95553305..a9021ffe 100644 --- a/tiny-doc/src/docs/asciidoc/docinfo-header.html +++ b/tiny-doc/src/docs/asciidoc/docinfo-header.html @@ -1,5 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tiny-doc/src/docs/asciidoc/document.json b/tiny-doc/src/docs/asciidoc/document.json new file mode 100644 index 00000000..08e17b25 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/document.json @@ -0,0 +1,50 @@ +[ + { + "library": "gfx", + "name": "cls", + "signature": "gfx.cls(color)", + "description": "Clear the entire screen with a single color from the palette. Call this at the beginning of _draw() to reset the screen before drawing your frame.", + "parameters": [ + { "name": "color", "type": "number", "description": "Color index from the palette (0\u2013255)" } + ], + "example": "function _draw()\n gfx.cls(0) -- clear with black\n print(\"hello!\", 10, 10, 7)\nend" + }, + { + "library": "shape", + "name": "rectf", + "signature": "shape.rectf(x, y, width, height, color)", + "description": "Draw a filled rectangle on screen. Useful for UI elements, health bars, backgrounds, and simple geometric visuals.", + "parameters": [ + { "name": "x", "type": "number", "description": "X position of the top-left corner" }, + { "name": "y", "type": "number", "description": "Y position of the top-left corner" }, + { "name": "width", "type": "number", "description": "Width in pixels" }, + { "name": "height", "type": "number", "description": "Height in pixels" }, + { "name": "color", "type": "number", "description": "Color index from the palette" } + ], + "example": "function _draw()\n gfx.cls(1)\n -- draw a red health bar\n shape.rectf(10, 10, 100, 8, 8)\nend" + }, + { + "library": "spr", + "name": "draw", + "signature": "spr.draw(spriteIndex, x, y, [flipX], [flipY])", + "description": "Draw a sprite from the current spritesheet at the given position. Optionally flip it horizontally or vertically for animations and directional characters.", + "parameters": [ + { "name": "spriteIndex", "type": "number", "description": "Index of the sprite in the spritesheet" }, + { "name": "x", "type": "number", "description": "X position to draw at" }, + { "name": "y", "type": "number", "description": "Y position to draw at" }, + { "name": "flipX", "type": "boolean", "description": "Flip horizontally (optional, default false)" }, + { "name": "flipY", "type": "boolean", "description": "Flip vertically (optional, default false)" } + ], + "example": "function _draw()\n gfx.cls(0)\n spr.draw(0, player.x, player.y)\n -- draw facing left\n spr.draw(0, enemy.x, enemy.y, true)\nend" + }, + { + "library": "ctrl", + "name": "pressing", + "signature": "ctrl.pressing(key)", + "description": "Check if a key is currently being held down. Returns true every frame the key is pressed, unlike ctrl.pressed() which fires only once. Ideal for continuous movement.", + "parameters": [ + { "name": "key", "type": "number", "description": "Key constant from the keys table (e.g. keys.left, keys.right, keys.space)" } + ], + "example": "function _update()\n if ctrl.pressing(keys.left) then\n player.x = player.x - 2\n end\n if ctrl.pressing(keys.right) then\n player.x = player.x + 2\n end\nend" + } +] diff --git a/tiny-doc/src/docs/asciidoc/documentation.html b/tiny-doc/src/docs/asciidoc/documentation.html new file mode 100644 index 00000000..340a6c65 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/documentation.html @@ -0,0 +1,348 @@ + + + + + + Documentation - Tiny Game Engine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

Documentation

+

+ Everything you need to build games with Tiny. Tutorials, API reference, and CLI commands. +

+
+ + +
+ +
+ + +
+
+
+ Learn by Doing +

Tutorials

+

Follow along and build complete games step by step.

+
+
+ +
+
+
+ + +
+
+
+ Look It Up +

API Reference

+

Complete documentation for every Lua function: graphics, sprites, sound, input, maps, and more.

+
+ + + + + +
+
+ + +
+
+
+ Command Line +

CLI Reference

+

All the commands you need to create, run, debug, and export your Tiny games.

+
+ +
+
+ + +
+ +
+ +
+ + + + +
+ + + + diff --git a/tiny-doc/src/docs/asciidoc/guide.adoc b/tiny-doc/src/docs/asciidoc/guide.adoc new file mode 100644 index 00000000..bc179aad --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/guide.adoc @@ -0,0 +1,91 @@ +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Tiny game engine documentation - installation guide, tutorial, API reference, showcase, and more. +:keywords: game engine, lua, virtual console, fantasy console, retro games, pixel art, indie game development, kotlin multiplatform, game jam, hot reload +:author: Tiny Game Engine Contributors + += Tiny Documentation + +== Tiny Engine + +Tiny is a free, open-source **fantasy console** and **lightweight game engine** for creating retro-style games using **Lua scripting**. Build pixel art games with a 256-color palette, test instantly with hot reload, and export to **desktop and web** platforms. Ideal for **game jams**, rapid prototyping, and learning game development. + +Create and test your ideas quickly and effectively. Run your games on your desktop computer and export them for the web, making it easy to share your creations with others. Get started right away and see your progress in real-time, thanks to Tiny's **hot reloading** feature. + +++++ + +++++ + +NOTE: The code source of this sample is available in the https://github.com/minigdx/tiny/tree/main/tiny-samples/breakout[Tiny git repository]. + +== Tutorials + +New to Tiny? Follow these step-by-step guides: + +[cols="1,3",options="header"] +|=== +| Guide | Description + +| link:tiny-install.html[1. Getting Started] +| Install the Tiny CLI, create your first project, and learn the game loop basics. + +| link:tiny-tutorial.html[2. Build a Pong Game] +| Learn the fundamentals by building a classic Pong game. Covers input handling, collision detection, and game state. + +| link:tiny-tutorial-export.html[3. Exporting Your Game] +| Export your game for the web or desktop, and deploy it to itch.io or any static host. + +| link:tiny-tutorial-sprites.html[4. Sprites & Animation] +| Draw spritesheets, animate characters, flip sprites, and manage multiple sheets. + +| link:tiny-tutorial-maps.html[5. Maps with LDtk] +| Build game levels with the LDtk editor. Draw maps, handle entities, and implement tile-based collision. + +| link:tiny-fonts.html[6. Custom Fonts] +| Create bitmap font spritesheets, configure font banks, and give your game a unique look. + +| link:tiny-tutorial-sound.html[7. Adding Sound] +| Design chip-tune sound effects with the SFX editor and play them from your Lua code. +|=== + +== Reference + +[cols="1,3",options="header"] +|=== +| Reference | Description + +| link:api.html[API Reference] +| Complete documentation for every Lua function: graphics, sprites, sound, input, maps, and more. + +| link:tiny-cli.html[CLI Commands] +| Command-line tools for creating, running, debugging, and exporting your games. +|=== + +== Try It Online + +You can try creating a game right away with `Tiny` using link:editor.html?game=[the Editor], or you can browse the link:showcase.html[Showcase] to see what others have built. + +== Open Source + +`Tiny` is an open-source project. Users can contribute to the project by reporting issues, suggesting improvements, and even submitting code changes. https://github.com/minigdx/tiny[Check the code source on Github]. + +Contributions from the community are welcome, and can help to improve the overall functionality and usability of the game engine! + +A presentation about the technologies used behind Tiny was also given during the conference https://2024.droidkaigi.jp/en/timetable/683368/[DroidKaigi 2024 @ Tokyo]. You can check https://speakerdeck.com/dwursteisen/crafting-cross-platform-adventures-building-a-game-engine-with-kotlin-multiplatform[the slides], or you also https://www.youtube.com/watch?v=4_i_Xp96IMM[watch the session]. + +image:sample/droidkaigi-tiny-export.gif[DroidKaigi 2024 presentation - Building a game engine with Kotlin Multiplatform,link=https://speakerdeck.com/dwursteisen/crafting-cross-platform-adventures-building-a-game-engine-with-kotlin-multiplatform] + +TIP: Want to help make this documentation even better? Feel free to contribute by updating https://github.com/minigdx/tiny/tree/main/tiny-doc/src/docs/asciidoc[the documentation source code]! + +== Links + +- https://tomhalligan.substack.com/p/tinkering-with-tiny[Tinkering with Tiny] +- https://tomhalligan.substack.com/p/tiny-gardening[Tiny Gardening] +- https://github.com/minigdx/tiny[GitHub Repository] +- https://github.com/minigdx/tiny/releases[Download Latest Release] + +include::licences.adoc[] diff --git a/tiny-doc/src/docs/asciidoc/index.adoc b/tiny-doc/src/docs/asciidoc/index.adoc deleted file mode 100644 index a68110cf..00000000 --- a/tiny-doc/src/docs/asciidoc/index.adoc +++ /dev/null @@ -1,76 +0,0 @@ -:docinfo: shared -:toc: left -:toclevels: 5 -:icons: font -:book: -:source-highlighter: rouge -:favicon: ./sample/favicon.png -:stylesheet: adoc-riak.css - -= Tiny 🧸 - -Welcome to the documentation for `🧸 Tiny`, a virtual console that makes building games and applications simple and fun! With Lua programming support, hot reloading, and a 256-color palette, you'll have everything you need to bring your ideas to life. - -== Tiny Engine - -`🧸 Tiny` is designed to help you create and test your ideas quickly and effectively. You can run your games on your desktop computer and export them for the web, making it easy to share your creations with others. - -With `🧸 Tiny`, you'll be able to get started right away and see your progress in real-time, thanks to its hot reloading feature. This documentation will guide you through the setup and usage of `🧸 Tiny` as well as provide you with helpful tips and tricks to make the most out of this powerful tool. - -> _Let's get started and unleash your creativity!_ - -++++ - -++++ - -NOTE: The code source of this sample is available in the https://github.com/minigdx/tiny/tree/main/tiny-sample[Tiny git repository]. - - -== Quick Navigation - -New to Tiny? Start here: - - 1. **<<_tiny_install,Installation>>** - Get Tiny up and running - 2. **<<_tiny_tutorial,Tutorial>>** - Build a complete Pong game - 3. **<<_tiny_api,API Reference>>** - Complete function documentation - 4. **<<_tiny_cli_commands,CLI Commands>>** - Command-line tools - -=== Resources - - https://github.com/minigdx/tiny[GitHub Repository] - - https://github.com/minigdx/tiny/releases[Download Latest Release] - -== Tiny Playground - -You can try creating a game right away with `🧸 Tiny` using link:playground.html?game=[the Playground], or you can experiment by updating the examples available on this page. - -== Tiny is open source - -`🧸 Tiny` is an open-source project. Users can contribute to the project by reporting issues, suggesting improvements, and even submitting code changes. https://github.com/minigdx/tiny[Check the code source on Github]. - -Contributions from the community are welcome, and can help to improve the overall functionality and usability of the game engine! - -A presentation about the technologies used behind 🧸 Tiny was also given during the conference https://2024.droidkaigi.jp/en/timetable/683368/[DroidKaigi 2024 @ 東京 Tokyo]. You can check https://speakerdeck.com/dwursteisen/crafting-cross-platform-adventures-building-a-game-engine-with-kotlin-multiplatform[the slides], or you also https://www.youtube.com/watch?v=4_i_Xp96IMM[watch the session]. - -image:sample/droidkaigi-tiny-export.gif[https://speakerdeck.com/dwursteisen/crafting-cross-platform-adventures-building-a-game-engine-with-kotlin-multiplatform -] - -TIP: Want to help make this documentation even better? Feel free to contribute by updating https://github.com/minigdx/tiny/tree/main/tiny-doc/src/docs/asciidoc[the documentation source code]! - -include::tiny-install.adoc[] - -include::tiny-tutorial.adoc[] - -include::tiny-showcase.adoc[] - -include::dependencies/tiny-cli-commands.adoc[] - -include::tiny-sfx-editor.adoc[] - -include::dependencies/tiny-api.adoc[] - -== Links - -- https://tomhalligan.substack.com/p/tinkering-with-tiny[Tinkering with Tiny] -- https://tomhalligan.substack.com/p/tiny-gardening[Tiny Gardening] - -include::licences.adoc[] diff --git a/tiny-doc/src/docs/asciidoc/index.html b/tiny-doc/src/docs/asciidoc/index.html new file mode 100644 index 00000000..02b64135 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/index.html @@ -0,0 +1,447 @@ + + + + + + Tiny - Lightweight Lua Game Engine & Virtual Console + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+

🧸 tiny

+

Build, Tweak, and Play.

+ +
+
+
+ + +
+
+
+ Core Features +

Designed for Playful Creation

+

+ A tiny virtual console for building pixel art games. Hot-reload, 256 colors, Lua scripting, and export to desktop or web. +

+
+
+
+
+ bolt +
+

Hot Reload

+

See your changes instantly. Edit your Lua scripts and watch the game update in real-time without restarting.

+
+
+
+ code +
+

Lua Scripting

+

Write game logic in Lua — a simple, powerful language perfect for beginners and veterans alike.

+
+
+
+ public +
+

Desktop & Web

+

Build once, run everywhere. Export your games for JVM desktop or WebGL browser — from a single codebase.

+
+
+
+
+ + +
+
+
+ +
+
+ + + +
+
+
+ + + + game.lua +
+
+ +
+
function _draw()
+    -- clear the screen
+    gfx.cls(1)
+
+    -- draw shapes
+    shape.rectf(10, 10, 50, 30, 8)
+    shape.circlef(128, 128, 24, 12)
+
+    -- print text
+    print("hello tiny!", 80, 200, 7)
+end
+
+ +
+
function _init()
+    player = { x = 128, y = 128 }
+end
+
+function _update()
+    if ctrl.pressing(keys.right) then
+        player.x = player.x + 2
+    end
+end
+
+function _draw()
+    gfx.cls(1)
+    spr.draw(0, player.x, player.y)
+end
+
+ +
+
function _update()
+    if ctrl.pressed(keys.space) then
+        sfx.play(0)
+    end
+end
+
+function _draw()
+    gfx.cls(2)
+    print("press space!", 80, 120, 7)
+end
+
+
+
+
+ +
+ +

Lua Scripting

+

+ Use Lua to bring your worlds to life. A lightweight, easy-to-learn language with a clean API for sprites, input, maps, and sound. Writing game logic feels like sketching on paper. +

+
+
+
+
+ + +
+
+
+ +
+ +

Hot Reload Everything

+

+ Edit your scripts while the game is running. Change colors, tweak physics, rewrite behaviors — your game state stays intact, so you never lose your flow. +

+
+ +
+
+
+ + + + enemy.lua +
+
+
function _update()
+    -- tweak speed while playing!
+    enemy.speed = 3
+
+    if enemy.x < player.x then
+        enemy.x = enemy.x + enemy.speed
+    end
+end
+
+function _draw()
+    spr.draw(1, enemy.x, enemy.y)
+end
+
+
+
+
+ refresh + Live Reloading +
+
+
+
+
+
+ + +
+
+
+
+

Ready to Play?

+

+ Start building your retro-style game right now, directly in your browser. No installation required. +

+ +
+
+ +
+ + + + +
+ + + + + diff --git a/tiny-doc/src/docs/asciidoc/playground.adoc b/tiny-doc/src/docs/asciidoc/playground.adoc index 0f41565f..7ad17ad8 100644 --- a/tiny-doc/src/docs/asciidoc/playground.adoc +++ b/tiny-doc/src/docs/asciidoc/playground.adoc @@ -3,35 +3,15 @@ :book: :source-highlighter: rouge :favicon: ./sample/favicon.png +:description: Try the Tiny game engine directly in your browser. Interactive playground with code examples for Lua game development. +:keywords: lua game playground, try lua game engine online, browser game editor -= Tiny Playground 🧸 - -Welcome to the Tiny Playground! This is your interactive sandbox where you can experiment with `🧸 Tiny` right in your browser. Try out the examples below, modify the code, and see your changes in real-time. It's the perfect place to learn and explore what's possible with Tiny! - -TIP: Need help? xref:index.adoc[Check out the documentation]! += Tiny Game Engine - Playground ++++ - -function _init() - -end - -function _update() - -end - -function _draw() - -end - + +

Redirecting to the Editor...

++++ - -== Examples - -- All examples from xref:index.adoc[the documentation] can be tweaked in this sandbox. -- link:playground.html?game=CiAgCmZ1bmN0aW9uIF9kcmF3KCkKICAgIGdmeC5jbHMoKQoKICAgIHByaW50KCJSZWN0YW5nbGUgbGltaXQiLCA2NCwgNjQgLSA1KQogICAgLS0gZHJhdyB0aGUgYm9yZGVyIGxpbWl0LgogICAgZ2Z4LmRpdGhlcigweEE1QTUpCiAgICBzaGFwZS5yZWN0KDY0LCA2NCwgMjU2IC0gMTI4LCAyNTYgLSAxMjgsIDIpCiAgICBnZnguZGl0aGVyKCkKCiAgICAtLSBkcmF3IHRoZSBtb3VzZSBwb3NpdGlvbi4KICAgIGxvY2FsIHBvcyA9IGN0cmwudG91Y2goKQogICAgc2hhcGUubGluZShwb3MueCAtIDIsIHBvcy55LCBwb3MueCArIDIsIHBvcy55LCAzKQogICAgc2hhcGUubGluZShwb3MueCwgcG9zLnkgLSAyLCBwb3MueCwgcG9zLnkgKyAyLCAzKQoKICAgIC0tIGRyYXcgdGhlIHJlY3RhbmdsZSB0aGF0IGlzIHN0YXlpbmcgd2l0aGluIHRoZSBib3JkZXIgbGltaXQuCiAgICBsb2NhbCB4ID0gbWF0aC5jbGFtcCg2NCwgcG9zLnggLSA0LCAyNTYgLSA2NCAtIDkpCiAgICBsb2NhbCB5ID0gbWF0aC5jbGFtcCg2NCwgcG9zLnkgLSA0LCAyNTYgLSA2NCAtIDkpCiAgICBzaGFwZS5yZWN0KHgsIHksIDksIDksIDgpCmVuZAo=[Tracking the player mouse] -- link:playground.html?game=ZnVuY3Rpb24gX2luaXQoKQogICAgbGV0dGVycyA9IHsiaCIsICJlIiwgImwiLCAibCIsICJvIiwgIiAiLCAidCIsICJpIiwgIm4iLCAieSIsICIhIn0KICAgIHN0YXJ0eCA9ICgyNTYgLSAoI2xldHRlcnMgKiA1KSkgKiAwLjUKZW5kCgpmdW5jdGlvbiBfZHJhdygpCiAgICBnZnguY2xzKCkKICAgIGZvciBpLCBsIGluIGlwYWlycyhsZXR0ZXJzKSBkbwogICAgICAgIHByaW50KAogICAgICAgICAgICBsLCAtLSBsZXR0ZXIgdG8gcHJpbnQKICAgICAgICAgICAgc3RhcnR4ICsgaSAqIDUsIC0tIHggcG9zaXRpb24gcmVnYXJkaW5nIHRoZSBsZXR0ZXIgaW5kZXguCiAgICAgICAgICAgIDEyOCArIG1hdGguY29zKHRpbnkudCAqIDMgKyBpKSAqIDgsIC0tIHkgcG9zaXRpb24gcmVnYXJkaW5nIHRoZSB0aW1lIGVsYXBzZWQgYW5kIHRoZSBsZXR0ZXIgaW5kZXggCiAgICAgICAgICAgIG1hdGguY2VpbCh0aW55LmZyYW1lICogMC4yIC0gaSkgLS0gY29sb3Igb2YgdGhlIGxldHRlciByZWdhcmRpbmcgdGhlIGluZGV4CiAgICAgICAgKQogICAgZW5kCmVuZA==[Waving letters] -- link:playground.html?game=LS0gRnVuY3Rpb24gdG8gY2hlY2sgY29sbGlzaW9uIGJldHdlZW4gdHdvIHJlY3RhbmdsZXMKZnVuY3Rpb24gY2hlY2tDb2xsaXNpb24ocmVjdDEsIHJlY3QyKQogICAgcmV0dXJuIHJlY3QxLnggPCByZWN0Mi54ICsgcmVjdDIud2lkdGggYW5kCiAgICAgICAgICAgcmVjdDEueCArIHJlY3QxLndpZHRoID4gcmVjdDIueCBhbmQKICAgICAgICAgICByZWN0MS55IDwgcmVjdDIueSArIHJlY3QyLmhlaWdodCBhbmQKICAgICAgICAgICByZWN0MS55ICsgcmVjdDEuaGVpZ2h0ID4gcmVjdDIueQplbmQKCi0tIEZ1bmN0aW9uIHRvIHJlc29sdmUgY29sbGlzaW9uIGFuZCBzbGlkZQpmdW5jdGlvbiBjb2xsaWRlQW5kU2xpZGUob2JqZWN0LCBvYnN0YWNsZSkKICAgIGlmIGNoZWNrQ29sbGlzaW9uKG9iamVjdCwgb2JzdGFjbGUpIHRoZW4KICAgICAgICBsb2NhbCBvdmVybGFwWCA9IG1hdGgubWluKG9iamVjdC54ICsgb2JqZWN0LndpZHRoLCBvYnN0YWNsZS54ICsgb2JzdGFjbGUud2lkdGgpIC0gbWF0aC5tYXgob2JqZWN0LngsIG9ic3RhY2xlLngpCiAgICAgICAgbG9jYWwgb3ZlcmxhcFkgPSBtYXRoLm1pbihvYmplY3QueSArIG9iamVjdC5oZWlnaHQsIG9ic3RhY2xlLnkgKyBvYnN0YWNsZS5oZWlnaHQpIC0gbWF0aC5tYXgob2JqZWN0LnksIG9ic3RhY2xlLnkpCgogICAgICAgIGlmIG92ZXJsYXBYIDwgb3ZlcmxhcFkgdGhlbgogICAgICAgICAgICBpZiBvYmplY3QueCA8IG9ic3RhY2xlLnggdGhlbgogICAgICAgICAgICAgICAgb2JqZWN0LnggPSBvYmplY3QueCAtIG92ZXJsYXBYCiAgICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgIG9iamVjdC54ID0gb2JqZWN0LnggKyBvdmVybGFwWAogICAgICAgICAgICBlbmQKICAgICAgICBlbHNlCiAgICAgICAgICAgIGlmIG9iamVjdC55IDwgb2JzdGFjbGUueSB0aGVuCiAgICAgICAgICAgICAgICBvYmplY3QueSA9IG9iamVjdC55IC0gb3ZlcmxhcFkKICAgICAgICAgICAgZWxzZQogICAgICAgICAgICAgICAgb2JqZWN0LnkgPSBvYmplY3QueSArIG92ZXJsYXBZCiAgICAgICAgICAgIGVuZAogICAgICAgIGVuZAogICAgZW5kCmVuZAoKcGxheWVyID0ge3ggPSA1MCwgeSA9IDUwLCB3aWR0aCA9IDIwLCBoZWlnaHQgPSAyMH0Kb2JzdGFjbGVzID0gewogICB7eCA9IDYwLCB5ID0gNDAsIHdpZHRoID0gMzAsIGhlaWdodCA9IDMwfSwKICB7eCA9IDY1LCB5ID0gNjAsIHdpZHRoID0gNDAsIGhlaWdodCA9IDMwfQp9CgpmdW5jdGlvbiBfdXBkYXRlKCkKICAgIGlmKGN0cmwucHJlc3Npbmcoa2V5cy5sZWZ0KSkgdGhlbgogICAgICAgICBwbGF5ZXIueCA9IHBsYXllci54IC0gMQogICAgZWxzZWlmIChjdHJsLnByZXNzaW5nKGtleXMucmlnaHQpKSB0aGVuCiAgICAgICAgIHBsYXllci54ID0gcGxheWVyLnggKyAxCiAgICBlbmQKCiAgICBpZihjdHJsLnByZXNzaW5nKGtleXMudXApKSB0aGVuCiAgICAgICAgIHBsYXllci55ID0gcGxheWVyLnkgLSAxCiAgICBlbHNlaWYgKGN0cmwucHJlc3Npbmcoa2V5cy5kb3duKSkgdGhlbgogICAgICAgICBwbGF5ZXIueSA9IHBsYXllci55ICsgMQogICAgZW5kCgogICAgZm9yIG9ic3RhY2xlIGluIGFsbChvYnN0YWNsZXMpIGRvCiAgICAgICBjb2xsaWRlQW5kU2xpZGUocGxheWVyLCBvYnN0YWNsZSkKICAgIGVuZAplbmQKCgpmdW5jdGlvbiBfZHJhdygpCiAgICBnZnguY2xzKCkKICAgIGZvciBvYnN0YWNsZSBpbiBhbGwob2JzdGFjbGVzKSBkbwogICAgICAgIHNoYXBlLnJlY3Qob2JzdGFjbGUueCwgb2JzdGFjbGUueSwgb2JzdGFjbGUud2lkdGgsIG9ic3RhY2xlLmhlaWdodCwgOSkKICAgIGVuZAoKICAgIHNoYXBlLnJlY3QocGxheWVyLngsIHBsYXllci55LCBwbGF5ZXIud2lkdGgsIHBsYXllci5oZWlnaHQsIDgpCmVuZA==[AABB Collision] -- link:playground.html?game=LS0gVXBkYXRlIHRoZSBjb2RlIHRvIHVwZGF0ZSB0aGUgZ2FtZSEKbG9jYWwgUGxheWVyID0gewogICB4ID0gMTI4IC0gMTYsCiAgIHkgPSAxMjggLSAxNiwKfQpmdW5jdGlvbiBfaW5pdCgpCiAgcGxheWVyID0gbmV3KFBsYXllcikKZW5kCgpmdW5jdGlvbiBfdXBkYXRlKCkKICBpZiBjdHJsLnByZXNzaW5nKGtleXMubGVmdCkgdGhlbgogICAgIHBsYXllci54ID0gbWF0aC5tYXgoMCwgcGxheWVyLnggLSAxKQogIGVsc2VpZiBjdHJsLnByZXNzaW5nKGtleXMucmlnaHQpIHRoZW4KICAgICAgcGxheWVyLnggPSBtYXRoLm1pbigyNDAsIHBsYXllci54ICsgMSkKICBlbmQKCmlmIGN0cmwucHJlc3Npbmcoa2V5cy51cCkgdGhlbgogICAgIHBsYXllci55ID0gbWF0aC5tYXgoMCwgcGxheWVyLnkgLSAxKQogIGVsc2VpZiBjdHJsLnByZXNzaW5nKGtleXMuZG93bikgdGhlbgogICAgICBwbGF5ZXIueSA9IG1hdGgubWluKDI0MCwgcGxheWVyLnkgKyAxKQogIGVuZAplbmQKCmZ1bmN0aW9uIF9kcmF3KCkKICAgZ2Z4LmNscygpCgogICBzaGFwZS5ncmFkaWVudCgwLCAwLCAyNTYsIDI1NiwgMywgNCkKCiAgIHNwci5kcmF3KDk2LCBwbGF5ZXIueCwgcGxheWVyLnkpCiAgIAplbmQK[Control sprite move] -- link:playground.html?game=bG9jYWwgUGxheWVyID0gewogIHggPSAxMjggLSAxNiwKICB5ID0gMTI4IC0gMTYsCiAgZHggPSAwLAogIGR5ID0gMAp9CiAgCmZ1bmN0aW9uIF9pbml0KCkKICAgbG9jYWwgc2VlZCA9IG1hdGgucm5kKCkKICAgcGxheWVyID0gbmV3KFBsYXllciwgewogICAgICAgZHggPSBtYXRoLmNvcyhzZWVkKSAqIDMsIAogICAgICAgZHkgPSBtYXRoLnNpbihzZWVkKSAqIDMKICAgfSkKZW5kCgpmdW5jdGlvbiBfdXBkYXRlKCkKICAgaWYgY3RybC5wcmVzc2VkKGtleXMuc3BhY2UpIHRoZW4KICAgICBfaW5pdCgpCiAgIGVuZAogICBwbGF5ZXIueCA9IHBsYXllci54ICsgcGxheWVyLmR4CiAgIHBsYXllci55ID0gcGxheWVyLnkgKyBwbGF5ZXIuZHkKICAgaWYgcGxheWVyLnggPiAyNDAgb3IgcGxheWVyLnggPCAwIHRoZW4KICAgICAgcGxheWVyLmR4ID0gcGxheWVyLmR4ICogLTEKICAgZW5kCiAgIGlmIHBsYXllci55ID4gMjQwIG9yIHBsYXllci55IDwgMCB0aGVuCiAgICAgIHBsYXllci5keSA9IHBsYXllci5keSAqIC0xCiAgIGVuZAogICBwbGF5ZXIueCA9IG1hdGgubWF4KDAsIG1hdGgubWluKHBsYXllci54LCAyNDApKQogICBwbGF5ZXIueSA9IG1hdGgubWF4KDAsIG1hdGgubWluKHBsYXllci55LCAyNDApKQplbmQKCmZ1bmN0aW9uIF9kcmF3KCkKICBnZnguY2xzKCkKICBzcHIuZHJhdygxMjAsIHBsYXllci54LCBwbGF5ZXIueSkKZW5kCg==[Bouncing Bat] -- link:playground.html?game=ZnVuY3Rpb24gX2RyYXcoKQogICAgZ2Z4LmNscygxKQoKICAgIGdmeC5kaXRoZXIoMHgwMDAxKQogICAgbG9jYWwgeCA9IG1hdGgucGVybGluKDAuMSwgMC4yLCB0aW55LmZyYW1lIC8gMTAwKQogICAgbG9jYWwgeSA9IG1hdGgucGVybGluKDAuNCwgMC41LCB0aW55LmZyYW1lIC8gMTAwKQogICAgc2hhcGUuY2lyY2xlZih4ICogMjU2LCB5ICogMjU2LCA2NCwgNykKCiAgICBsb2NhbCB4ID0gbWF0aC5wZXJsaW4oMC45LCAwLjcsIHRpbnkuZnJhbWUgLyAxMDApCiAgICBsb2NhbCB5ID0gbWF0aC5wZXJsaW4oMC4zLCAwLjEsIHRpbnkuZnJhbWUgLyAxMDApCiAgICBzaGFwZS5jaXJjbGVmKHggKiAyNTYsIHkgKiAyNTYsIDMyLCA3KQoKICAgIGxvY2FsIHggPSBtYXRoLnBlcmxpbigwLjMsIDAuMSwgdGlueS5mcmFtZSAvIDEwMCkKICAgIGxvY2FsIHkgPSBtYXRoLnBlcmxpbigwLjIsIDAuNCwgdGlueS5mcmFtZSAvIDEwMCkKICAgIHNoYXBlLmNpcmNsZWYoeCAqIDI1NiwgeSAqIDI1NiwgNTYsIDcpCgogICAgZ2Z4LmRpdGhlcigpCgogICAgZm9yIHh4ID0gNjQsIDI1NiAtIDY0LCA0IGRvCiAgICAgICAgbG9jYWwgYWRqdXN0ID0gbWF0aC5wZXJsaW4oeHggLyA2NCwgMC4xLCB0aW55LmZyYW1lIC8gNTApCiAgICAgICAgc2hhcGUuY2lyY2xlZih4eCAtIDEsIDEyNiArIGFkanVzdCAqIDgsIDgsIDcpCiAgICAgICAgc2hhcGUuY2lyY2xlZih4eCArIDEsIDEyNiArIGFkanVzdCAqIDgsIDgsIDcpCiAgICAgICAgc2hhcGUuY2lyY2xlZih4eCwgMSArIDEyNiArIGFkanVzdCAqIDgsIDgsIDcpCiAgICAgICAgc2hhcGUuY2lyY2xlZih4eCwgLTEgKyAxMjYgKyBhZGp1c3QgKiA4LCA4LCA3KQogICAgZW5kCgogICAgZm9yIHh4ID0gNjQsIDI1NiAtIDY0LCA0IGRvCiAgICAgICAgbG9jYWwgYWRqdXN0ID0gbWF0aC5wZXJsaW4oeHggLyA2NCwgMC4xLCB0aW55LmZyYW1lIC8gNTApCiAgICAgICAgc2hhcGUuY2lyY2xlZih4eCwgMTI2ICsgYWRqdXN0ICogOCwgOCwgMSkgICAgCiAgICBlbmQKCiAgICBsb2NhbCB0eHQgPSAiaGVsbG8gd29ybGQgIgogICAgbG9jYWwgbGV0dGVyID0gMQogICAgZm9yIHh4ID0gNjQsIDI1NiAtIDY0LCA0IGRvCiAgICAgICAgbG9jYWwgYWRqdXN0ID0gbWF0aC5wZXJsaW4oeHggLyA2NCwgMC4xLCB0aW55LmZyYW1lIC8gNTApCiAgICAgICAgcHJpbnQoc3RyaW5nLnN1Yih0eHQsIGxldHRlciwgbGV0dGVyKSwgMSArIHh4LCAxMjQgKyBhZGp1c3QgKiA4LCA3KQogICAgICAgIHByaW50KHN0cmluZy5zdWIodHh0LCBsZXR0ZXIsIGxldHRlciksIC0xICsgeHgsIDEyNCArIGFkanVzdCAqIDgsIDcpCiAgICAgICAgcHJpbnQoc3RyaW5nLnN1Yih0eHQsIGxldHRlciwgbGV0dGVyKSwgeHgsIDEgKyAxMjQgKyBhZGp1c3QgKiA4LCA3KQogICAgICAgIHByaW50KHN0cmluZy5zdWIodHh0LCBsZXR0ZXIsIGxldHRlciksIHh4LCAtMSArIDEyNCArIGFkanVzdCAqIDgsIDcpCgogICAgICAgIHByaW50KHN0cmluZy5zdWIodHh0LCBsZXR0ZXIsIGxldHRlciksIHh4LCAxMjQgKyBhZGp1c3QgKiA4LCAxKQoKICAgICAgICBsZXR0ZXIgPSBtYXRoLm1heCgxLCAobGV0dGVyICsgMSkgJSAoI3R4dCArIDEpKQogICAgZW5kCmVuZAoK[Perlin and dither] diff --git a/tiny-doc/src/docs/asciidoc/robots.txt b/tiny-doc/src/docs/asciidoc/robots.txt new file mode 100644 index 00000000..141c78e6 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://minigdx.github.io/tiny/sitemap.xml diff --git a/tiny-doc/src/docs/asciidoc/sample/2026-1bit-jam-2.gif b/tiny-doc/src/docs/asciidoc/sample/2026-1bit-jam-2.gif new file mode 100644 index 00000000..1f9c26b4 Binary files /dev/null and b/tiny-doc/src/docs/asciidoc/sample/2026-1bit-jam-2.gif differ diff --git a/tiny-doc/src/docs/asciidoc/showcase.html b/tiny-doc/src/docs/asciidoc/showcase.html new file mode 100644 index 00000000..65ad840a --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/showcase.html @@ -0,0 +1,274 @@ + + + + + + Showcase - Tiny Game Engine + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

Showcase

+

+ Get inspired by what's possible with Tiny! Explore games created by the community. +

+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ + + + +
+ + + + diff --git a/tiny-doc/src/docs/asciidoc/showcase.json b/tiny-doc/src/docs/asciidoc/showcase.json new file mode 100644 index 00000000..10563ca5 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/showcase.json @@ -0,0 +1,62 @@ +[ + { + "title": "Camping", + "image": "sample/camping.gif", + "url": "https://dwursteisen.itch.io/trijam-camping", + "genres": ["survival"] + }, + { + "title": "Level Up", + "image": "sample/level-up.gif", + "url": "https://dwursteisen.itch.io/trijam-220-type-it", + "genres": ["typing"] + }, + { + "title": "Memory Pong", + "image": "sample/memory.gif", + "url": "https://dwursteisen.itch.io/memory-pong-trijam-251", + "genres": ["arcade"] + }, + { + "title": "Connect Me", + "image": "sample/connect_me.gif", + "url": "https://dwursteisen.itch.io/connect-me", + "genres": ["puzzle"] + }, + { + "title": "Only Three Seconds", + "image": "sample/only_three_seconds.gif", + "url": "https://dwursteisen.itch.io/one-light-for-three-seconds", + "genres": ["arcade"] + }, + { + "title": "Meiro de Maigo", + "image": "sample/meiro_de_maigo2.gif", + "url": "https://dwursteisen.itch.io/meiro-de", + "genres": ["exploration"] + }, + { + "title": "Freezming", + "image": "sample/freezming.gif", + "url": "https://dwursteisen.itch.io/freezming", + "genres": ["arcade"] + }, + { + "title": "Gravity Balls", + "image": "sample/gravity-balls.gif", + "url": "https://dwursteisen.itch.io/gravity-balls", + "genres": ["physics"] + }, + { + "title": "Reflections", + "image": "sample/reflections.gif", + "url": "https://dwursteisen.itch.io/macro-jams-06-reflections", + "genres": ["puzzle"] + }, + { + "title": "Pair of Pipes", + "image": "sample/2026-1bit-jam-2.gif", + "url": "https://dwursteisen.itch.io/pair-of-pipes", + "genres": ["puzzle"] + } +] diff --git a/tiny-doc/src/docs/asciidoc/sitemap.xml b/tiny-doc/src/docs/asciidoc/sitemap.xml new file mode 100644 index 00000000..8ded434b --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/sitemap.xml @@ -0,0 +1,63 @@ + + + + https://minigdx.github.io/tiny/index.html + monthly + 1.0 + + + https://minigdx.github.io/tiny/editor.html + monthly + 0.8 + + + https://minigdx.github.io/tiny/tiny-api.html + monthly + 0.9 + + + https://minigdx.github.io/tiny/tiny-cli.html + monthly + 0.7 + + + https://minigdx.github.io/tiny/tiny-install.html + monthly + 0.8 + + + https://minigdx.github.io/tiny/tiny-tutorial.html + monthly + 0.8 + + + https://minigdx.github.io/tiny/tiny-tutorial-export.html + monthly + 0.7 + + + https://minigdx.github.io/tiny/tiny-tutorial-sprites.html + monthly + 0.7 + + + https://minigdx.github.io/tiny/tiny-tutorial-maps.html + monthly + 0.7 + + + https://minigdx.github.io/tiny/tiny-fonts.html + monthly + 0.7 + + + https://minigdx.github.io/tiny/tiny-tutorial-sound.html + monthly + 0.7 + + + https://minigdx.github.io/tiny/tiny-showcase.html + monthly + 0.6 + + diff --git a/tiny-doc/src/docs/asciidoc/tiny-common.css b/tiny-doc/src/docs/asciidoc/tiny-common.css new file mode 100644 index 00000000..c1ea39d2 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-common.css @@ -0,0 +1,267 @@ +/* ── Reset & base ─────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --pink: #ff0080; + --cyan: #00e5ff; + --green: #a2ff00; + --yellow: #fff000; + --dark: #1e293b; + --bg: #fff5f9; +} + +body { + font-family: 'Quicksand', sans-serif; + color: #334155; + background: var(--bg); + padding-top: 60px; + background-image: + radial-gradient(circle at 10% 20%, rgba(255,0,128,.05) 0%, transparent 20%), + radial-gradient(circle at 90% 80%, rgba(0,229,255,.05) 0%, transparent 20%); + -webkit-font-smoothing: antialiased; + overflow-x: hidden; +} + +a { color: inherit; } + +.font-display { font-family: 'Lilita One', cursive; font-weight: 400; } +.font-mono { font-family: 'Source Code Pro', monospace; } + +/* ── Layout helpers ────────────────────────────── */ +.page { display: flex; flex-direction: column; min-height: 100vh; } +.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; } +.section { padding: 80px 24px; } +.section--white { background: #fff; } +.section--green { background: rgba(162,255,0,.1); } +.section--cyan { background: rgba(0,229,255,.1); } +.center-text { text-align: center; } + +/* ── Bubble shadows ────────────────────────────── */ +.shadow-bubble { box-shadow: 0 8px 0 rgba(0,0,0,.1); } +.shadow-bubble-pink { box-shadow: 0 6px 0 #d4006b; } +.shadow-bubble-blue { box-shadow: 0 6px 0 #00b8cc; } + +/* ── Buttons ───────────────────────────────────── */ +.button { + display: inline-flex; align-items: center; justify-content: center; + padding: 16px 40px; border-radius: 2rem; + font-family: 'Lilita One', cursive; font-weight: 400; + font-size: 1.5rem; text-transform: uppercase; letter-spacing: 2px; + text-decoration: none; border: 4px solid var(--dark); + transition: transform .15s, box-shadow .15s; + cursor: pointer; +} +.button:hover { transform: translateY(-2px); } +.button:active { transform: translateY(0); } + +.button--pink { background: var(--pink); color: #fff; box-shadow: 0 6px 0 #d4006b; } +.button--white { background: #fff; color: var(--cyan); box-shadow: 0 8px 0 rgba(0,0,0,.1); } +.button--cyan { background: var(--cyan); color: #fff; border-color: var(--dark); box-shadow: 0 6px 0 #00b8cc; } +.button--green { background: var(--green); color: var(--dark); border-color: var(--dark); box-shadow: 0 6px 0 #7acc00; } +.button--small { font-size: 1.1rem; padding: 12px 28px; } + +/* ── Feature cards ─────────────────────────────── */ +.feature-card { + display: flex; flex-direction: column; gap: 24px; + padding: 40px; border-radius: 2.5rem; + background: #fff; border: 4px solid var(--pink); + transition: transform .2s; + text-decoration: none; +} +.feature-card:hover { transform: rotate(2deg); } +.feature-card--cyan { border-color: var(--cyan); } +.feature-card--cyan:hover { transform: rotate(-2deg); } +.feature-card--green { border-color: var(--green); } +.feature-card--green:hover { transform: rotate(1deg); } + +.feature-card__icon { + width: 64px; height: 64px; + display: flex; align-items: center; justify-content: center; + border-radius: 1rem; color: #fff; + box-shadow: 4px 4px 0 #000; + transition: transform .2s; +} +.feature-card:hover .feature-card__icon { transform: scale(1.1); } +.feature-card__icon--pink { background: var(--pink); } +.feature-card__icon--cyan { background: var(--cyan); } +.feature-card__icon--green { background: var(--green); } + +.feature-card__title { font-size: 1.5rem; color: var(--dark); } +.feature-card__desc { color: #64748b; font-weight: 500; line-height: 1.6; } + +/* ── Section headings ─────────────────────────── */ +.section-header { margin-bottom: 48px; } +.section-badge { + display: inline-block; padding: 8px 24px; + border: 2px solid var(--pink); border-radius: 999px; + font-size: .9rem; text-transform: uppercase; letter-spacing: .2em; + color: var(--pink); background: #fff; + transform: rotate(-2deg); +} +.section-badge--cyan { border-color: var(--cyan); color: var(--cyan); } +.section-badge--green { border-color: var(--green); color: var(--green); } +.section-title { font-size: 3rem; line-height: 1.1; color: var(--dark); margin-top: 16px; } +.section-desc { font-size: 1.15rem; color: #64748b; max-width: 640px; margin: 12px auto 0; font-weight: 500; } + +/* ── Terminal code block ───────────────────────── */ +.terminal { + border: 1px solid #e0e0e0; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0,0,0,.06); +} +.terminal__header { + display: flex; align-items: center; + padding: 10px 16px; + background: #eef0f2; + border-bottom: 1px solid #e0e0e0; +} +.terminal__dot { + width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; +} +.terminal__dot--red { background: #ff5f57; } +.terminal__dot--yellow { background: #febc2e; } +.terminal__dot--green { background: #28c840; } +.terminal__filename { + margin-left: 12px; color: #888; font-size: .8rem; + font-family: 'Source Code Pro', monospace; +} +.terminal__body { + padding: 20px 24px; + background: #f8f9fa; + font-family: 'Source Code Pro', monospace; + font-size: .88rem; line-height: 1.7; color: #333; +} + +/* Syntax colors */ +.syn-kw { color: #7c3aed; } +.syn-fn { color: #2563eb; } +.syn-cm { color: #9ca3af; font-style: italic; } +.syn-st { color: #16a34a; } +.syn-nb { color: #ea580c; } + +/* ── Filter pills ─────────────────────────────── */ +.filter-bar { + display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; + padding: 0 24px 48px; + max-width: 900px; margin: 0 auto; +} +.filter-pill { + padding: 8px 24px; border-radius: 999px; + font-family: 'Quicksand', sans-serif; + font-size: .85rem; font-weight: 700; + text-transform: uppercase; letter-spacing: .1em; + border: 3px solid var(--dark); + background: #fff; color: var(--dark); + cursor: pointer; + transition: background .15s, color .15s, transform .15s; +} +.filter-pill:hover { transform: translateY(-2px); } +.filter-pill--active { + background: var(--pink); color: #fff; border-color: var(--pink); +} + +/* ── Navbar (static, matches tiny-nav.js output) ─ */ +.tiny-nav { + position: fixed; + top: 0; left: 0; right: 0; + z-index: 10000; + height: 56px; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 4px solid rgba(0, 229, 255, 0.2); + display: flex; + align-items: center; + padding: 0 24px; + font-family: 'Quicksand', sans-serif; +} +.tiny-nav__logo { + color: #1e293b; + font-family: 'Lilita One', cursive; + font-weight: 400; + font-size: 1.3em; + margin-right: auto; + white-space: nowrap; + letter-spacing: 2px; + text-decoration: none; + transition: color 0.15s ease; +} +.tiny-nav__logo:hover { color: #ff0080; } +.tiny-nav__links { + display: flex; + align-items: center; + gap: 8px; +} +.tiny-nav__link { + color: #444; + text-decoration: none; + margin: 0 12px; + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + transition: color 0.15s ease; +} +.tiny-nav__link:hover { color: #ff0080; } +.tiny-nav__cta { + background: #00e5ff; + color: #fff; + padding: 8px 20px; + border-radius: 16px; + border: 2px solid #1e293b; + font-family: 'Lilita One', cursive; + font-weight: 400; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 1px; + text-decoration: none; + box-shadow: 0 6px 0 #00b8cc; + transition: transform 0.15s ease; +} +.tiny-nav__cta:hover { + background: #00d4ec; + color: #fff; + transform: translateY(-2px); +} + +/* ── Lazy-load shimmer skeleton ───────────────── */ +.game-card__image[data-src] { + background: linear-gradient(90deg, #eef0f2 25%, #f5f6f8 50%, #eef0f2 75%); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* ── Footer ────────────────────────────────────── */ +.footer { + display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 32px; + padding: 64px 24px; + border-top: 4px solid rgba(0,0,0,.05); + max-width: 1200px; margin: 0 auto; + color: #94a3b8; font-weight: 500; +} +.footer__brand { display: flex; align-items: center; gap: 12px; } +.footer__brand-text { font-size: 1.5rem; letter-spacing: 2px; color: var(--dark); } +.footer__links { display: flex; gap: 48px; } +.footer__link { + font-size: .85rem; font-weight: 700; text-transform: uppercase; + letter-spacing: 2px; text-decoration: none; color: #94a3b8; + transition: color .15s; +} +.footer__link:hover { color: var(--pink); } +.footer__copy { font-size: .85rem; font-weight: 700; opacity: .6; } + +/* ── Responsive ────────────────────────────────── */ +@media (max-width: 960px) { + .tiny-nav__links { display: none; } + .section-title { font-size: 2.5rem; } +} +@media (max-width: 600px) { + .button { font-size: 1.2rem; padding: 14px 28px; } + .footer { flex-direction: column; text-align: center; } + .footer__links { flex-direction: column; gap: 16px; } +} diff --git a/tiny-doc/src/docs/asciidoc/tiny-fonts.adoc b/tiny-doc/src/docs/asciidoc/tiny-fonts.adoc new file mode 100644 index 00000000..91f4f518 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-fonts.adoc @@ -0,0 +1,149 @@ +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Add custom bitmap fonts to your Tiny game. Learn how to create font spritesheets, configure font banks, and use the CLI to register fonts. +:keywords: tiny fonts, custom fonts, bitmap fonts, pixel font, game font, tiny game engine, font spritesheet + += Managing Custom Fonts + +`Tiny` comes with a built-in default font, but you can also use your own custom fonts to give your game a unique look. +Custom fonts are bitmap fonts: each character is drawn as a small sprite on a PNG spritesheet, arranged in a grid. + +== What is a font? + +A font in Tiny is a PNG image (the spritesheet) combined with metadata that describes how to read characters from it. +Each font has a **name**, a reference to its **spritesheet** image, and one or more **banks** of characters. + +== What is a font bank? + +A font bank is a set of characters within a font that share the same cell size. +Each bank defines: + +- A **name** to identify the bank (e.g., `default`, `uppercase`, `symbols`). +- The **width** and **height** of each character cell, in pixels. +- A **characters** list describing which characters are in each row of the grid. +- An optional **x** and **y** offset where the character grid begins in the spritesheet. + +A single font can have multiple banks. +This is useful when you want to group different character sets -- for example, one bank for Latin letters and another for punctuation -- while keeping them under the same font name. + +== How the character grid works + +The characters in a bank are arranged left-to-right, top-to-bottom in the spritesheet. +Tiny uses the image width and the character cell width to determine how many characters fit per row. + +For example, with an image that is 108 pixels wide and a character cell width of 6 pixels, each row contains 18 characters. +If you provide 144 characters, they fill 8 rows of 18 characters. + +== Adding a font with the CLI + +Use the `tiny-cli add` command with the `--font` and `--chars` options to register a PNG as a font. + +```bash +tiny-cli add --font=myfont --chars "abcdefghijklmnopqrstuvwxyz0123456789" myfont.png +``` + +- `--font` marks the PNG as a font instead of a spritesheet. You can optionally give the font a name (e.g., `--font=myfont`). If you omit the name (`--font`), it is derived from the filename. +- `--chars` lists all the characters in the spritesheet, in reading order (left-to-right, top-to-bottom). +- `--size` specifies the character cell size in pixels. Use `WxH` for rectangular cells (e.g., `6x12`) or a single number for square cells (e.g., `8`). **Optional**: if omitted, the size is auto-detected from the image. +- `--offset` specifies the pixel offset where the character grid begins in the image (e.g., `8,12` or `8x12`). **Optional**: if omitted, the offset is auto-detected from the image. + +=== Auto-detection + +When `--size` or `--offset` are omitted, Tiny analyzes the PNG image to detect the values automatically: + +1. **Bounding box detection**: Scans all pixels to find the region containing non-transparent pixels. +2. **Gap analysis**: Looks for fully-transparent columns and rows within the bounding box to identify individual character cells. +3. **Divisor fallback**: If gap analysis fails, tries to find cell dimensions that evenly divide the bounding box and can fit all characters. + +The auto-detected values are displayed during the add operation. If auto-detection fails, an error message asks you to provide `--size` explicitly. + +=== Explicit size and offset + +You can always provide explicit values to override auto-detection: + +```bash +tiny-cli add --font=myfont --size 8x12 --chars "abcdefghijklmnopqrstuvwxyz0123456789" myfont.png +tiny-cli add --font=myfont --size 8x12 --offset 4,2 --chars "abcdefghijklmnopqrstuvwxyz0123456789" myfont.png +``` + +=== Adding multiple banks + +To add a second bank to an existing font, run the command again with the same font name but different characters: + +```bash +tiny-cli add --font=myfont --size 8x12 --chars "ABCDEFGHIJKLMNOPQRSTUVWXYZ" myfont-uppercase.png +``` + +Both banks are stored under the same font name `myfont`. + +=== Creating separate fonts + +To create distinct fonts, use different names: + +```bash +tiny-cli add --font=big --size 16x16 --chars "abcdefghijklmnopqrstuvwxyz" big-font.png +tiny-cli add --font=small --size 6x8 --chars "abcdefghijklmnopqrstuvwxyz" small-font.png +``` + +== Configuration in _tiny.json + +When you add a font, it is stored in the `fonts` section of `_tiny.json`: + +```json +{ + "fonts": [ + { + "name": "myfont", + "spritesheet": "myfont.png", + "banks": [ + { + "name": "default", + "width": 8, + "height": 12, + "x": 0, + "y": 0, + "characters": [ + "abcdefgh", + "ijklmnop", + "qrstuvwx", + "yz012345", + "6789" + ] + } + ] + } + ] +} +``` + +The `characters` array contains one string per row. +Each string has as many characters as fit in one row of the spritesheet (image width / character width). + +The `x` and `y` fields specify the pixel offset where the character grid starts in the spritesheet. They default to `0` when not specified. + +== Managing fonts + +You can view all resources, including fonts, with: + +```bash +tiny-cli resources +``` + +To remove a font, use `--delete` with the font name or spritesheet filename: + +```bash +tiny-cli resources --delete myfont.png +``` + +== What's next? + +Want to add audio to your game? Continue with the link:tiny-tutorial-sound.html[Adding Sound to Your Game] tutorial. + +You can also explore: + +- link:api.html[API Reference] -- Complete documentation for every Lua function. +- link:tiny-cli.html[CLI Reference] -- All available command-line tools. diff --git a/tiny-doc/src/docs/asciidoc/tiny-install.adoc b/tiny-doc/src/docs/asciidoc/tiny-install.adoc index a5203533..b59f7248 100644 --- a/tiny-doc/src/docs/asciidoc/tiny-install.adoc +++ b/tiny-doc/src/docs/asciidoc/tiny-install.adoc @@ -1,10 +1,156 @@ -== Tiny Install +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Install the Tiny game engine CLI and create your first Lua game project. Step-by-step setup for macOS, Linux, and Windows. +:keywords: tiny install, game engine setup, lua game engine, tiny cli install, getting started, game development -`🧸 Tiny` is a game engine that runs through its command-line client. -Once installed, you can start creating and developing games in no time! += Getting Started with Tiny -- Download Tiny CLI from https://github.com/minigdx/tiny/releases[the release Github page] -- Unzip it and put the `bin` directory in your path (i.e.: `export PATH=$PATH:/bin`) -- Create your first game using `tiny-cli create my-first-game` +`Tiny` is a free, open-source game engine that runs through its command-line client. +Once installed, you can start creating and developing retro-style games in no time! -What's next? You can check the <<_tiny_tutorial,Tiny Tutorial>>, <<_tiny_api,Tiny API>> or get more information about the <<_tiny_cli_commands,Tiny CLI>>. +== Prerequisites + +Before installing Tiny, make sure you have: + +- **Java 21** or later installed on your system. You can download it from https://adoptium.net/[Adoptium] or use your system's package manager. + +To verify your Java installation, run: + +```bash +java -version +``` + +You should see output indicating Java 21 or higher. + +== Download Tiny CLI + +Download the latest release of the Tiny CLI from the https://github.com/minigdx/tiny/releases[GitHub releases page]. + +Choose the archive for your platform and extract it to a location of your choice. + +== Add to your PATH + +To use the `tiny-cli` command from anywhere, add the `bin` directory to your system PATH. + +**macOS / Linux:** + +```bash +export PATH=$PATH:/path/to/tiny-cli/bin +``` + +To make this permanent, add the line to your shell profile (`~/.bashrc`, `~/.zshrc`, or `~/.profile`). + +**Windows:** + +Add the `bin` directory to your PATH through *System Properties > Environment Variables*, or run in PowerShell: + +```powershell +$env:PATH += ";C:\path\to\tiny-cli\bin" +``` + +== Verify the installation + +Run the following command to confirm Tiny CLI is installed correctly: + +```bash +tiny-cli --help +``` + +You should see the list of available commands. + +== Create your first project + +Generate a new game project with: + +```bash +tiny-cli create my-first-game +``` + +This creates a `my-first-game` directory with the following structure: + +``` +my-first-game/ + _tiny.json <-- Game configuration (screen size, colors, resources) + game.lua <-- Your game code +``` + +- **`_tiny.json`**: Defines your game's settings such as screen resolution, color palette, spritesheets, levels, sounds, and fonts. +- **`game.lua`**: The main script where you write your game logic using Lua. + +== Run your game + +Navigate into the project directory and start the game: + +```bash +cd my-first-game +tiny-cli run +``` + +A window opens displaying your game. You can edit `game.lua` while the game is running -- Tiny's **hot reload** feature picks up your changes instantly. + +== Game structure basics + +Every Tiny game uses three callback functions in Lua: + +```lua +function _init() + -- Called once when the game starts. + -- Initialize your variables here. +end + +function _update() + -- Called every frame before drawing. + -- Update game logic here. +end + +function _draw() + -- Called every frame after update. + -- Draw your game here. + gfx.cls() + print("hello tiny!", 80, 120, 7) +end +``` + +++++ + +function _init() + -- Called once when the game starts. + x = 128 + y = 128 + color = 8 +end + +function _update() + -- Move the circle with arrow keys + if ctrl.pressing(keys.left) then x = x - 2 end + if ctrl.pressing(keys.right) then x = x + 2 end + if ctrl.pressing(keys.up) then y = y - 2 end + if ctrl.pressing(keys.down) then y = y + 2 end + + -- Change color on space + if ctrl.pressed(keys.space) then + color = color + 1 + if color > 15 then color = 1 end + end +end + +function _draw() + gfx.cls() + shape.circlef(x, y, 16, color) + print("arrows: move / space: color", 30, 8, 7) +end + +++++ + +== What's next? + +Now that you have Tiny installed, follow the link:tiny-tutorial.html[Build Your First Game: Pong] tutorial to learn the engine fundamentals by creating a complete game. + +You can also explore: + +- link:api.html[API Reference] -- Complete documentation for every Lua function. +- link:tiny-cli.html[CLI Reference] -- All available command-line tools. diff --git a/tiny-doc/src/docs/asciidoc/tiny-nav.js b/tiny-doc/src/docs/asciidoc/tiny-nav.js new file mode 100644 index 00000000..e868d43a --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-nav.js @@ -0,0 +1,91 @@ +(function () { + if (document.querySelector('.tiny-nav')) return; + + // Inject CSS + var style = document.createElement('style'); + style.textContent = [ + '.tiny-nav {', + ' position: fixed;', + ' top: 0; left: 0; right: 0;', + ' z-index: 10000;', + ' height: 56px;', + ' background: rgba(255, 255, 255, 0.8);', + ' backdrop-filter: blur(12px);', + ' -webkit-backdrop-filter: blur(12px);', + ' border-bottom: 4px solid rgba(0, 229, 255, 0.2);', + ' display: flex;', + ' align-items: center;', + ' padding: 0 24px;', + ' font-family: "Quicksand", sans-serif;', + '}', + '.tiny-nav__logo, .tiny-nav__logo:link, .tiny-nav__logo:visited {', + ' color: #1e293b !important;', + ' font-family: "Lilita One", cursive;', + ' font-weight: 400;', + ' font-size: 1.3em;', + ' margin-right: auto;', + ' white-space: nowrap;', + ' letter-spacing: 2px;', + ' text-decoration: none !important;', + ' transition: color 0.15s ease;', + '}', + '.tiny-nav__logo:hover { color: #ff0080 !important; }', + '.tiny-nav__links {', + ' display: flex;', + ' align-items: center;', + ' gap: 8px;', + '}', + '.tiny-nav__link, .tiny-nav__link:link, .tiny-nav__link:visited {', + ' color: #444 !important;', + ' text-decoration: none !important;', + ' margin: 0 12px;', + ' font-size: 0.85rem;', + ' font-weight: 700;', + ' text-transform: uppercase;', + ' letter-spacing: 1px;', + ' transition: color 0.15s ease;', + '}', + '.tiny-nav__link:hover { color: #ff0080 !important; }', + '.tiny-nav__cta, .tiny-nav__cta:link, .tiny-nav__cta:visited {', + ' background: #00e5ff;', + ' color: #fff !important;', + ' padding: 8px 20px;', + ' border-radius: 16px;', + ' border: 2px solid #1e293b;', + ' font-family: "Lilita One", cursive;', + ' font-weight: 400;', + ' font-size: 0.85rem;', + ' text-transform: uppercase;', + ' letter-spacing: 1px;', + ' text-decoration: none !important;', + ' box-shadow: 0 6px 0 #00b8cc;', + ' transition: transform 0.15s ease;', + '}', + '.tiny-nav__cta:hover, .tiny-nav__cta:focus {', + ' background: #00d4ec;', + ' color: #fff !important;', + ' transform: translateY(-2px);', + '}', + '@media (max-width: 960px) {', + ' .tiny-nav__links { display: none; }', + '}' + ].join('\n'); + document.head.appendChild(style); + + // Inject HTML + var nav = document.createElement('nav'); + nav.className = 'tiny-nav'; + nav.innerHTML = [ + '', + '' + ].join('\n'); + document.body.insertBefore(nav, document.body.firstChild); +})(); diff --git a/tiny-doc/src/docs/asciidoc/tiny-sfx-editor.adoc b/tiny-doc/src/docs/asciidoc/tiny-sfx-editor.adoc deleted file mode 100644 index f8e2991a..00000000 --- a/tiny-doc/src/docs/asciidoc/tiny-sfx-editor.adoc +++ /dev/null @@ -1,11 +0,0 @@ -== Tiny Sfx Editor - -`🧸 Tiny` is bundle with a small sfx editor, that you can start using the command `tiny-cli sfx`, within your game directory. - -This editor can design an instrument and the notes played by this instrument, that generate a sfx. - -The editor can be tested below: - -++++ - -++++ diff --git a/tiny-doc/src/docs/asciidoc/tiny-showcase.adoc b/tiny-doc/src/docs/asciidoc/tiny-showcase.adoc index 3fe1a61a..c24b1910 100644 --- a/tiny-doc/src/docs/asciidoc/tiny-showcase.adoc +++ b/tiny-doc/src/docs/asciidoc/tiny-showcase.adoc @@ -4,15 +4,16 @@ Get inspired by what's possible with `🧸 Tiny`! This showcase features amazing Here are a few examples of games created using `🧸 Tiny`. -image:sample/camping.gif[link=https://dwursteisen.itch.io/trijam-camping] -image:sample/level-up.gif[link=https://dwursteisen.itch.io/trijam-220-type-it] -image:sample/memory.gif[link=https://dwursteisen.itch.io/memory-pong-trijam-251] -image:sample/connect_me.gif[link=https://dwursteisen.itch.io/connect-me] -image:sample/only_three_seconds.gif[link=https://dwursteisen.itch.io/one-light-for-three-seconds] -image:sample/meiro_de_maigo2.gif[link=https://dwursteisen.itch.io/meiro-de] -image:sample/freezming.gif[link=https://dwursteisen.itch.io/freezming] -image:sample/gravity-balls.gif[link=https://dwursteisen.itch.io/gravity-balls] -image:sample/reflections.gif[link=https://dwursteisen.itch.io/macro-jams-06-reflections] +image:sample/camping.gif[Camping - a cozy survival game made with Tiny game engine,link=https://dwursteisen.itch.io/trijam-camping] +image:sample/level-up.gif[Level Up - a typing game made with Tiny game engine,link=https://dwursteisen.itch.io/trijam-220-type-it] +image:sample/memory.gif[Memory Pong - a hack module and pong mashup game made with Tiny,link=https://dwursteisen.itch.io/memory-pong-trijam-251] +image:sample/connect_me.gif[Connect Me - a puzzle connection game made with Tiny game engine,link=https://dwursteisen.itch.io/connect-me] +image:sample/only_three_seconds.gif[Only Three Seconds - a light-based game made with Tiny,link=https://dwursteisen.itch.io/one-light-for-three-seconds] +image:sample/meiro_de_maigo2.gif[Meiro de Maigo - a maze exploration game made with Tiny game engine,link=https://dwursteisen.itch.io/meiro-de] +image:sample/freezming.gif[Freezming - a freezing-themed game made with Tiny game engine,link=https://dwursteisen.itch.io/freezming] +image:sample/gravity-balls.gif[Gravity Balls - a physics gravity game made with Tiny game engine,link=https://dwursteisen.itch.io/gravity-balls] +image:sample/reflections.gif[Reflections - a reflection-themed game made with Tiny game engine,link=https://dwursteisen.itch.io/macro-jams-06-reflections] +image:sample/2026-1bit-jam-2.gif[2026 1bit Jam 2 - a game made with Tiny game engine,link=https://dwursteisen.itch.io/pair-of-pipes] TIP: Want your game to appear here? Create a post about it in https://github.com/minigdx/tiny/discussions/categories/show-and-tell[the Show and tell board] and share all information about it. diff --git a/tiny-doc/src/docs/asciidoc/tiny-tutorial-export.adoc b/tiny-doc/src/docs/asciidoc/tiny-tutorial-export.adoc new file mode 100644 index 00000000..2cf94168 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-tutorial-export.adoc @@ -0,0 +1,101 @@ +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Export and deploy your Tiny game for the web or desktop. Learn to use tiny-cli export, deploy to itch.io, and host on static servers. +:keywords: tiny export, game export, itch.io deploy, web game, game publishing, tiny game engine, webgl game + += Exporting Your Game for the Web + +Once your game is ready, it's time to share it with the world! +Tiny makes it easy to export your game for the web or desktop so anyone can play it. + +== Preview locally + +Before exporting, you can preview your game in a browser using the built-in development server: + +```bash +tiny-cli serve --port 8080 +``` + +This starts a local web server with hot reload. Open `http://localhost:8080` in your browser to play the game. Any changes to your Lua scripts are picked up automatically. + +== Export for the web + +To create a web-ready archive of your game: + +```bash +tiny-cli export -p web --archive my-game.zip +``` + +This produces a ZIP file containing everything needed to run your game in a browser: + +- `index.html` -- The HTML page that loads your game. +- `tiny-engine.js` -- The Tiny engine compiled to JavaScript. +- Your game scripts and assets (Lua files, spritesheets, sounds, etc.). + +The game runs using WebGL 2.0 and works in all modern browsers. + +== Export for desktop + +To create a desktop build: + +```bash +tiny-cli export -p desktop +``` + +This produces a runnable archive that requires Java to be installed on the target machine. + +You can bundle a JDK for a self-contained package: + +```bash +tiny-cli export -p desktop --include-jdk +``` + +To target a specific platform: + +```bash +tiny-cli export -p desktop --include-jdk --desktop-platform linux +tiny-cli export -p desktop --include-jdk --desktop-platform macos +tiny-cli export -p desktop --include-jdk --desktop-platform windows +``` + +== Deploy to itch.io + +https://itch.io[itch.io] is one of the easiest ways to share your game online. Here's how to deploy your web export: + +1. **Export your game** as a ZIP file: ++ +```bash +tiny-cli export -p web --archive my-game.zip +``` + +2. **Create a new project** on itch.io: + - Go to https://itch.io/game/new[itch.io/game/new] + - Fill in the title and description + +3. **Upload the ZIP file**: + - In the *Uploads* section, upload `my-game.zip` + - Check **"This file will be played in the browser"** + +4. **Configure the viewport**: + - Set *Viewport dimensions* to match your game resolution (e.g., `516x516`) + - Enable *Fullscreen button* for a better experience + +5. **Save and publish** -- your game is now live! + +== Deploy to any static host + +Since the web export is just static files, you can host it anywhere: + +- **GitHub Pages**: Push the contents of the ZIP to a `gh-pages` branch. +- **Netlify / Vercel**: Drop the folder and deploy. +- **Any web server**: Upload the files to your server's public directory. + +No backend is required -- the game runs entirely in the browser. + +== What's next? + +Learn how to add visual assets to your game with the link:tiny-tutorial-sprites.html[Managing Sprites and Animation] tutorial. diff --git a/tiny-doc/src/docs/asciidoc/tiny-tutorial-maps.adoc b/tiny-doc/src/docs/asciidoc/tiny-tutorial-maps.adoc new file mode 100644 index 00000000..48dfa438 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-tutorial-maps.adoc @@ -0,0 +1,164 @@ +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Build game levels with LDtk and the Tiny game engine. Learn to draw maps, manage layers, handle entities, and implement tile-based collision. +:keywords: tiny maps, ldtk, level editor, tilemap, game levels, tiny game engine, map collision, tile-based game + += Managing Maps with the LDtk Editor + +Maps let you build rich game worlds using a visual editor. +Tiny integrates with https://ldtk.io/[LDtk] (Level Designer Toolkit), a free and open-source 2D level editor. + +== What is LDtk? + +LDtk is a modern level editor designed for 2D games. It provides: + +- A visual tile-based map editor +- Support for multiple layers (tiles, entities, collision) +- Entity placement with custom fields +- Auto-tiling rules +- JSON-based output that Tiny reads directly + +Download LDtk from https://ldtk.io/[ldtk.io] to get started. + +== Setting up a map + +=== Create an LDtk project + +1. Open LDtk and create a new project. +2. Set the grid size to match your spritesheet tile size (e.g., 16x16). +3. Import your spritesheet as a tileset. +4. Paint your level using the tile editor. +5. Save the `.ldtk` file in your game directory. + +=== Add to your game configuration + +Register the LDtk file in your `_tiny.json`: + +```json +{ + "levels": ["my-level.ldtk"] +} +``` + +You can also add it via the CLI: + +```bash +tiny-cli add my-level.ldtk +``` + +== Drawing maps + +To draw the entire map with all its layers: + +```lua +function _draw() + gfx.cls() + map.draw() +end +``` + +=== Drawing specific layers + +If your map has multiple layers, you can draw them individually: + +```lua +function _draw() + gfx.cls() + -- Draw only the "Background" layer + map.draw("Background") + -- Draw player on top + spr.draw(player_sprite, player_x, player_y) + -- Draw the "Foreground" layer on top of the player + map.draw("Foreground") +end +``` + +This gives you control over the rendering order, so you can draw the player between background and foreground layers. + +== Working with multiple levels + +LDtk projects can contain multiple levels. Switch between them using `map.level()`: + +```lua +function _init() + current_level = 0 + map.level(current_level) +end + +function change_level(n) + current_level = n + map.level(current_level) +end +``` + +Levels are indexed starting from `0`, in the order they appear in the LDtk project. + +== Working with entities + +LDtk lets you place entities (spawn points, enemies, items, triggers) directly in the level editor. +Access them in your game with `map.entities()`: + +```lua +function _init() + -- Get all entities from the current level + local entities = map.entities() + + for _, entity in ipairs(entities) do + if entity.name == "PlayerSpawn" then + player_x = entity.x + player_y = entity.y + elseif entity.name == "Enemy" then + -- Access custom fields defined in LDtk + local speed = entity.fields.speed or 1 + spawn_enemy(entity.x, entity.y, speed) + end + end +end +``` + +Each entity has: + +- `name` -- The entity identifier defined in LDtk. +- `x`, `y` -- The position in pixels. +- `fields` -- A table of custom fields you defined in LDtk. + +== Tile-based collision + +Use `map.cflag()` to check collision flags on tiles: + +```lua +function _update() + -- Convert pixel position to tile coordinates + local cx, cy = map.from(player_x, player_y) + + -- Check if the tile to the right is solid + if ctrl.pressing(keys.right) then + local next_cx = cx + 1 + if map.cflag(next_cx, cy) == 0 then + player_x = player_x + 2 + end + end +end +``` + +=== Coordinate conversion + +Tiny provides functions to convert between pixel coordinates and tile coordinates: + +```lua +-- Convert pixel coordinates to tile coordinates +local tile_x, tile_y = map.from(pixel_x, pixel_y) + +-- Convert tile coordinates to pixel coordinates +local pixel_x, pixel_y = map.to(tile_x, tile_y) +``` + +This is useful for snapping objects to the grid or checking which tile a character is standing on. + +== What's next? + +Learn how to give your game a unique visual identity with the link:tiny-fonts.html[Managing Custom Fonts] tutorial. diff --git a/tiny-doc/src/docs/asciidoc/tiny-tutorial-sound.adoc b/tiny-doc/src/docs/asciidoc/tiny-tutorial-sound.adoc new file mode 100644 index 00000000..8a6bf232 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-tutorial-sound.adoc @@ -0,0 +1,130 @@ +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Add sound effects and music to your Tiny game. Use the SFX editor, play sounds from Lua, loop music, and manage audio playback. +:keywords: tiny sound, game audio, sfx editor, sound effects, chip tune, tiny game engine, game music, sound.sfx + += Adding Sound to Your Game + +Sound effects bring your game to life. +Tiny uses a chip-tune style sound system -- you design sounds using a built-in SFX editor, then play them from your Lua code. + +== How sound works in Tiny + +Tiny's audio system is based on synthesized sound effects (SFX). Instead of loading audio files, you design sounds using waveform parameters (frequency, volume, waveform type, etc.) in the SFX editor. These sounds are stored in a `.sfx` file. + +IMPORTANT: In web browsers, sound can only start after the first user interaction (click or key press). This is a browser security requirement, not a Tiny limitation. Your game should be designed so that sounds are triggered by player actions. + +== The SFX Editor + +Tiny includes a built-in sound effect editor. Launch it with: + +```bash +tiny-cli sfx +``` + +The SFX editor lets you: + +- Design sounds using waveform types (square, sine, triangle, sawtooth, noise). +- Adjust frequency, volume, attack, decay, and other parameters. +- Preview sounds in real time. +- Save sounds to a `.sfx` file. + +Each sound occupies a numbered slot (0, 1, 2, ...) in the SFX file. + +++++ + +++++ + +== Configuration + +Register your sound file in `_tiny.json`: + +```json +{ + "sounds": ["my-sounds.sfx"] +} +``` + +You can also add it via the CLI: + +```bash +tiny-cli add my-sounds.sfx +``` + +== Playing sound effects + +Use `sfx.play()` to trigger a sound by its slot index: + +```lua +function _update() + -- Play sound 0 when space is pressed + if ctrl.pressed(keys.space) then + sfx.play(0) + end +end +``` + +=== Looping sounds + +To loop a sound (useful for background music or continuous effects): + +```lua +function _init() + -- Start looping sound 1 (e.g., background music) + music_handle = sfx.play(1, true) +end +``` + +The second argument `true` enables looping. The function returns a handle you can use to control playback. + +=== Controlling playback + +The handle returned by `sfx.play()` lets you stop the sound or check its status: + +```lua +function _init() + music = sfx.play(1, true) +end + +function _update() + -- Stop the music when M is pressed + if ctrl.pressed(keys.m) then + if music.playing then + music.stop() + else + music = sfx.play(1, true) + end + end +end +``` + +== Playing notes + +You can play individual musical notes with `sfx.note()`: + +```lua +-- Play note at index 0 +sfx.note(0) +``` + +This is useful for musical games or dynamic sound generation. + +== Best practices + +- **Trigger sounds on user input**: Always tie sounds to player actions (key presses, collisions) rather than playing them automatically on game start. This ensures sounds work in web browsers. +- **Manage your handles**: Keep references to looping sounds so you can stop them later. Orphaned looping sounds continue playing indefinitely. +- **Use distinct slots**: Organize your SFX file so each slot has a clear purpose (e.g., slot 0 = jump, slot 1 = coin, slot 2 = explosion). +- **Test in the browser**: Audio behavior can differ between desktop and web. Always test your sounds in both environments. + +== What's next? + +You've completed the Tiny tutorial series! Here are some resources to continue your journey: + +- link:api.html[API Reference] -- Complete documentation for every Lua function. +- link:tiny-cli.html[CLI Reference] -- All available command-line tools. +- link:guide.html#_tiny_showcase[Showcase] -- See what others have built with Tiny. +- https://github.com/minigdx/tiny[GitHub Repository] -- Source code, issues, and contributions. diff --git a/tiny-doc/src/docs/asciidoc/tiny-tutorial-sprites.adoc b/tiny-doc/src/docs/asciidoc/tiny-tutorial-sprites.adoc new file mode 100644 index 00000000..4345778a --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tiny-tutorial-sprites.adoc @@ -0,0 +1,191 @@ +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Learn how to use sprites and animation in the Tiny game engine. Draw spritesheets, animate characters, flip sprites, and manage multiple sheets. +:keywords: tiny sprites, sprite animation, spritesheet, pixel art, game sprites, tiny game engine, spr.draw + += Managing Sprites and Animation + +Sprites are the visual building blocks of your game. +In Tiny, sprites are stored in **spritesheets** -- PNG images arranged in a grid of equally-sized cells. + +== What are sprites? + +A spritesheet is a single PNG image containing multiple small images (sprites) laid out in a grid. +Each sprite occupies a fixed-size cell, and Tiny numbers them left-to-right, top-to-bottom starting from `0`. + +For example, with a 128x128 spritesheet and 16x16 cells: + +``` + 0 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 15 +16 17 18 19 20 21 22 23 +... +``` + +== Setting up your spritesheet + +1. Create a spritesheet PNG using any pixel art editor (Aseprite, Piskel, LibreSprite, etc.). +2. Add it to your game: ++ +```bash +tiny-cli add my-sprites.png +``` +3. The spritesheet is registered in your `_tiny.json` configuration: ++ +```json +{ + "sprites": ["my-sprites.png"] +} +``` + +The sprite size is configured in `_tiny.json` under the game settings. The default is `16x16` pixels. + +== Drawing sprites + +Use `spr.draw()` to draw a sprite at a given position: + +```lua +function _draw() + gfx.cls() + -- Draw sprite number 0 at position (100, 100) + spr.draw(0, 100, 100) +end +``` + +=== Flipping sprites + +You can flip a sprite horizontally or vertically: + +```lua +-- Flip horizontally +spr.draw(0, 100, 100, true, false) + +-- Flip vertically +spr.draw(0, 100, 100, false, true) + +-- Flip both +spr.draw(0, 100, 100, true, true) +``` + +This is useful for characters that face left or right -- you only need to draw one direction in the spritesheet. + +== Drawing sub-regions + +For more control, use `spr.sdraw()` to draw a specific region from the spritesheet: + +```lua +-- spr.sdraw(x, y, sprX, sprY, width, height) +-- Draw a 32x32 region starting at pixel (0, 0) from the spritesheet +spr.sdraw(100, 100, 0, 0, 32, 32) +``` + +You can also flip sub-regions: + +```lua +-- spr.sdraw(x, y, sprX, sprY, width, height, flipX, flipY) +spr.sdraw(100, 100, 0, 0, 32, 32, true, false) +``` + +== Switching spritesheets + +If your game uses multiple spritesheets, switch between them with `spr.sheet()`: + +```lua +-- Switch to the second spritesheet (index 1) +spr.sheet(1) +spr.draw(0, 100, 100) + +-- Switch back to the first spritesheet (index 0) +spr.sheet(0) +spr.draw(0, 50, 50) +``` + +Spritesheets are indexed in the order they appear in the `sprites` array of `_tiny.json`. + +== Simple animation + +You can animate a sprite by cycling through sprite indices using `tiny.frame`: + +```lua +function _init() + player_x = 128 + player_y = 128 + -- Animation frames: sprite indices to cycle through + anim_frames = {0, 1, 2, 3} + anim_speed = 8 -- Change frame every 8 game frames +end + +function _update() + -- Move player + if ctrl.pressing(keys.right) then + player_x = player_x + 2 + end + if ctrl.pressing(keys.left) then + player_x = player_x - 2 + end +end + +function _draw() + gfx.cls() + + -- Pick the current animation frame + local frame_index = math.floor(tiny.frame / anim_speed) % #anim_frames + local sprite_n = anim_frames[frame_index + 1] + + spr.draw(sprite_n, player_x, player_y) +end +``` + +The pattern is simple: divide `tiny.frame` by the animation speed, take the modulo of the number of frames, and use the result to index into your animation table. + +== Try it out + +Use the interactive editor below to experiment with sprites. This example uses the built-in dungeon spritesheet: + +++++ + +function _init() + x = 120 + y = 120 + frame_count = 0 + -- Sprite indices for animation + frames = {0, 1, 2, 3} + speed = 10 + flip = false +end + +function _update() + frame_count = frame_count + 1 + + if ctrl.pressing(keys.right) then + x = x + 1 + flip = false + end + if ctrl.pressing(keys.left) then + x = x - 1 + flip = true + end + if ctrl.pressing(keys.up) then y = y - 1 end + if ctrl.pressing(keys.down) then y = y + 1 end +end + +function _draw() + gfx.cls() + + -- Draw animated sprite + local idx = math.floor(frame_count / speed) % #frames + local n = frames[idx + 1] + spr.draw(n, x, y, flip, false) + + print("arrows: move", 80, 8, 7) +end + +++++ + +== What's next? + +Now that you know how to work with sprites, learn how to build game levels with the link:tiny-tutorial-maps.html[Managing Maps with the LDtk Editor] tutorial. diff --git a/tiny-doc/src/docs/asciidoc/tiny-tutorial.adoc b/tiny-doc/src/docs/asciidoc/tiny-tutorial.adoc index b2d04b57..58f3200b 100644 --- a/tiny-doc/src/docs/asciidoc/tiny-tutorial.adoc +++ b/tiny-doc/src/docs/asciidoc/tiny-tutorial.adoc @@ -1,18 +1,27 @@ -== Tiny Tutorial +:docinfo: shared +:icons: font +:book: +:source-highlighter: rouge +:favicon: ./sample/favicon.png +:stylesheet: adoc-riak.css +:description: Build your first game with the Tiny game engine. Step-by-step Pong tutorial covering game state, input handling, collision detection, and drawing. +:keywords: tiny tutorial, pong game, lua game tutorial, game development tutorial, beginner game programming, tiny game engine -This tutorial will guide you through creating your very first game step by step. Don't worry if you're new to game development – we'll take it nice and easy. += Build Your First Game: Pong -In this tutorial, we'll be creating a simple Pong game using the Lua programming language and `🧸 Tiny`. +This tutorial will guide you through creating your very first game step by step. Don't worry if you're new to game development -- we'll take it nice and easy. + +In this tutorial, we'll be creating a simple Pong game using the Lua programming language and `Tiny`. Pong is a classic 2D arcade game where two players control paddles on opposite sides of the screen, and try to hit a ball back and forth without letting it pass their paddle. The game ends when one player misses the ball, and the other player scores a point. -Our implementation of Pong will have a fixed screen size of 256 pixels for width and height, and we'll use the `ctrl.pressing()` function to check for key presses. Check the <<_tiny_api,Tiny API>> to know more about the Tiny Engine API. +Our implementation of Pong will have a fixed screen size of 256 pixels for width and height, and we'll use the `ctrl.pressing()` function to check for key presses. Check the link:api.html[Tiny API] to know more about the Tiny Engine API. We'll cover four main steps: initializing the game state with the `_init()` function, updating the game state with the `_update()` function and drawing the game with the `_draw()` function. By the end of this tutorial, you should have a basic Pong game that you can customize and build upon. -=== Step 1: Initialize the Game State +== Step 1: Initialize the Game State First, we need to initialize the game state. We'll define the position and size of the paddles, the position and size of the ball, and the initial velocity of the ball. ```lua @@ -28,7 +37,20 @@ function _init() end ``` -=== Step 2: Update the Game State +== Step 2: Draw the Game +In the `_draw()` callback, we'll draw the paddles and the ball using the `shape.rectf()` and `shape.circlef()` functions. + +```lua +function _draw() + -- Draw game + gfx.cls() + shape.rectf(player1_pos.x, player1_pos.y, paddle_width, paddle_height, 7) + shape.rectf(player2_pos.x, player2_pos.y, paddle_width, paddle_height, 7) + shape.circlef(ball_pos.x, ball_pos.y, ball_radius, 7) +end +``` + +== Step 3: Update the Game State In the `_update()` callback, we'll update the game state by moving the paddles and the ball. We'll also check for collisions between the ball and the paddles, and update the ball's velocity accordingly. ```lua @@ -74,18 +96,7 @@ function _update() end ``` -=== Step 3: Draw the Game -In the `_draw()` callback, we'll draw the paddles and the ball using the `shape.rectf()` and `shape.circlef()` functions. -```lua -function _draw() - -- Draw game - gfx.cls() - shape.rectf(player1_pos.x, player1_pos.y, paddle_width, paddle_height, 7) - shape.rectf(player2_pos.x, player2_pos.y, paddle_width, paddle_height, 7) - shape.circlef(ball_pos.x, ball_pos.y, ball_radius, 7) -end -``` And that's it! With these three steps, you should have a basic Pong game up and running in Lua. Feel free to experiment with the game state, update function, and drawing function to customize the game to your liking. @@ -152,3 +163,7 @@ function _draw() end ++++ + +== What's next? + +Ready to share your game with the world? Check out the link:tiny-tutorial-export.html[Exporting Your Game for the Web] tutorial to learn how to publish your creation online. diff --git a/tiny-doc/src/docs/asciidoc/tutorials.json b/tiny-doc/src/docs/asciidoc/tutorials.json new file mode 100644 index 00000000..7f6445b8 --- /dev/null +++ b/tiny-doc/src/docs/asciidoc/tutorials.json @@ -0,0 +1,44 @@ +[ + { + "title": "1. Getting Started", + "icon": "download", + "href": "tiny-install.html", + "description": "Install Tiny CLI, create your first project, and learn the basics of the game loop." + }, + { + "title": "2. Build a Pong Game", + "icon": "sports_tennis", + "href": "tiny-tutorial.html", + "description": "Learn the fundamentals of Tiny by building a classic Pong game. Covers input handling, collision detection, and game state." + }, + { + "title": "3. Exporting Your Game", + "icon": "public", + "href": "tiny-tutorial-export.html", + "description": "Export your game for the web or desktop, and deploy it to itch.io or any static host." + }, + { + "title": "4. Sprites & Animation", + "icon": "image", + "href": "tiny-tutorial-sprites.html", + "description": "Draw spritesheets, animate characters, flip sprites, and manage multiple sheets." + }, + { + "title": "5. Maps with LDtk", + "icon": "map", + "href": "tiny-tutorial-maps.html", + "description": "Build game levels with the LDtk editor. Draw maps, handle entities, and implement tile-based collision." + }, + { + "title": "6. Custom Fonts", + "icon": "font_download", + "href": "tiny-fonts.html", + "description": "Create bitmap font spritesheets, configure font banks, and give your game a unique look." + }, + { + "title": "7. Adding Sound", + "icon": "music_note", + "href": "tiny-tutorial-sound.html", + "description": "Design chip-tune sound effects with the SFX editor and play them from your Lua code." + } +] diff --git a/tiny-doc/src/docs/templates/base.peb b/tiny-doc/src/docs/templates/base.peb new file mode 100644 index 00000000..e1ee3761 --- /dev/null +++ b/tiny-doc/src/docs/templates/base.peb @@ -0,0 +1,38 @@ + + + + + + {% block title %}Tiny Game Engine{% endblock %} + + +{% block meta %}{% endblock %} + + + + + + + + + + + + + + +{% block pageStyles %}{% endblock %} + + +{% include "partials/nav.peb" %} +
+ +
+{% block content %}{% endblock %} +
+ +{% include "partials/footer.peb" %} +
+{% block pageScripts %}{% endblock %} + + diff --git a/tiny-doc/src/docs/templates/pages/api.peb b/tiny-doc/src/docs/templates/pages/api.peb new file mode 100644 index 00000000..bafc608d --- /dev/null +++ b/tiny-doc/src/docs/templates/pages/api.peb @@ -0,0 +1,355 @@ +{% extends "base.peb" %} + +{% block title %}API Reference - Tiny Game Engine{% endblock %} + +{% block meta %} + + + + + + + + + + +{% endblock %} + +{% block pageStyles %} + +{% endblock %} + +{% block content %} + +
+

API Reference

+

+ Every Lua function at your fingertips. Search, browse, and learn the complete Tiny API. +

+
+ + +
+ +
+ + +
+ +{% for lib in libraries %} + +{% endfor %} +
+ + +
+
+
+{% for fn in functions %} +{% include "partials/fn-card.peb" %} +{% endfor %} +
+
+ search_off + No functions match your search. +
+
+
+ +{% endblock %} + +{% block pageScripts %} + + + +{% endblock %} diff --git a/tiny-doc/src/docs/templates/pages/cli.peb b/tiny-doc/src/docs/templates/pages/cli.peb new file mode 100644 index 00000000..e7ec4492 --- /dev/null +++ b/tiny-doc/src/docs/templates/pages/cli.peb @@ -0,0 +1,252 @@ +{% extends "base.peb" %} + +{% block title %}CLI Reference - Tiny Game Engine{% endblock %} + +{% block meta %} + + + + + + + + + + +{% endblock %} + +{% block pageStyles %} + +{% endblock %} + +{% block content %} + +
+

CLI Reference

+

+ The Tiny command-line interface provides everything you need to create, develop, test, and export your games. +

+
+ + +
+ +
+ + +
+
+
+{% for cmd in commands %} +{% include "partials/cmd-card.peb" %} +{% endfor %} +
+
+ search_off + No commands match your search. +
+ + +
+

SFX Editor

+

+ Tiny is bundled with a small SFX editor that you can start using the command tiny-cli sfx within your game directory. + This editor can design an instrument and the notes played by this instrument to generate a sound effect. + Try it below: +

+
+ +
+
+
+
+{% endblock %} + +{% block pageScripts %} + +{% endblock %} diff --git a/tiny-doc/src/docs/templates/pages/documentation.peb b/tiny-doc/src/docs/templates/pages/documentation.peb new file mode 100644 index 00000000..e41bf69b --- /dev/null +++ b/tiny-doc/src/docs/templates/pages/documentation.peb @@ -0,0 +1,272 @@ +{% extends "base.peb" %} + +{% block title %}Documentation - Tiny Game Engine{% endblock %} + +{% block meta %} + + + + + + + + + + + + +{% endblock %} + +{% block pageStyles %} + +{% endblock %} + +{% block content %} + +
+

Documentation

+

+ Everything you need to build games with Tiny. Tutorials, API reference, and CLI commands. +

+
+ + +
+ +
+ + +
+
+
+ Learn by Doing +

Tutorials

+

Follow along and build complete games step by step.

+
+
+{% for tutorial in tutorials %} +{% include "partials/tutorial-card.peb" %} +{% endfor %} +
+
+
+ + +
+
+
+ Look It Up +

API Reference

+

Complete documentation for every Lua function: graphics, sprites, sound, input, maps, and more.

+
+ + + + + +
+
+ + +
+
+
+ Command Line +

CLI Reference

+

All the commands you need to create, run, debug, and export your Tiny games.

+
+ +
+
+ + +
+ +
+{% endblock %} + +{% block pageScripts %} + + + +{% endblock %} diff --git a/tiny-doc/src/docs/templates/pages/editor.peb b/tiny-doc/src/docs/templates/pages/editor.peb new file mode 100644 index 00000000..144cab42 --- /dev/null +++ b/tiny-doc/src/docs/templates/pages/editor.peb @@ -0,0 +1,255 @@ +{% extends "base.peb" %} + +{% block title %}Online Editor - Tiny Game Engine{% endblock %} + +{% block meta %} + + + + + + + + + + +{% endblock %} + +{% block pageStyles %} + +{% endblock %} + +{% block content %} +
+

Editor

+

+ Write, run, and experiment with Lua games directly in your browser. No installation required. +

+
+ +
+
+
+ + + + game.lua + +
+ +function _init() + +end + +function _update() + +end + +function _draw() + +end + +
+ + + Share this code + +
+{% endblock %} + +{% block pageScripts %} + + + +{% endblock %} diff --git a/tiny-doc/src/docs/templates/pages/index.peb b/tiny-doc/src/docs/templates/pages/index.peb new file mode 100644 index 00000000..af794368 --- /dev/null +++ b/tiny-doc/src/docs/templates/pages/index.peb @@ -0,0 +1,413 @@ +{% extends "base.peb" %} + +{% block title %}Tiny - Lightweight Lua Game Engine & Virtual Console{% endblock %} + +{% block meta %} + + + + + + + + + + + + + + + + + + + + + + +{% endblock %} + +{% block pageStyles %} + +{% endblock %} + +{% block content %} + +
+
+ +
+

🧸 tiny

+

Build, Tweak, and Play.

+ +
+
+
+ + +
+
+
+ Core Features +

Designed for Playful Creation

+

+ A tiny virtual console for building pixel art games. Hot-reload, 256 colors, Lua scripting, and export to desktop or web. +

+
+
+
+
+ bolt +
+

Hot Reload

+

See your changes instantly. Edit your Lua scripts and watch the game update in real-time without restarting.

+
+
+
+ code +
+

Lua Scripting

+

Write game logic in Lua — a simple, powerful language perfect for beginners and veterans alike.

+
+
+
+ public +
+

Desktop & Web

+

Build once, run everywhere. Export your games for JVM desktop or WebGL browser — from a single codebase.

+
+
+
+
+ + +
+
+
+ +
+
+ + + +
+
+
+ + + + game.lua +
+
+ +
+
function _draw()
+    -- clear the screen
+    gfx.cls(1)
+
+    -- draw shapes
+    shape.rectf(10, 10, 50, 30, 8)
+    shape.circlef(128, 128, 24, 12)
+
+    -- print text
+    print("hello tiny!", 80, 200, 7)
+end
+
+ +
+
function _init()
+    player = { x = 128, y = 128 }
+end
+
+function _update()
+    if ctrl.pressing(keys.right) then
+        player.x = player.x + 2
+    end
+end
+
+function _draw()
+    gfx.cls(1)
+    spr.draw(0, player.x, player.y)
+end
+
+ +
+
function _update()
+    if ctrl.pressed(keys.space) then
+        sfx.play(0)
+    end
+end
+
+function _draw()
+    gfx.cls(2)
+    print("press space!", 80, 120, 7)
+end
+
+
+
+
+ +
+ +

Lua Scripting

+

+ Use Lua to bring your worlds to life. A lightweight, easy-to-learn language with a clean API for sprites, input, maps, and sound. Writing game logic feels like sketching on paper. +

+
+
+
+
+ + +
+
+
+ +
+ +

Hot Reload Everything

+

+ Edit your scripts while the game is running. Change colors, tweak physics, rewrite behaviors — your game state stays intact, so you never lose your flow. +

+
+ +
+
+
+ + + + enemy.lua +
+
+
function _update()
+    -- tweak speed while playing!
+    enemy.speed = 3
+
+    if enemy.x < player.x then
+        enemy.x = enemy.x + enemy.speed
+    end
+end
+
+function _draw()
+    spr.draw(1, enemy.x, enemy.y)
+end
+
+
+
+
+ refresh + Live Reloading +
+
+
+
+
+
+ + +
+
+
+
+

Ready to Play?

+

+ Start building your retro-style game right now, directly in your browser. No installation required. +

+ +
+
+{% endblock %} + +{% block pageScripts %} + + +{% endblock %} diff --git a/tiny-doc/src/docs/templates/pages/showcase.peb b/tiny-doc/src/docs/templates/pages/showcase.peb new file mode 100644 index 00000000..47b979f7 --- /dev/null +++ b/tiny-doc/src/docs/templates/pages/showcase.peb @@ -0,0 +1,232 @@ +{% extends "base.peb" %} + +{% block title %}Showcase - Tiny Game Engine{% endblock %} + +{% block meta %} + + + +{% endblock %} + +{% block pageStyles %} + +{% endblock %} + +{% block content %} + +
+

Showcase

+

+ Get inspired by what's possible with Tiny! Explore games created by the community. +

+ +
+ + +
+ +{% for genre in genres %} + +{% endfor %} +
+ + +
+{% for game in games %} +{% include "partials/game-card.peb" %} +{% endfor %} +
+ + +
+ +
+ +
+{% endblock %} + +{% block pageScripts %} + +{% endblock %} diff --git a/tiny-doc/src/docs/templates/partials/cmd-card.peb b/tiny-doc/src/docs/templates/partials/cmd-card.peb new file mode 100644 index 00000000..bbd9e7e7 --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/cmd-card.peb @@ -0,0 +1,32 @@ +
+
+ {{ cmd.name }} +
+

{{ cmd.description }}

+
+
Usage
+
{{ cmd.usage }}
+
+{% if cmd.options is not empty %} +
+
Options
+{% for opt in cmd.options %} +
+ {% for name in opt.names %}{{ name }}{% if not loop.last %}, {% endif %}{% endfor %} + {{ opt.help }} +
+{% endfor %} +
+{% endif %} +{% if cmd.arguments is not empty %} +
+
Arguments
+{% for arg in cmd.arguments %} +
+ <{{ arg.name }}> + {{ arg.help }} +
+{% endfor %} +
+{% endif %} +
diff --git a/tiny-doc/src/docs/templates/partials/fn-card.peb b/tiny-doc/src/docs/templates/partials/fn-card.peb new file mode 100644 index 00000000..2e1bc755 --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/fn-card.peb @@ -0,0 +1,34 @@ +
+
+ {{ fn.library }} + {{ fn.signature }} +
+

{{ fn.description }}

+{% if fn.parameters is not empty %} +
+
Parameters
+{% for param in fn.parameters %} +
+ {{ param.name }} + {{ param.type }} + {{ param.description }} +
+{% endfor %} +
+{% endif %} +{% if fn.example is not empty %} +
+
+
+ example.lua + + play_arrow + Try it + +
+
{{ fn.example }}
+
+ {{ fn.example | raw }} +
+{% endif %} +
diff --git a/tiny-doc/src/docs/templates/partials/fn-day-card.peb b/tiny-doc/src/docs/templates/partials/fn-day-card.peb new file mode 100644 index 00000000..4f172f91 --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/fn-day-card.peb @@ -0,0 +1,16 @@ +
+
+ + +
+

+
+
+ + + + example.lua +
+
+
+
diff --git a/tiny-doc/src/docs/templates/partials/footer.peb b/tiny-doc/src/docs/templates/partials/footer.peb new file mode 100644 index 00000000..0c67f63d --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/footer.peb @@ -0,0 +1,11 @@ + diff --git a/tiny-doc/src/docs/templates/partials/game-card.peb b/tiny-doc/src/docs/templates/partials/game-card.peb new file mode 100644 index 00000000..ac80d9c2 --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/game-card.peb @@ -0,0 +1,19 @@ +
+ {{ game.title }} - a game made with Tiny game engine +
+
+{% for genre in game.genres %} + {{ genre | capitalize }} +{% endfor %} +
+

{{ game.title }}

+ +
+
diff --git a/tiny-doc/src/docs/templates/partials/nav.peb b/tiny-doc/src/docs/templates/partials/nav.peb new file mode 100644 index 00000000..f719188f --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/nav.peb @@ -0,0 +1,12 @@ + diff --git a/tiny-doc/src/docs/templates/partials/terminal.peb b/tiny-doc/src/docs/templates/partials/terminal.peb new file mode 100644 index 00000000..9af1187a --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/terminal.peb @@ -0,0 +1,9 @@ +
+
+ + + + {{ terminalFilename }} +
+
{{ terminalCode }}
+
diff --git a/tiny-doc/src/docs/templates/partials/tutorial-card.peb b/tiny-doc/src/docs/templates/partials/tutorial-card.peb new file mode 100644 index 00000000..6a13e0ff --- /dev/null +++ b/tiny-doc/src/docs/templates/partials/tutorial-card.peb @@ -0,0 +1,10 @@ + +
+ {{ tutorial.icon }} +
+
+

{{ tutorial.title }}

+

{{ tutorial.description }}

+
+ arrow_forward +
diff --git a/tiny-doc/src/main/kotlin/com/github/minigdx/tiny/doc/PebbleRenderer.kt b/tiny-doc/src/main/kotlin/com/github/minigdx/tiny/doc/PebbleRenderer.kt new file mode 100644 index 00000000..ce5b03d3 --- /dev/null +++ b/tiny-doc/src/main/kotlin/com/github/minigdx/tiny/doc/PebbleRenderer.kt @@ -0,0 +1,251 @@ +package com.github.minigdx.tiny.doc + +import io.pebbletemplates.pebble.PebbleEngine +import io.pebbletemplates.pebble.loader.FileLoader +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.longOrNull +import java.io.File + +fun main(args: Array) { + require(args.size == 4 || args.size == 5) { + "Usage: PebbleRenderer [cliJsonFile]" + } + PebbleRenderer( + templateDir = File(args[0]), + dataDir = File(args[1]), + outputDir = File(args[2]), + apiJsonFile = File(args[3]), + cliJsonFile = if (args.size == 5) File(args[4]) else null, + ).render() +} + +class PebbleRenderer( + private val templateDir: File, + private val dataDir: File, + private val outputDir: File, + private val apiJsonFile: File, + private val cliJsonFile: File? = null, +) { + fun render() { + val games = parseJsonArray(dataDir.resolve("showcase.json")) + val tutorials = parseJsonArray(dataDir.resolve("tutorials.json")) + val functions = flattenApiJson(apiJsonFile) + val functionsJson = Json.encodeToString(JsonElement.serializer(), toJsonElement(functions)) + val commands = parseCliJson() + + val genres = games + .flatMap { game -> + @Suppress("UNCHECKED_CAST") + (game["genres"] as? List) ?: emptyList() + } + .toSortedSet() + .toList() + + val libraries = functions + .mapNotNull { fn -> fn["library"] as? String } + .toSortedSet() + .toList() + + val engine = createEngine() + outputDir.mkdirs() + + renderTemplate(engine, "pages/index.peb", "index.html", emptyMap()) + + renderTemplate( + engine, + "pages/showcase.peb", + "showcase.html", + mapOf( + "games" to games, + "genres" to genres, + "initialCount" to 6, + ), + ) + + renderTemplate( + engine, + "pages/documentation.peb", + "documentation.html", + mapOf( + "tutorials" to tutorials, + "functions" to functions, + "functionsJson" to functionsJson, + ), + ) + + renderTemplate( + engine, + "pages/api.peb", + "api.html", + mapOf( + "functions" to functions, + "libraries" to libraries, + ), + ) + + renderTemplate( + engine, + "pages/editor.peb", + "editor.html", + emptyMap(), + ) + + renderTemplate( + engine, + "pages/cli.peb", + "tiny-cli.html", + mapOf( + "commands" to commands, + ), + ) + + println("Rendered 6 Pebble templates to ${outputDir.absolutePath}") + } + + private fun createEngine(): PebbleEngine { + val loader = FileLoader() + loader.setPrefix(templateDir.absolutePath + "/") + loader.setSuffix("") + return PebbleEngine.Builder() + .loader(loader) + .autoEscaping(true) + .build() + } + + private fun renderTemplate( + engine: PebbleEngine, + templateName: String, + outputName: String, + context: Map, + ) { + val template = engine.getTemplate(templateName) + File(outputDir, outputName).writer().use { writer -> + template.evaluate(writer, context) + } + } + + /** + * Parse the CLI JSON file into a list of command maps. + * + * Input structure: { commands: [{ name, description, usage, options: [{ names, help }], arguments: [{ name, help }] }] } + * Output structure: list of maps with the same structure, ready for Pebble templates. + */ + @Suppress("UNCHECKED_CAST") + private fun parseCliJson(): List> { + val file = cliJsonFile ?: return emptyList() + if (!file.exists()) return emptyList() + + val root = toNative(Json.parseToJsonElement(file.readText())) as Map<*, *> + return root["commands"] as? List> ?: emptyList() + } + + /** + * Flatten the nested tiny-api.json structure into the flat format expected by Pebble templates. + * + * Input structure: { libraries: [{ name, functions: [{ name, calls: [{ args }] }], variables: [{ name }] }] } + * Output structure: [{ library, name, signature, description, parameters: [{ name, type, description }], example }] + */ + private fun flattenApiJson(file: File): List> { + val root = toNative(Json.parseToJsonElement(file.readText())) as Map<*, *> + + @Suppress("UNCHECKED_CAST") + val libraries = root["libraries"] as List> + + return libraries.flatMap { lib -> + val libName = lib["name"] as String + val functions = flattenFunctions(libName, lib) + val variables = flattenVariables(libName, lib) + functions + variables + } + } + + @Suppress("UNCHECKED_CAST") + private fun flattenFunctions( + libName: String, + lib: Map, + ): List> { + val functions = lib["functions"] as? List> ?: emptyList() + return functions.map { fn -> + val name = fn["name"] as String + val calls = fn["calls"] as? List> ?: emptyList() + val primaryCall = calls.maxByOrNull { call -> + (call["args"] as? List<*>)?.size ?: 0 + } + val args = primaryCall?.let { it["args"] as? List> } ?: emptyList() + val argNames = args.joinToString(", ") { arg -> arg["name"] as? String ?: "" } + + mapOf( + "library" to libName, + "name" to name, + "signature" to "$libName.$name($argNames)", + "description" to (fn["description"] as? String ?: ""), + "parameters" to args.map { arg -> + mapOf( + "name" to (arg["name"] as? String ?: ""), + "type" to (arg["type"] as? String ?: ""), + "description" to (arg["description"] as? String ?: ""), + ) + }, + "example" to (fn["example"] as? String ?: ""), + ) + } + } + + @Suppress("UNCHECKED_CAST") + private fun flattenVariables( + libName: String, + lib: Map, + ): List> { + val variables = lib["variables"] as? List> ?: emptyList() + return variables.map { v -> + val name = v["name"] as String + mapOf( + "library" to libName, + "name" to name, + "type" to "variable", + "signature" to "$libName.$name", + "description" to (v["description"] as? String ?: ""), + "parameters" to emptyList>(), + "example" to (v["example"] as? String ?: ""), + ) + } + } + + private fun parseJsonArray(file: File): List> { + val element = Json.parseToJsonElement(file.readText()) + @Suppress("UNCHECKED_CAST") + return toNative(element) as List> + } + + private fun toNative(element: JsonElement): Any? = + when (element) { + is JsonNull -> null + is JsonPrimitive -> when { + element.isString -> element.content + element.booleanOrNull != null -> element.booleanOrNull + element.longOrNull != null -> element.longOrNull + element.doubleOrNull != null -> element.doubleOrNull + else -> element.content + } + is JsonArray -> element.map { toNative(it) } + is JsonObject -> element.entries.associate { (key, value) -> key to toNative(value) } + } + + private fun toJsonElement(value: Any?): JsonElement = + when (value) { + null -> JsonNull + is String -> JsonPrimitive(value) + is Boolean -> JsonPrimitive(value) + is Number -> JsonPrimitive(value) + is List<*> -> JsonArray(value.map { toJsonElement(it) }) + is Map<*, *> -> JsonObject(value.entries.associate { (k, v) -> k.toString() to toJsonElement(v) }) + else -> JsonPrimitive(value.toString()) + } +} diff --git a/tiny-engine/build.gradle.kts b/tiny-engine/build.gradle.kts index c1eef2a0..fc5f8b45 100644 --- a/tiny-engine/build.gradle.kts +++ b/tiny-engine/build.gradle.kts @@ -51,8 +51,8 @@ dependencies { because("KSP will generate all Lua stub methods from all Lua libs from Tiny.") } - add("kspJvm", project(":tiny-annotation-processors:tiny-api-to-asciidoc-generator")) { - because("KSP will generate the asciidoctor documentation of all Lua libs from Tiny.") + add("kspJvm", project(":tiny-annotation-processors:tiny-api-to-json-generator")) { + because("KSP will generate the JSON API reference of all Lua libs from Tiny.") } } @@ -98,8 +98,8 @@ configurations.create("tinyWebEngine") { outgoing.artifact(tinyEngineJsJar) } -// -- Asciidoctor artifact configuration -configurations.create("tinyApiAsciidoctor") { +// -- JSON API artifact configuration +configurations.create("tinyApiJson") { isCanBeResolved = false isCanBeConsumed = true } @@ -130,8 +130,8 @@ artifacts { add("tinyWebEngine", tinyEngineJsJar) { builtBy(tinyEngineJsJar) } - // API as Asciidoctor. - add("tinyApiAsciidoctor", project.layout.buildDirectory.dir("generated/ksp/jvm/jvmMain/resources/tiny-api.adoc")) { + // API as JSON. + add("tinyApiJson", project.layout.buildDirectory.dir("generated/ksp/jvm/jvmMain/resources/tiny-api.json")) { builtBy("kspKotlinJvm") } // API as Lua stub. diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/FontDescriptor.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/FontDescriptor.kt new file mode 100644 index 00000000..334542f5 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/FontDescriptor.kt @@ -0,0 +1,232 @@ +package com.github.minigdx.tiny.engine + +import com.github.minigdx.tiny.render.VirtualFrameBuffer +import com.github.minigdx.tiny.resources.SpriteSheet +import kotlin.math.max + +/** + * Maps accented characters to their ASCII base letter for boot font fallback. + */ +val ACCENT_MAP = + mapOf( + 'à' to 'a', 'á' to 'a', 'â' to 'a', 'ã' to 'a', 'ä' to 'a', 'å' to 'a', + 'ç' to 'c', + 'è' to 'e', 'é' to 'e', 'ê' to 'e', 'ë' to 'e', + 'ì' to 'i', 'í' to 'i', 'î' to 'i', 'ï' to 'i', + 'ñ' to 'n', + 'ò' to 'o', 'ó' to 'o', 'ô' to 'o', 'õ' to 'o', 'ö' to 'o', + 'ù' to 'u', 'ú' to 'u', 'û' to 'u', 'ü' to 'u', + 'ý' to 'y', 'ÿ' to 'y', + ) + +data class CharResolution( + val sourceX: Int, + val sourceY: Int, + val charWidth: Int, + val charHeight: Int, +) + +data class FontBank( + val name: String, + val charWidth: Int, + val charHeight: Int, + val x: Int, + val y: Int, + val charMap: Map>, +) + +data class FontDescriptor( + val name: String, + val spritesheet: String, + val spaceWidth: Int, + val lineHeight: Int, + val banks: List, +) { + fun resolve(codepoint: Int): CharResolution? { + for (bank in banks) { + val coord = bank.charMap[codepoint] + if (coord != null) { + return CharResolution( + sourceX = bank.x + coord.first * bank.charWidth, + sourceY = bank.y + coord.second * bank.charHeight, + charWidth = bank.charWidth, + charHeight = bank.charHeight, + ) + } + } + return null + } + + companion object { + fun fromConfig(config: GameConfigFont): FontDescriptor { + var maxHeight = 0 + val banks = config.banks.map { bankConfig -> + val bank = FontBank( + name = bankConfig.name, + charWidth = bankConfig.width, + charHeight = bankConfig.height, + x = bankConfig.x, + y = bankConfig.y, + charMap = buildCharMap(bankConfig.characters), + ) + maxHeight = max(maxHeight, bankConfig.height) + bank + } + val defaultSpaceWidth = config.banks.firstOrNull()?.let { it.width / 2 } ?: 4 + return FontDescriptor( + name = config.name, + spritesheet = config.spritesheet, + spaceWidth = config.spaceWidth ?: defaultSpaceWidth, + lineHeight = maxHeight, + banks = banks, + ) + } + + /** + * Create a FontDescriptor for the boot font (_boot.png). + * Layout: 256×256 px, 4×4 cells. + * Row 0: a-z, Row 1: 0-9, Row 2: !-/, Row 3: [-`, Row 4: {-~, Row 5: :-@ + */ + fun createBootDescriptor(): FontDescriptor { + val charMap = mutableMapOf>() + + // Row 0: a-z (uppercase maps to same) + for (c in 'a'..'z') { + charMap[c.code] = (c - 'a') to 0 + charMap[c.uppercaseChar().code] = (c - 'a') to 0 + } + // Row 1: 0-9 + for (c in '0'..'9') { + charMap[c.code] = (c - '0') to 1 + } + // Row 2: ! to / (ASCII 33-47) + for (c in '!'..'/') { + charMap[c.code] = (c - '!') to 2 + } + // Row 3: [ to ` (ASCII 91-96) + for (c in '['..'`') { + charMap[c.code] = (c - '[') to 3 + } + // Row 4: { to ~ (ASCII 123-126) + for (c in '{'..'~') { + charMap[c.code] = (c - '{') to 4 + } + // Row 5: : to @ (ASCII 58-64) + for (c in ':'..'@') { + charMap[c.code] = (c - ':') to 5 + } + + val bank = FontBank( + name = "ascii", + charWidth = 4, + charHeight = 4, + x = 0, + y = 0, + charMap = charMap, + ) + + return FontDescriptor( + name = "boot", + spritesheet = "_boot", + spaceWidth = 4, + lineHeight = 6, + banks = listOf(bank), + ) + } + } +} + +/** + * KMP-compatible codepoint iteration with surrogate pair handling. + * Skips variation selectors (U+FE0E, U+FE0F). + */ +inline fun String.forEachCodepoint(action: (codepoint: Int) -> Unit) { + var i = 0 + while (i < length) { + val c = this[i] + val codepoint = if (c.isHighSurrogate() && i + 1 < length && this[i + 1].isLowSurrogate()) { + val low = this[i + 1] + i += 2 + ((c.code - 0xD800) shl 10) + (low.code - 0xDC00) + 0x10000 + } else { + i++ + c.code + } + if (codepoint != 0xFE0E && codepoint != 0xFE0F) { + action(codepoint) + } + } +} + +/** + * Shared text rendering function using a FontDescriptor and boot spritesheet. + * Handles newlines, spaces, regular characters, and accent fallback. + */ +fun renderText( + descriptor: FontDescriptor, + spritesheet: SpriteSheet, + str: String, + x: Int, + y: Int, + color: Int, + virtualFrameBuffer: VirtualFrameBuffer, +) { + var currentX = x + var currentY = y + + str.forEachCodepoint { codepoint -> + when (codepoint) { + '\n'.code -> { + currentY += descriptor.lineHeight + currentX = x + } + ' '.code -> { + currentX += descriptor.spaceWidth + } + else -> { + val resolved = descriptor.resolve(codepoint) + ?: resolveAccentFallback(descriptor, codepoint) + if (resolved != null) { + virtualFrameBuffer.drawMonocolor( + spritesheet, + color, + resolved.sourceX, + resolved.sourceY, + resolved.charWidth, + resolved.charHeight, + currentX, + currentY, + flipX = false, + flipY = false, + ) + currentX += resolved.charWidth + } + } + } + } +} + +private fun resolveAccentFallback( + descriptor: FontDescriptor, + codepoint: Int, +): CharResolution? { + val char = codepoint.toChar() + if (!char.isLetter()) return null + val base = ACCENT_MAP[char.lowercaseChar()] ?: return null + // Try lowercase base + return descriptor.resolve(base.code) + // Try uppercase base + ?: descriptor.resolve(base.uppercaseChar().code) +} + +fun buildCharMap(characters: List): Map> { + val map = mutableMapOf>() + characters.forEachIndexed { row, line -> + var col = 0 + line.forEachCodepoint { codepoint -> + map[codepoint] = col to row + col++ + } + } + return map +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameConfig.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameConfig.kt new file mode 100644 index 00000000..e35edfb2 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameConfig.kt @@ -0,0 +1,85 @@ +package com.github.minigdx.tiny.engine + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonClassDiscriminator + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +@JsonClassDiscriminator("version") +sealed class GameConfig { + abstract val name: String + abstract val id: String + + abstract fun toGameOptions(): GameOptions + + companion object { + private val JSON = Json { ignoreUnknownKeys = true } + + fun parse(jsonString: String): GameConfig = JSON.decodeFromString(jsonString) + } +} + +@Serializable +data class GameConfigSize(val width: Int, val height: Int) + +@Serializable +data class GameConfigFontBank( + val name: String, + val width: Int, + val height: Int, + val characters: List, + val x: Int = 0, + val y: Int = 0, +) + +@Serializable +data class GameConfigFont( + val name: String, + val spritesheet: String, + val spaceWidth: Int? = null, + val banks: List, +) + +@SerialName("V1") +@Serializable +data class GameConfigV1( + override val name: String, + override val id: String, + val resolution: GameConfigSize, + val sprites: GameConfigSize, + val zoom: Int, + val colors: List, + val scripts: List = emptyList(), + val spritesheets: List = emptyList(), + val levels: List = emptyList(), + val sound: String? = null, + val hideMouseCursor: Boolean = false, + /** + * Custom boot script to use instead of the default boot.lua. + * This script should exist in the game directory. + * When set, this script will be used as the first script to run. + */ + val bootScript: String? = null, + val fonts: List = emptyList(), +) : GameConfig() { + override fun toGameOptions(): GameOptions = + GameOptions( + width = resolution.width, + height = resolution.height, + palette = colors, + spriteSize = sprites.width to sprites.height, + gameScripts = scripts, + spriteSheets = spritesheets, + gameLevels = levels, + zoom = zoom, + sound = sound, + hideMouseCursor = hideMouseCursor, + bootScript = bootScript, + fonts = fonts.map { font -> + FontDescriptor.fromConfig(font) + }, + ) +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt index bbeef9d2..93b2eb29 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameEngine.kt @@ -31,6 +31,8 @@ class GameEngine( private var accumulator: Seconds = 0f private var currentFrame: Long = 0L + private var previousFrame: Long = 0L + private var effectiveMaxFrames: Long = gameOptions.maxFrames private var currentMetrics: PerformanceMetrics? = null lateinit var inputHandler: InputHandler @@ -114,6 +116,10 @@ class GameEngine( accumulator -= REFRESH_LIMIT currentFrame++ + if (effectiveMaxFrames > 0 && currentFrame >= effectiveMaxFrames) { + platform.endGameLoop() + } + interceptUserShortcup() currentMetrics?.run { storeFrameMetrics(this) } @@ -139,6 +145,11 @@ class GameEngine( // Complete frame monitoring and get metrics currentMetrics = performanceMonitor.frameEnd() + + if (currentFrame != previousFrame) { + platform.newFrameRendered(virtualFrameBuffer) + previousFrame = currentFrame + } } private suspend fun advanceEngineScript() { @@ -297,6 +308,20 @@ class GameEngine( } } + /** + * Reset the frame counter to 0 and optionally set a new max frames limit. + * + * Used by the record command to restart counting after the boot animation + * so that [maxFrames] counts only game frames. + */ + fun resetFrameCounter(maxFrames: Long? = null) { + currentFrame = 0 + previousFrame = 0 + if (maxFrames != null) { + effectiveMaxFrames = maxFrames + } + } + override fun end() { soundManager.destroy() } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameOptions.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameOptions.kt index ef6dd2c3..dcb7d790 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameOptions.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameOptions.kt @@ -9,9 +9,9 @@ import com.github.minigdx.tiny.input.Vector2 data class GameOptions( val width: Pixel, val height: Pixel, - val palette: List, - val gameScripts: List, - val spriteSheets: List, + val palette: List = emptyList(), + val gameScripts: List = emptyList(), + val spriteSheets: List = emptyList(), val gameLevels: List = emptyList(), val sound: String? = null, val zoom: Int = 2, @@ -19,6 +19,12 @@ data class GameOptions( val gutter: Pair = 10 to 10, val spriteSize: Pair = 8 to 8, val hideMouseCursor: Boolean = false, + val bootScript: String? = null, + val icon: String? = null, + val fonts: List = emptyList(), + val headless: Boolean = false, + // 0 = unlimited + val maxFrames: Long = 0L, ) : MouseProject { init { require(width > 0) { "The width needs to be a positive number." } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceAccess.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceAccess.kt index d37a57b1..9f219db0 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceAccess.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceAccess.kt @@ -56,4 +56,14 @@ interface GameResourceAccess { fun findSound(name: String): Sound? fun findGameScript(name: String): GameScript? + + /** + * Access a font spritesheet by its index. + */ + fun findFontSpritesheet(index: Int): SpriteSheet? + + /** + * Find a font spritesheet by its name. + */ + fun findFontSpritesheet(name: String): SpriteSheet? } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceProcessor.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceProcessor.kt index fd19fdcc..3046401d 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceProcessor.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/engine/GameResourceProcessor.kt @@ -12,6 +12,7 @@ import com.github.minigdx.tiny.resources.ResourceType import com.github.minigdx.tiny.resources.ResourceType.BOOT_GAMESCRIPT import com.github.minigdx.tiny.resources.ResourceType.BOOT_SPRITESHEET import com.github.minigdx.tiny.resources.ResourceType.ENGINE_GAMESCRIPT +import com.github.minigdx.tiny.resources.ResourceType.FONT_SPRITESHEET import com.github.minigdx.tiny.resources.ResourceType.GAME_GAMESCRIPT import com.github.minigdx.tiny.resources.ResourceType.GAME_LEVEL import com.github.minigdx.tiny.resources.ResourceType.GAME_SOUND @@ -39,6 +40,7 @@ class GameResourceProcessor( private var spriteSheets: Array private val levels: Array private val sounds: Array + private val fontSheets: Array override var bootSpritesheet: SpriteSheet? = null private set @@ -88,19 +90,32 @@ class GameResourceProcessor( Sound(0, 0, "default-sound", SoundData.DEFAULT_EMPTY) } + val fontSpritesheets = gameOptions.fonts.mapIndexed { index, font -> + resourceFactory.fontSpritesheet(index, font.spritesheet) + } + this.fontSheets = Array(fontSpritesheets.size) { null } + + val bootScriptFlow = if (gameOptions.bootScript != null) { + resourceFactory.customBootScript(gameOptions.bootScript) + } else { + resourceFactory.bootscript("_boot.lua") + } + val bootScriptName = gameOptions.bootScript ?: "_boot.lua" + resources = listOf( - resourceFactory.bootscript("_boot.lua"), + bootScriptFlow, resourceFactory.enginescript("_engine.lua"), resourceFactory.bootSpritesheet("_boot.png"), - ) + gameScripts + spriteSheets + gameLevels + listOfNotNull(sounds) + ) + gameScripts + spriteSheets + gameLevels + listOfNotNull(sounds) + fontSpritesheets toBeLoaded.addAll( - setOf("_boot.lua", "_engine.lua", "_boot.png"), + setOf(bootScriptName, "_engine.lua", "_boot.png"), ) toBeLoaded.addAll(gameOptions.gameLevels) toBeLoaded.addAll(gameOptions.gameScripts) toBeLoaded.addAll(gameOptions.spriteSheets) gameOptions.sound?.let { toBeLoaded.add(it) } + toBeLoaded.addAll(gameOptions.fonts.map { it.spritesheet }) numberOfResources = resources.size logger.debug("GAME_ENGINE") { "Number of resources to load: $numberOfResources" } @@ -160,6 +175,7 @@ class GameResourceProcessor( GAME_SPRITESHEET -> loadGameSpriteSheet(resource) GAME_LEVEL -> loadGameLevel(resource) GAME_SOUND -> loadGameSound(resource) + FONT_SPRITESHEET -> loadFontSpriteSheet(resource) PRIMITIVE_SPRITESHEET -> Unit } } @@ -183,6 +199,13 @@ class GameResourceProcessor( spritesheetToBind.addAll(gameLevel.tilesset.values) } + private fun loadFontSpriteSheet(resource: GameResource) { + val spriteSheet = resource as SpriteSheet + spriteSheet.textureUnit = fontSheets[resource.index]?.textureUnit + fontSheets[resource.index] = spriteSheet + spritesheetToBind.add(spriteSheet) + } + private fun loadGameSpriteSheet(resource: GameResource) { val spriteSheet = resource as SpriteSheet // Copy the texture unit used by the current spritesheet @@ -285,6 +308,14 @@ class GameResourceProcessor( return scripts.find { it?.name == name } } + override fun findFontSpritesheet(index: Int): SpriteSheet? { + return fontSheets.atIndex(index) + } + + override fun findFontSpritesheet(name: String): SpriteSheet? { + return fontSheets.find { it?.name == name } + } + fun status(): Map> { return gameResourceCollector.status() } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/file/SoundDataSourceStream.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/file/SoundDataSourceStream.kt index ffd98b23..6d166c2d 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/file/SoundDataSourceStream.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/file/SoundDataSourceStream.kt @@ -2,7 +2,13 @@ package com.github.minigdx.tiny.file import com.github.minigdx.tiny.platform.SoundData import com.github.minigdx.tiny.sound.Music +import com.github.minigdx.tiny.sound.MusicGenerator +import com.github.minigdx.tiny.sound.MusicalBar +import com.github.minigdx.tiny.sound.MusicalSequence import com.github.minigdx.tiny.sound.SoundManager +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope class SoundDataSourceStream( private val name: String, @@ -30,11 +36,57 @@ class SoundDataSourceStream( sequence.tracks.forEach { track -> track.instrument = music.instruments[track.instrumentIndex] } + sequence.configuration?.let { config -> + MusicGenerator.generate(sequence, config) + } } - val sounds = music.musicalBars.map { bar -> soundManager.convert(bar) } + // Synthesize bars and sequences in parallel. + // Each task gets a copied instrument to avoid shared mutable state + // (NOISE wave type has filter state in Instrument). + val (sounds, sequences) = coroutineScope { + val soundsDeferred = music.musicalBars.map { bar -> + async { + val isolatedBar = MusicalBar( + index = bar.index, + instrumentIndex = bar.instrumentIndex, + tempo = bar.tempo, + name = bar.name, + volume = bar.volume, + ) + isolatedBar.instrument = bar.instrument?.copyWithFreshState() + isolatedBar.setNotes(bar.beats) + soundManager.convert(isolatedBar) + } + } + val sequencesDeferred = music.sequences.map { sequence -> + async { + // Create an isolated copy with fresh instrument state per track + val isolatedTracks = sequence.tracks.map { track -> + MusicalSequence.Track( + index = track.index, + instrumentIndex = track.instrumentIndex, + mute = track.mute, + volume = track.volume, + ).also { + it.instrument = track.instrument?.copyWithFreshState() + it.beats.clear() + it.beats.addAll(track.beats) + } + }.toTypedArray() + val isolatedSequence = MusicalSequence( + index = sequence.index, + tracks = isolatedTracks, + tempo = sequence.tempo, + name = sequence.name, + ) + soundManager.convert(isolatedSequence) + } + } + soundsDeferred.awaitAll() to sequencesDeferred.awaitAll() + } - return SoundData(name, music, sounds) + return SoundData(name, music, sounds, sequences) } override fun wasModified(): Boolean = delegate.wasModified() diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/graphic/FrameBufferParameters.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/graphic/FrameBufferParameters.kt index 28b42d4d..a845dabd 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/graphic/FrameBufferParameters.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/graphic/FrameBufferParameters.kt @@ -10,11 +10,6 @@ class Blender(internal val gamePalette: ColorPalette) { internal var dithering: Int = 0xFFFF - internal val hasDithering: Boolean - get() { - return dithering != 0xFFFF - } - fun palette(): Array { fun cache(): Array { val copyOf = switch.copyOf() @@ -24,6 +19,11 @@ class Blender(internal val gamePalette: ColorPalette) { return cachedPaletteReference ?: cache() } + fun color(color: ColorIndex): ColorIndex { + val currentPalette = palette() + return currentPalette[color % currentPalette.size] + } + fun dither(pattern: Int): Int { val prec = dithering dithering = pattern and 0xFFFF @@ -42,30 +42,6 @@ class Blender(internal val gamePalette: ColorPalette) { cachedPaletteReference = null switch[gamePalette.check(source)] = gamePalette.check(target) } - - fun mix( - colors: ByteArray, - x: Pixel, - y: Pixel, - transparency: Array?, - ): ByteArray? { - fun dither(pattern: Int): Boolean { - val a = x % 4 - val b = (y % 4) * 4 - - return (pattern shr (15 - (a + b))) and 0x01 == 0x01 - } - - val color = gamePalette.check(colors[0].toInt()) - colors[0] = switch[color].toByte() - // Return null if transparent - if (transparency == null && gamePalette.isTransparent(colors[0].toInt())) return null - return if (!dither(dithering)) { - null - } else { - colors - } - } } class Camera() { diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/input/TouchManager.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/input/TouchManager.kt index 416f637c..dccf60bb 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/input/TouchManager.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/input/TouchManager.kt @@ -165,6 +165,35 @@ class TouchManager(lastKeyCode: KeyCode) { queueEvents.add(event) } + /** + * Reset all input state. + * + * This is useful when the window loses focus to avoid + * keys or touches being stuck in a pressed state. + */ + fun resetAllState() { + for (i in touch.indices) { + touch[i] = null + } + for (i in justTouch.indices) { + justTouch[i] = null + } + for (i in type.indices) { + type[i] = null + } + for (i in keyPressed.indices) { + keyPressed[i] = false + } + for (i in justKeyPressed.indices) { + justKeyPressed[i] = false + } + justPressedKeyCode.clear() + isAnyKeyJustPressed = false + numberOfKeyPressed = 0 + eventsPool.free(queueEvents) + queueEvents.clear() + } + /** * Process received touch events * diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ConsoleLib.kt similarity index 82% rename from tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLib.kt rename to tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ConsoleLib.kt index db357bf1..6161f080 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ConsoleLib.kt @@ -12,18 +12,17 @@ import org.luaj.vm2.Varargs import org.luaj.vm2.lib.TwoArgFunction import org.luaj.vm2.lib.VarArgFunction -@TinyLib("debug", "Helpers to debug your game by drawing or printing information on screen.") -class DebugLib(private val logger: Logger) : TwoArgFunction() { +@TinyLib("console", "Helpers to log information in the console.", icon = "terminal") +class ConsoleLib(private val logger: Logger) : TwoArgFunction() { override fun call( arg1: LuaValue, arg2: LuaValue, ): LuaValue { val tiny = LuaTable() - // TODO: move it into console.log instead of debug.console ? - tiny["console"] = console() + tiny["log"] = log() - arg2["debug"] = tiny - arg2["package"]["loaded"]["debug"] = tiny + arg2["console"] = tiny + arg2["package"]["loaded"]["console"] = tiny return tiny } @@ -52,7 +51,7 @@ class DebugLib(private val logger: Logger) : TwoArgFunction() { } @TinyFunction("Log a message into the console.", example = DEBUG_EXAMPLE) - internal inner class console : VarArgFunction() { + internal inner class log : VarArgFunction() { @TinyCall("Log a message into the console.") override fun invoke( @TinyArg("str", type = LuaType.ANY) args: Varargs, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/CtrlLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/CtrlLib.kt index 0916b3c7..ce75b762 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/CtrlLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/CtrlLib.kt @@ -16,6 +16,7 @@ import org.luaj.vm2.lib.TwoArgFunction @TinyLib( "ctrl", "Access to controllers like touch/mouse events or accessing which key is pressed by the user.", + icon = "keyboard", ) class CtrlLib( private val inputHandler: InputHandler, @@ -78,16 +79,33 @@ class CtrlLib( @TinyFunction( "Return true if the key was pressed during the last frame. " + + "When called without argument, return a table of all keys pressed during the last frame " + + "(values matching the keys lib), or false if no key was pressed. " + "If you need to check that the key is still pressed, see `ctrl.pressing` instead.", example = CTRL_PRESSING_EXAMPLE, ) inner class pressed : OneArgFunction() { private val values = Key.entries.toTypedArray() + @TinyCall("Get all keys just pressed as a table of key values, or false if none.") + override fun call(): LuaValue { + val result = LuaTable() + var index = 1 + for (key in values) { + if (key == Key.ANY_KEY) continue + if (inputHandler.isKeyJustPressed(key)) { + result.rawset(index, valueOf(key.ordinal)) + index++ + } + } + return if (index > 1) result else BFALSE + } + @TinyCall("Is the key was pressed?") override fun call( @TinyArg("key", type = LuaType.NUMBER) arg: LuaValue, ): LuaValue { + if (arg.isnil()) return call() val int = arg.checkint() if (int >= values.size || int < 0) return BFALSE diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLibExamples.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLibExamples.kt index 976f146d..447729fc 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLibExamples.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/DebugLibExamples.kt @@ -3,11 +3,9 @@ package com.github.minigdx.tiny.lua //language=Lua const val DEBUG_EXAMPLE = """ function _update() - local pos = ctrl.touch() - - debug.table(pos) - debug.log("frame "..tiny.frame) - debug.console("hello from the console") + console.log("hello from the console") + console.log("Log a value: ", tiny.frame) + console.log("Log a table: ", ctrl.touch()) end function _draw() @@ -19,39 +17,3 @@ function _draw() shape.line(pos.x, pos.y - 2, pos.x, pos.y + 2, 3) end """ - -//language=Lua -const val DEBUG_ENABLED_EXAMPLE = """ -function _init() - switch = true -end - -function _update() - local pos = ctrl.touch() - - debug.rect(pos.x, pos.y, 16, 16) - - if ctrl.touched(0) then - switch = not switch - - debug.enabled(switch) - end - - debug.log("debug ".. tostring(switch)) -end - -function _draw() - gfx.cls() - -- draw the mouse position. - local pos = ctrl.touch() - - shape.line(pos.x - 2, pos.y, pos.x + 2, pos.y, 3) - shape.line(pos.x, pos.y - 2, pos.x, pos.y + 2, 3) - - if switch then - print("debug enabled", 10, 40) - else - print("debug disabled", 10, 40) - end -end -""" diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/FloppyLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/FloppyLib.kt index 9a8cfb84..0794565b 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/FloppyLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/FloppyLib.kt @@ -26,7 +26,7 @@ import org.luaj.vm2.LuaValue import org.luaj.vm2.lib.OneArgFunction import org.luaj.vm2.lib.TwoArgFunction -@TinyLib("floppy", "Floppy allow you to get or save user Lua structure.") +@TinyLib("floppy", "Floppy allow you to get or save user Lua structure.", icon = "save") class FloppyLib( private val platform: Platform, private val logger: Logger, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt index ffa0441e..2916d53c 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/GfxLib.kt @@ -24,6 +24,7 @@ import kotlin.math.min @TinyLib( "gfx", "Access to graphical API like updating the color palette or applying a dithering pattern.", + icon = "palette", ) class GfxLib( private val resourceAccess: GameResourceAccess, @@ -99,12 +100,17 @@ class GfxLib( } } - @TinyFunction("clear the screen", example = GFX_CLS_EXAMPLE) + @TinyFunction( + "Clear the screen. " + + "When called without arguments, clears with the color closest to black (#000000) in the palette. " + + "To ensure visibility, always draw with a color index DIFFERENT from the cls() color.", + example = GFX_CLS_EXAMPLE, + ) internal inner class cls : OneArgFunction() { - @TinyCall("Clear the screen with a default color.") + @TinyCall("Clear the screen with the color closest to black (#000000) in the palette.") override fun call(): LuaValue = super.call() - @TinyCall("Clear the screen with a color.") + @TinyCall("Clear the screen with the given color index (1 to N). Color 0 clears to transparent.") override fun call( @TinyArg("color", type = LuaType.NUMBER) arg: LuaValue, ): LuaValue { @@ -190,7 +196,11 @@ class GfxLib( } private fun getIndexAndName(arg: LuaValue): Pair { - return if (arg.isstring()) { + return if (arg.isint()) { + val index = arg.toint() + val spriteSheet = resourceAccess.findSpritesheet(index) + index to (spriteSheet?.name ?: "frame_buffer_$index") + } else if (arg.isstring()) { val name = arg.tojstring() val existing = resourceAccess.findSpritesheet(name) val index = existing?.index ?: resourceAccess.newSpritesheetIndex() @@ -216,8 +226,8 @@ class GfxLib( @TinyCall("Replace the color a for the color b.") override fun call( - a: LuaValue, - b: LuaValue, + @TinyArg("a", type = LuaType.NUMBER) a: LuaValue, + @TinyArg("b", type = LuaType.NUMBER) b: LuaValue, ): LuaValue { virtualFrameBuffer.swapPalette(a.checkint(), b.checkint()) return NONE diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/JuiceLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/JuiceLib.kt index b1e56d85..e09c82ac 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/JuiceLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/JuiceLib.kt @@ -29,6 +29,7 @@ import org.luaj.vm2.lib.TwoArgFunction "- exp10, expIn10, expOut10,\n" + "- exp5, expIn5, expOut5,\n" + "- linear ", + icon = "sparkles", ) class JuiceLib : TwoArgFunction() { @TinyFunction(name = "pow2", example = JUICE_EXAMPLE) diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt index 0bb1d1b0..c1c37fd4 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/KeysLib.kt @@ -14,6 +14,7 @@ import org.luaj.vm2.lib.TwoArgFunction "- `keys.up`, `keys.down`, `keys.left`, `keys.right` for directions.\n" + "- `keys.a` to `keys.z` and `keys.0` to `keys.9` for letters and numbers.\n" + "- `keys.space` and `keys.enter` for other keys.\n", + icon = "command", ) class KeysLib : TwoArgFunction() { @TinyVariable("a", "the key a", hideInDocumentation = true) diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/LuaErrorExts.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/LuaErrorExts.kt index 5ab7a259..67596787 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/LuaErrorExts.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/LuaErrorExts.kt @@ -25,7 +25,7 @@ fun LuaError.fromErrorLine(): Int? { fun LuaError.fromMessage(): Int? { val msg = message ?: return null - val pattern = """\[[\s\S]*]:(\d+):.*""".toRegex() + val pattern = """\[[\s\S]*\]:(\d+):.*""".toRegex() val match = pattern.matchEntire(msg) return match?.groupValues?.get(1)?.toIntOrNull() } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt index 7b37ff7d..2dfd8ba4 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MapLib.kt @@ -51,6 +51,7 @@ import kotlin.math.floor @TinyLib( "map", "Access map created with LDTk ( https://ldtk.io/ ).", + icon = "map", ) class MapLib( private val resourceAccess: GameResourceAccess, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MathLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MathLib.kt index a1faea5a..66d7ab10 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MathLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/MathLib.kt @@ -20,6 +20,7 @@ import kotlin.random.Random @TinyLib( "math", "Math functions. Please note that standard Lua math methods are also available.", + icon = "calculator", ) class MathLib : org.luaj.vm2.lib.MathLib() { @TinyVariable("pi", "value of pi (~3.14)") diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/NotesLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/NotesLib.kt index 655407df..5a9c886c 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/NotesLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/NotesLib.kt @@ -218,6 +218,7 @@ enum class Note(val frequency: Frequency, val index: Int) { "notes", "List all notes from C0 to B8. " + "Please note that bemols are the note with b (ie: Gb2) while sharps are the note with s (ie: As3).", + icon = "music", ) class NotesLib : TwoArgFunction() { override fun call( diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SfxLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SfxLib.kt index 8436b6c9..63b2446c 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SfxLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SfxLib.kt @@ -5,6 +5,7 @@ import com.github.mingdx.tiny.doc.TinyFunction import com.github.mingdx.tiny.doc.TinyLib import com.github.minigdx.tiny.engine.GameResourceAccess import com.github.minigdx.tiny.lua.sfx.InstrumentLuaWrapper +import com.github.minigdx.tiny.lua.sfx.SequenceLuaWrapper import com.github.minigdx.tiny.lua.sfx.SfxLuaWrapper import com.github.minigdx.tiny.platform.Platform import com.github.minigdx.tiny.sound.Instrument @@ -20,6 +21,7 @@ import org.luaj.vm2.lib.ZeroArgFunction @TinyLib( "sfx", """TODO""", + icon = "audio-waveform", ) class SfxLib( private val resourceAccess: GameResourceAccess, @@ -36,6 +38,7 @@ class SfxLib( ctrl.set("instrument", instrument()) ctrl.set("sfx", sfx()) + ctrl.set("sequence", sequence()) ctrl.set("save", save()) @@ -124,6 +127,20 @@ class SfxLib( } } + @TinyFunction("Access musical sequence using its index.") + inner class sequence : OneArgFunction() { + @TinyCall("Access musical sequence using its index.") + override fun call(arg: LuaValue): LuaValue { + val sound = resourceAccess.findSound(0) ?: return NIL + val music = sound.data.music + + val index = arg.checkint() + return music.sequences + .getOrNull(index) + ?.let { SequenceLuaWrapper(music, it, soundBoard, platform) } ?: NIL + } + } + private fun LuaValue.asInstrumentIndex(music: Music): Int? { return if (this.isint()) { val index = this.checkint() diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ShapeLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ShapeLib.kt index aade6249..1d67af7e 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ShapeLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/ShapeLib.kt @@ -59,10 +59,11 @@ private class Shape(private val gameOptions: GameOptions) { } private fun LuaValue.checkColorIndex(): Int { + val colors = gameOptions.colors() return if (this.isnumber()) { - this.checkint() + colors.check(this.checkint()) } else { - gameOptions.colors().getColorIndex(this.checkjstring()!!) + colors.getColorIndex(this.checkjstring()!!) } } } @@ -72,6 +73,7 @@ private class Shape(private val gameOptions: GameOptions) { "Shape API to draw...shapes. " + "Those shapes can be circle, rectangle, line or oval." + "All shapes can be draw filed or not filed.", + icon = "pentagon", ) class ShapeLib( private val gameOptions: GameOptions, @@ -123,15 +125,15 @@ class ShapeLib( @TinyCall("Draw a rectangle.") override fun call( - @TinyArg("rect", "A rectangle {x, y, width, height, color}") a: LuaValue, + @TinyArg("rect", "A rectangle {x, y, width, height, color}", type = LuaType.TABLE) a: LuaValue, ): LuaValue { return super.call(a) } @TinyCall("Draw a rectangle using a rectangle and a color.") override fun call( - @TinyArg("rect", "A rectangle {x, y, width, height}") a: LuaValue, - @TinyArg("color") b: LuaValue, + @TinyArg("rect", "A rectangle {x, y, width, height}", type = LuaType.TABLE) a: LuaValue, + @TinyArg("color", type = LuaType.NUMBER) b: LuaValue, ): LuaValue = super.call(a, b) } @@ -154,23 +156,24 @@ class ShapeLib( @TinyCall("Draw a filled rectangle.") override fun call( - @TinyArg("rect", "A rectangle {x, y, width, height, color}") a: LuaValue, + @TinyArg("rect", "A rectangle {x, y, width, height, color}", type = LuaType.TABLE) a: LuaValue, ): LuaValue { return super.call(a) } @TinyCall("Draw a filled rectangle using a rectangle and a color.") override fun call( - @TinyArg("rect", "A rectangle {x, y, width, height}") a: LuaValue, - @TinyArg("color") b: LuaValue, + @TinyArg("rect", "A rectangle {x, y, width, height}", type = LuaType.TABLE) a: LuaValue, + @TinyArg("color", type = LuaType.NUMBER) b: LuaValue, ): LuaValue = super.call(a, b) } private fun LuaValue.checkColorIndex(): Int { + val colors = gameOptions.colors() return if (this.isnumber()) { - this.checkint() + colors.check(this.checkint()) } else { - gameOptions.colors().getColorIndex(this.checkjstring()!!) + colors.getColorIndex(this.checkjstring()!!) } } @@ -178,10 +181,10 @@ class ShapeLib( internal inner class circlef : LibFunction() { @TinyCall("Draw a circle at the coordinate (centerX, centerY) with the radius and the color.") override fun call( - @TinyArg("centerX") a: LuaValue, - @TinyArg("centerY") b: LuaValue, - @TinyArg("radius") c: LuaValue, - @TinyArg("color") d: LuaValue, + @TinyArg("centerX", type = LuaType.NUMBER) a: LuaValue, + @TinyArg("centerY", type = LuaType.NUMBER) b: LuaValue, + @TinyArg("radius", type = LuaType.NUMBER) c: LuaValue, + @TinyArg("color", type = LuaType.NUMBER) d: LuaValue, ): LuaValue { val centerX = a.checkint() val centerY = b.checkint() @@ -237,10 +240,10 @@ class ShapeLib( @TinyCall("Draw a line with a default color.") override fun call( - @TinyArg("x0") a: LuaValue, - @TinyArg("y0") b: LuaValue, - @TinyArg("x1") c: LuaValue, - @TinyArg("y1") d: LuaValue, + @TinyArg("x0", type = LuaType.NUMBER) a: LuaValue, + @TinyArg("y0", type = LuaType.NUMBER) b: LuaValue, + @TinyArg("x1", type = LuaType.NUMBER) c: LuaValue, + @TinyArg("y1", type = LuaType.NUMBER) d: LuaValue, ): LuaValue { val args: Array = arrayOf(a, b, c, d, valueOf("#FFFFFF")) invoke(args) @@ -252,19 +255,19 @@ class ShapeLib( internal inner class circle : LibFunction() { @TinyCall("Draw a circle with the default color.") override fun call( - a: LuaValue, - b: LuaValue, - c: LuaValue, + @TinyArg("a", type = LuaType.NUMBER) a: LuaValue, + @TinyArg("b", type = LuaType.NUMBER) b: LuaValue, + @TinyArg("c", type = LuaType.NUMBER) c: LuaValue, ): LuaValue { return call(a, b, c, valueOf("#FFFFFF")) } @TinyCall("Draw a circle.") override fun call( - @TinyArg("centerX") a: LuaValue, - @TinyArg("centerY") b: LuaValue, - @TinyArg("radius") c: LuaValue, - @TinyArg("color") d: LuaValue, + @TinyArg("centerX", type = LuaType.NUMBER) a: LuaValue, + @TinyArg("centerY", type = LuaType.NUMBER) b: LuaValue, + @TinyArg("radius", type = LuaType.NUMBER) c: LuaValue, + @TinyArg("color", type = LuaType.NUMBER) d: LuaValue, ): LuaValue { val centerX = a.checkint() val centerY = b.checkint() @@ -284,7 +287,10 @@ class ShapeLib( inner class trianglef : LibFunction() { @TinyCall("Draw a filled triangle using the coordinates of (x1, y1), (x2, y2) and (x3, y3).") override fun invoke( - @TinyArgs(["x1", "y1", "x2", "y2", "x3", "y3", "color"]) args: Varargs, + @TinyArgs( + names = ["x1", "y1", "x2", "y2", "x3", "y3", "color"], + types = [LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER], + ) args: Varargs, ): Varargs { if (args.narg() < 7) throw LuaError("Expected 7 args") @@ -310,7 +316,10 @@ class ShapeLib( @TinyCall("Draw a triangle using the coordinates of (x1, y1), (x2, y2) and (x3, y3).") override fun invoke( - @TinyArgs(["x1", "y1", "x2", "y2", "x3", "y3", "color"]) args: Varargs, + @TinyArgs( + names = ["x1", "y1", "x2", "y2", "x3", "y3", "color"], + types = [LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER], + ) args: Varargs, ): Varargs { if (args.narg() < 7) throw LuaError("Expected 7 args") @@ -355,7 +364,10 @@ class ShapeLib( @TinyCall("Draw a gradient using dithering, only from color c1 to color c2.") override fun invoke( - @TinyArgs(["x", "y", "width", "height", "color1", "color2", "is_horizontal"]) args: Varargs, + @TinyArgs( + names = ["x", "y", "width", "height", "color1", "color2", "is_horizontal"], + types = [LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.NUMBER, LuaType.BOOLEAN], + ) args: Varargs, ): Varargs { if (args.narg() < 6) throw LuaError("Expected 6 args") diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SoundLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SoundLib.kt index e563956c..e1bee015 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SoundLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SoundLib.kt @@ -7,8 +7,6 @@ import com.github.mingdx.tiny.doc.TinyFunction import com.github.mingdx.tiny.doc.TinyLib import com.github.minigdx.tiny.engine.GameResourceAccess import com.github.minigdx.tiny.sound.Instrument -import com.github.minigdx.tiny.sound.MusicalBar -import com.github.minigdx.tiny.sound.MusicalSequence import com.github.minigdx.tiny.sound.VirtualSoundBoard import org.luaj.vm2.LuaTable import org.luaj.vm2.LuaValue @@ -26,6 +24,7 @@ Avoid to start a music or a sound at the beginning of the game. Before it, force the player to hit a key or click by adding an interactive menu or by starting the sound as soon as the player is moving. """, + icon = "volume-2", ) class SoundLib( private val resourceAccess: GameResourceAccess, @@ -59,17 +58,21 @@ class SoundLib( arg2: LuaValue, ): LuaValue { val loop = arg2.optboolean(false) - val sfx = getSfx(arg1.checkint()) + val index = arg1.checkint() val result = WrapperLuaTable() - if (playSound && sfx != null) { - val handler = soundBoard.prepare(sfx) + val sound = resourceAccess.findSound(0) + val buffer = sound?.data?.musicalBars?.getOrNull(index) + if (playSound && buffer != null) { + val handler = soundBoard.createHandler(buffer) result.function0("stop") { handler.stop() NONE } + result.wrap("playing") { valueOf(handler.isPlaying()) } + if (loop) { handler.loop() } else { @@ -79,6 +82,7 @@ class SoundLib( result.function0("stop") { NONE } + result.wrap("playing") { valueOf(false) } } return result } @@ -94,15 +98,19 @@ class SoundLib( arg2: LuaValue, ): LuaValue { val loop = arg2.optboolean(false) - val music = getMusic(arg1.checkint()) + val index = arg1.checkint() val result = WrapperLuaTable() - if (playSound && music != null) { - val handler = soundBoard.prepare(music) + val sound = resourceAccess.findSound(0) + val buffer = sound?.data?.musicalSequences?.getOrNull(index) + if (playSound && buffer != null) { + val handler = soundBoard.createHandler(buffer) result.function0("stop") { handler.stop() NONE } + result.wrap("playing") { valueOf(handler.isPlaying()) } + if (loop) { handler.loop() } else { @@ -112,6 +120,7 @@ class SoundLib( result.function0("stop") { NONE } + result.wrap("playing") { valueOf(false) } } return result @@ -153,30 +162,4 @@ class SoundLib( null } } - - private fun getSfx(index: Int): MusicalBar? { - val sfx = resourceAccess.findSound(0) - ?.data - ?.music - ?.musicalBars ?: return null - - return if (index in 0 until sfx.size) { - sfx[index] - } else { - null - } - } - - private fun getMusic(index: Int): MusicalSequence? { - val music = resourceAccess.findSound(0) - ?.data - ?.music - ?.sequences ?: return null - - return if (index in 0 until music.size) { - music[index] - } else { - null - } - } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SprLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SprLib.kt index 9206915e..f9f43232 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SprLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/SprLib.kt @@ -18,7 +18,7 @@ import org.luaj.vm2.lib.OneArgFunction import org.luaj.vm2.lib.ThreeArgFunction import org.luaj.vm2.lib.TwoArgFunction -@TinyLib("spr", "Sprite API to draw or update sprites.") +@TinyLib("spr", "Sprite API to draw or update sprites.", icon = "image") class SprLib( val virtualFrameBuffer: VirtualFrameBuffer, val resourceAccess: GameResourceAccess, @@ -120,6 +120,8 @@ class SprLib( val previousSpriteSheet = currentSpritesheet currentSpritesheet = if (arg.isnil()) { 0 + } else if (arg.isint()) { + arg.checkint() } else if (arg.isstring()) { val spritesheet = resourceAccess.findSpritesheet(arg.tojstring()) spritesheet?.index ?: 0 diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLib.kt index 4cdc41a6..ae75cb28 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLib.kt @@ -6,8 +6,10 @@ import com.github.mingdx.tiny.doc.TinyArgs import com.github.mingdx.tiny.doc.TinyCall import com.github.mingdx.tiny.doc.TinyFunction import com.github.mingdx.tiny.doc.TinyLib +import com.github.minigdx.tiny.engine.FontDescriptor import com.github.minigdx.tiny.engine.GameOptions import com.github.minigdx.tiny.engine.GameResourceAccess +import com.github.minigdx.tiny.engine.renderText import com.github.minigdx.tiny.render.VirtualFrameBuffer import org.luaj.vm2.LuaTable import org.luaj.vm2.LuaValue @@ -16,12 +18,14 @@ import org.luaj.vm2.lib.LibFunction import org.luaj.vm2.lib.TwoArgFunction import org.luaj.vm2.lib.VarArgFunction -@TinyLib(description = "Standard library.") +@TinyLib(description = "Standard library.", icon = "book-open") class StdLib( val gameOptions: GameOptions, val resourceAccess: GameResourceAccess, val virtualFrameBuffer: VirtualFrameBuffer, ) : TwoArgFunction() { + private val bootFontDescriptor by lazy { FontDescriptor.createBootDescriptor() } + override fun call( arg1: LuaValue, arg2: LuaValue, @@ -54,12 +58,11 @@ class StdLib( @TinyArg("class", type = LuaType.TABLE) arg1: LuaValue, @TinyArg("default", type = LuaType.TABLE) arg2: LuaValue, ): LuaValue { - val default = - if (arg2.istable()) { - arg2.checktable()!!.deepCopy() - } else { - LuaTable() - } + val default = if (arg2.istable()) { + arg2.checktable()!!.deepCopy() + } else { + LuaTable() + } val reference = arg1.checktable()!!.deepCopy() default.setmetatable(reference) reference.rawset("__index", reference) @@ -70,14 +73,18 @@ class StdLib( val result = LuaTable() this.keys().forEach { key -> var value = this[key] - value = - if (value.istable()) { - value.checktable()!!.deepCopy() - } else { - value - } + value = if (value.istable()) { + value.checktable()!!.deepCopy() + } else { + value + } result[key] = value } + // Preserve the metatable during deep copy + val metatable = this.getmetatable() + if (metatable != null) { + result.setmetatable(metatable) + } return result } } @@ -101,7 +108,7 @@ class StdLib( val value = arg1.get(k) arg2[k] = value } - return arg2 + arg2 } else { NIL } @@ -203,10 +210,15 @@ class StdLib( } } - @TinyFunction("Print on the screen a string.", example = STD_PRINT_EXAMPLE) + @TinyFunction( + "Print on the screen a string. " + + "Default color is the closest to white (#FFFFFF) in the palette. " + + "To ensure visibility, use a color that contrasts with the cls() background color.", + example = STD_PRINT_EXAMPLE, + ) internal inner class print : LibFunction() { @TinyCall( - description = "print on the screen a string at (0,0) with a default color.", + description = "print on the screen a string at (0,0) with the default color (closest to white).", ) override fun call( @TinyArg("str", type = LuaType.STRING) a: LuaValue, @@ -215,7 +227,7 @@ class StdLib( } @TinyCall( - description = "print on the screen a string with a default color.", + description = "print on the screen a string with the default color (closest to white).", ) override fun call( @TinyArg("str", type = LuaType.STRING) a: LuaValue, @@ -226,13 +238,13 @@ class StdLib( } @TinyCall( - description = "print on the screen a string with a specific color.", + description = "print on the screen a string with a specific color index (1 to N).", ) override fun call( @TinyArg("str", type = LuaType.STRING) a: LuaValue, @TinyArg("x", type = LuaType.NUMBER) b: LuaValue, @TinyArg("y", type = LuaType.NUMBER) c: LuaValue, - @TinyArg("color", type = LuaType.ANY) d: LuaValue, + @TinyArg("color", type = LuaType.NUMBER) d: LuaValue, ): LuaValue { val spritesheet = resourceAccess.bootSpritesheet ?: return NONE val str = a.tojstring() @@ -240,93 +252,18 @@ class StdLib( val y = c.checkint() val color = d.checkColorIndex() - val space = 4 - var currentX = x - var currentY = y - str.forEach { char -> - - val coord = if (char.isLetter()) { - // The character has an accent. Let's try to get rid of it - val l = if (char.hasAccent) { - ACCENT_MAP[char.lowercaseChar()] ?: char.lowercaseChar() - } else { - char.lowercaseChar() - } - val index = l - 'a' - index to 0 - } else if (char.isDigit()) { - val index = char.lowercaseChar() - '0' - index to 1 - } else if (char in '!'..'/') { - val index = char.lowercaseChar() - '!' - index to 2 - } else if (char in '['..'`') { - val index = char.lowercaseChar() - '[' - index to 3 - } else if (char in '{'..'~') { - val index = char.lowercaseChar() - '{' - index to 4 - } else if (char in ':'..'@') { - val index = char.lowercaseChar() - ':' - index to 5 - } else if (char == '\n') { - currentY += 6 - currentX = x - space // compensate the next space - null - } else { - // Maybe it's an emoji: try EMOJI MAP conversion - EMOJI_MAP[char] - } - if (coord != null) { - val (indexX, indexY) = coord - - virtualFrameBuffer.drawMonocolor( - spritesheet, - color, - indexX * 4, - indexY * 4, - 4, - 4, - currentX, - currentY, - flipX = false, - flipY = false, - ) - } - currentX += space - } + renderText(bootFontDescriptor, spritesheet, str, x, y, color, virtualFrameBuffer) return NONE } - - val Char.hasAccent: Boolean - get() = this.isLetter() && this.lowercaseChar() !in 'a'..'z' } private fun LuaValue.checkColorIndex(): Int { + val colors = gameOptions.colors() return if (this.isnumber()) { - this.checkint() + colors.check(this.checkint()) } else { - gameOptions.colors().getColorIndex(this.checkjstring()!!) + colors.getColorIndex(this.checkjstring()!!) } } - - companion object { - val ACCENT_MAP = - mapOf( - 'à' to 'a', 'á' to 'a', 'â' to 'a', 'ã' to 'a', 'ä' to 'a', 'å' to 'a', - 'ç' to 'c', - 'è' to 'e', 'é' to 'e', 'ê' to 'e', 'ë' to 'e', - 'ì' to 'i', 'í' to 'i', 'î' to 'i', 'ï' to 'i', - 'ñ' to 'n', - 'ò' to 'o', 'ó' to 'o', 'ô' to 'o', 'õ' to 'o', 'ö' to 'o', - 'ù' to 'u', 'ú' to 'u', 'û' to 'u', 'ü' to 'u', - 'ý' to 'y', 'ÿ' to 'y', - ) - - val EMOJI_MAP = - mapOf( - '⚠' to (0 to 0), - ) - } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLibExamples.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLibExamples.kt index 36752fce..ee7d2972 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLibExamples.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/StdLibExamples.kt @@ -6,11 +6,11 @@ function _draw() gfx.cls() -- every character is a sprite 4x4 pixels. print("hello") - print("world", 10, 10) - print("how", 10, 20, 4) - print("are", 26, 20, 5) - print("you", 42, 20, 6) - print("...", 58, 20, math.rnd(10)) + print("world", 10, 8) + print("how", 10, 16, 4) + print("are", 28, 16, 5) + print("you", 46, 16, 6) + print("...", 64, 16, math.rnd(10)) end """ @@ -65,7 +65,11 @@ function _draw() local src = {x = 1, y = 2, z = 3} local dst = {a = 4, b = 5} local result = merge(src, dst) - debug.table(result) + local index = 1 + for k,v in pairs(result) do + print(k..":"..v , index * 4 * 8, 8) + index = index + 1 + end end """ @@ -76,6 +80,8 @@ function _draw() local src = {1, 2, 3} local dst = {4, 5} local result = append(src, dst) - debug.table(result) + for k,v in ipairs(result) do + print(v, (k + 1) * 8, 8) + end end """ diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TextLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TextLib.kt new file mode 100644 index 00000000..db5da512 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TextLib.kt @@ -0,0 +1,224 @@ +package com.github.minigdx.tiny.lua + +import com.github.mingdx.tiny.doc.LuaType +import com.github.mingdx.tiny.doc.TinyArg +import com.github.mingdx.tiny.doc.TinyCall +import com.github.mingdx.tiny.doc.TinyFunction +import com.github.mingdx.tiny.doc.TinyLib +import com.github.minigdx.tiny.engine.FontDescriptor +import com.github.minigdx.tiny.engine.GameOptions +import com.github.minigdx.tiny.engine.GameResourceAccess +import com.github.minigdx.tiny.engine.forEachCodepoint +import com.github.minigdx.tiny.engine.renderText +import com.github.minigdx.tiny.render.VirtualFrameBuffer +import org.luaj.vm2.LuaTable +import org.luaj.vm2.LuaValue +import org.luaj.vm2.Varargs +import org.luaj.vm2.lib.TwoArgFunction +import org.luaj.vm2.lib.VarArgFunction +import kotlin.math.max + +@TinyLib( + "text", + "Text rendering library for custom fonts. " + + "Allows selecting fonts configured in `_tiny.json` and rendering text with them. " + + "When no font is selected, uses the default boot font (same as `print()`).", + icon = "type", +) +class TextLib( + private val gameOptions: GameOptions, + private val resourceAccess: GameResourceAccess, + private val virtualFrameBuffer: VirtualFrameBuffer, +) : TwoArgFunction() { + private val bootFontDescriptor by lazy { FontDescriptor.createBootDescriptor() } + private var currentFontIndex: Int? = null + + override fun call( + arg1: LuaValue, + arg2: LuaValue, + ): LuaValue { + val func = LuaTable() + func["font"] = font() + func["print"] = print() + func["width"] = width() + + arg2["text"] = func + arg2["package"]["loaded"]["text"] = func + return func + } + + private fun currentFont(): FontDescriptor? { + val index = currentFontIndex ?: return null + return gameOptions.fonts.getOrNull(index) + } + + @TinyFunction( + "Select a font to use for text rendering. " + + "Call without arguments to reset to the default boot font.", + example = TEXT_FONT_EXAMPLE, + ) + inner class font : VarArgFunction() { + @TinyCall("Reset to the default boot font.") + override fun call(): LuaValue { + currentFontIndex = null + return NIL + } + + @TinyCall("Select a font by index.") + override fun call( + @TinyArg("index", type = LuaType.NUMBER) arg: LuaValue, + ): LuaValue { + if (arg.isnil()) { + currentFontIndex = null + } else if (arg.isnumber()) { + currentFontIndex = arg.checkint() + } else { + val name = arg.checkjstring()!! + val index = gameOptions.fonts.indexOfFirst { it.name == name } + if (index >= 0) { + currentFontIndex = index + } + } + return NIL + } + + override fun invoke(args: Varargs): Varargs { + if (args.narg() == 0 || args.isnil(1)) { + currentFontIndex = null + } else if (args.isnumber(1)) { + currentFontIndex = args.checkint(1) + } else { + val name = args.checkjstring(1)!! + val index = gameOptions.fonts.indexOfFirst { it.name == name } + if (index >= 0) { + currentFontIndex = index + } + } + return NIL + } + } + + @TinyFunction( + "Print text on the screen using the currently selected font.", + example = TEXT_PRINT_EXAMPLE, + ) + inner class print : VarArgFunction() { + @TinyCall("Print text at the given position with an optional color.") + override fun invoke(args: Varargs): Varargs { + val str = args.checkjstring(1) ?: return NIL + val x = args.optint(2, 0) + val y = args.optint(3, 0) + + val color = if (args.narg() >= 4 && !args.isnil(4)) { + checkColorIndex(args.arg(4)) + } else { + gameOptions.colors().getColorIndex("#FFFFFF") + } + + val font = currentFont() + if (font != null) { + renderCustomFont(str, x, y, color, font) + } else { + val spritesheet = resourceAccess.bootSpritesheet ?: return NIL + renderText(bootFontDescriptor, spritesheet, str, x, y, color, virtualFrameBuffer) + } + + return NIL + } + } + + @TinyFunction( + "Measure the width in pixels of a string using the currently selected font.", + example = TEXT_WIDTH_EXAMPLE, + ) + inner class width : VarArgFunction() { + @TinyCall("Returns the width in pixels of the text.") + override fun invoke( + @TinyArg("str", type = LuaType.STRING) args: Varargs, + ): Varargs { + val str = args.checkjstring(1) ?: return valueOf(0) + val font = currentFont() + + var maxWidth = 0 + var currentWidth = 0 + + if (font != null) { + str.forEachCodepoint { codepoint -> + if (codepoint == '\n'.code) { + maxWidth = max(maxWidth, currentWidth) + currentWidth = 0 + } else if (codepoint == ' '.code) { + currentWidth += font.spaceWidth + } else { + currentWidth += font.resolve(codepoint)?.charWidth ?: 0 + } + } + } else { + str.forEachCodepoint { codepoint -> + if (codepoint == '\n'.code) { + maxWidth = max(maxWidth, currentWidth) + currentWidth = 0 + } else if (codepoint == ' '.code) { + currentWidth += bootFontDescriptor.spaceWidth + } else { + currentWidth += bootFontDescriptor.resolve(codepoint)?.charWidth ?: 0 + } + } + } + maxWidth = max(maxWidth, currentWidth) + + return valueOf(maxWidth) + } + } + + private fun renderCustomFont( + str: String, + x: Int, + y: Int, + color: Int, + font: FontDescriptor, + ) { + val spritesheet = resourceAccess.findFontSpritesheet(currentFontIndex!!) ?: return + var currentX = x + var currentY = y + + str.forEachCodepoint { codepoint -> + when (codepoint) { + '\n'.code -> { + currentY += font.lineHeight + currentX = x + } + ' '.code -> { + currentX += font.spaceWidth + } + else -> { + val resolved = font.resolve(codepoint) + if (resolved != null) { + virtualFrameBuffer.drawMonocolor( + spritesheet, + color, + resolved.sourceX, + resolved.sourceY, + resolved.charWidth, + resolved.charHeight, + currentX, + currentY, + flipX = false, + flipY = false, + ) + currentX += resolved.charWidth + } + } + } + } + } + + private fun checkColorIndex(value: LuaValue): Int { + val colors = gameOptions.colors() + return if (value.isnumber()) { + colors.check(value.checkint()) + } else { + colors.getColorIndex(value.checkjstring()!!) + } + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TextLibExamples.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TextLibExamples.kt new file mode 100644 index 00000000..f3d47472 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TextLibExamples.kt @@ -0,0 +1,50 @@ +package com.github.minigdx.tiny.lua + +//language=Lua +const val TEXT_FONT_EXAMPLE = """ +function _draw() + gfx.cls() + -- Use the default boot font + text.font() + text.print("default font", 10, 10) + + -- Switch to the first custom font (index 0) + text.font(0) + text.print("custom font", 10, 30) + + -- Switch to a custom font by name + text.font("big_font") + text.print("big font", 10, 50) + + -- Reset to default + text.font() + text.print("back to default", 10, 70) +end +""" + +//language=Lua +const val TEXT_PRINT_EXAMPLE = """ +function _draw() + gfx.cls() + -- Print with default color (white) + text.print("hello world", 10, 10) + + -- Print with a color index + text.print("colored text", 10, 20, 9) + + -- Print multiline text + text.print("line 1\nline 2\nline 3", 10, 40) +end +""" + +//language=Lua +const val TEXT_WIDTH_EXAMPLE = """ +function _draw() + gfx.cls() + local msg = "hello" + local w = text.width(msg) + -- Center the text on screen + local x = (256 - w) / 2 + text.print(msg, x, 120) +end +""" diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TinyLib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TinyLib.kt index 36fe3487..5f8de688 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TinyLib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/TinyLib.kt @@ -22,6 +22,7 @@ internal expect fun platformValue(): Int "game dimensions (`tiny.width`, `tiny.height`), " + "platform information (`tiny.platform`) and " + "to switch to another script using `exit`.", + icon = "gamepad-2", ) class TinyLib( private val gameScript: List, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/Vec2Lib.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/Vec2Lib.kt index 64b21fd3..3f428ac2 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/Vec2Lib.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/Vec2Lib.kt @@ -12,7 +12,7 @@ import org.luaj.vm2.lib.LibFunction import org.luaj.vm2.lib.TwoArgFunction import org.luaj.vm2.lib.VarArgFunction -@TinyLib("vec2", "Vector2 manipulation library.") +@TinyLib("vec2", "Vector2 manipulation library.", icon = "move") class Vec2Lib : TwoArgFunction() { override fun call( arg1: LuaValue, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/InstrumentLuaWrapper.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/InstrumentLuaWrapper.kt index 3aa5d976..0e46c3cd 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/InstrumentLuaWrapper.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/InstrumentLuaWrapper.kt @@ -134,12 +134,20 @@ class InstrumentLuaWrapper( wrap("harmonics") { WrapperLuaTable().apply { - (0 until instrument.harmonics.size).forEach { index -> - wrap( - "${index + 1}", - { valueOf(instrument.harmonics[index].toDouble()) }, - { instrument.harmonics[index] = it.tofloat() }, - ) + (0 until 7).forEach { index -> + if (index < instrument.harmonics.size) { + wrap( + "${index + 1}", + { valueOf(instrument.harmonics[index].toDouble()) }, + { instrument.harmonics[index] = it.tofloat() }, + ) + } else { + wrap( + "${index + 1}", + { valueOf(0.0) }, + { }, + ) + } } } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SequenceLuaWrapper.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SequenceLuaWrapper.kt new file mode 100644 index 00000000..d8178534 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SequenceLuaWrapper.kt @@ -0,0 +1,130 @@ +package com.github.minigdx.tiny.lua.sfx + +import com.github.minigdx.tiny.lua.WrapperLuaTable +import com.github.minigdx.tiny.platform.Platform +import com.github.minigdx.tiny.sound.Music +import com.github.minigdx.tiny.sound.MusicConfiguration +import com.github.minigdx.tiny.sound.MusicGenerator +import com.github.minigdx.tiny.sound.MusicalSequence +import com.github.minigdx.tiny.sound.VirtualSoundBoard +import org.luaj.vm2.LuaTable +import org.luaj.vm2.LuaValue + +class SequenceLuaWrapper( + private val music: Music, + private val sequence: MusicalSequence, + private val soundBoard: VirtualSoundBoard, + private val platform: Platform, +) : WrapperLuaTable() { + private var cachedBuffer: FloatArray? = null + + init { + wrap( + "index", + { valueOf(sequence.index) }, + ) + + wrap( + "name", + { sequence.name?.let { valueOf(it) } ?: NIL }, + { sequence.name = it.optjstring(null) }, + ) + + wrap( + "tempo", + { valueOf(sequence.tempo) }, + { sequence.tempo = it.checkint() }, + ) + + function1("track") { arg -> + val index = arg.checkint() + val track = sequence.tracks.getOrNull(index) ?: return@function1 NIL + TrackLuaWrapper(music, track) + } + + wrap( + "config", + { + val config = sequence.configuration ?: return@wrap NIL + configToLuaTable(config) + }, + ) + + function1("generate") { arg -> + val config = luaTableToConfig(arg.checktable()!!) + MusicGenerator.generate(sequence, config) + sequence.configuration = config + // Link instruments after generation + sequence.tracks.forEach { track -> + track.instrument = music.instruments.getOrNull(track.instrumentIndex) + } + cachedBuffer = null + NONE + } + + function0("invalidate") { + cachedBuffer = null + NONE + } + + function0("play") { + val buffer = cachedBuffer ?: soundBoard.convert(sequence).also { cachedBuffer = it } + val handler = soundBoard.createHandler(buffer).also { it.play() } + val result = WrapperLuaTable() + result.function0("stop") { + handler.stop() + NONE + } + result.wrap("playing") { valueOf(handler.isPlaying()) } + result + } + + function0("export") { + val buffer = cachedBuffer ?: soundBoard.convert(sequence).also { cachedBuffer = it } + platform.saveWave(buffer) + NONE + } + } + + companion object { + fun configToLuaTable(config: MusicConfiguration): LuaValue { + val table = LuaTable() + table.set("root", LuaValue.valueOf(config.root)) + table.set("scale_name", LuaValue.valueOf(config.scaleName)) + table.set("progression_name", LuaValue.valueOf(config.progressionName)) + table.set("lead_style", LuaValue.valueOf(config.leadStyle)) + table.set("drum_pattern", LuaValue.valueOf(config.drumPattern)) + table.set("chord_instrument", LuaValue.valueOf(config.chordInstrument)) + table.set("bass_instrument", LuaValue.valueOf(config.bassInstrument)) + table.set("lead_instrument", LuaValue.valueOf(config.leadInstrument)) + table.set("drum_instrument", LuaValue.valueOf(config.drumInstrument)) + table.set("chord_volume", LuaValue.valueOf(config.chordVolume.toDouble())) + table.set("bass_volume", LuaValue.valueOf(config.bassVolume.toDouble())) + table.set("lead_volume", LuaValue.valueOf(config.leadVolume.toDouble())) + table.set("drum_volume", LuaValue.valueOf(config.drumVolume.toDouble())) + table.set("bpm", LuaValue.valueOf(config.bpm)) + table.set("seed", LuaValue.valueOf(config.seed.toDouble())) + return table + } + + fun luaTableToConfig(table: LuaTable): MusicConfiguration { + return MusicConfiguration( + root = table.get("root").optjstring("C") ?: "C", + scaleName = table.get("scale_name").optjstring("Major") ?: "Major", + progressionName = table.get("progression_name").optjstring("Classic") ?: "Classic", + leadStyle = table.get("lead_style").optjstring("Stepwise") ?: "Stepwise", + drumPattern = table.get("drum_pattern").optjstring("Rock") ?: "Rock", + chordInstrument = table.get("chord_instrument").optint(0), + bassInstrument = table.get("bass_instrument").optint(1), + leadInstrument = table.get("lead_instrument").optint(2), + drumInstrument = table.get("drum_instrument").optint(3), + chordVolume = table.get("chord_volume").optdouble(0.3).toFloat(), + bassVolume = table.get("bass_volume").optdouble(0.4).toFloat(), + leadVolume = table.get("lead_volume").optdouble(0.25).toFloat(), + drumVolume = table.get("drum_volume").optdouble(0.35).toFloat(), + bpm = table.get("bpm").optint(120), + seed = table.get("seed").optlong(42L), + ) + } + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SfxLuaWrapper.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SfxLuaWrapper.kt index e736d60d..a141a801 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SfxLuaWrapper.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/SfxLuaWrapper.kt @@ -37,6 +37,18 @@ class SfxLuaWrapper( { sfx.tempo = it.checkint() }, ) + wrap( + "name", + { sfx.name?.let { valueOf(it) } ?: NIL }, + { sfx.name = it.optjstring(null) }, + ) + + wrap( + "volume", + { valueOf(sfx.volume.toDouble()) }, + { sfx.volume = it.todouble().toFloat().coerceIn(0f, 1f) }, + ) + function1("set_volume") { arg -> val beat = arg["beat"].todouble().toFloat() val volume = arg["volume"].todouble().toFloat() @@ -101,6 +113,7 @@ class SfxLuaWrapper( handler.stop() NONE } + result.wrap("playing") { valueOf(handler.isPlaying()) } result } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/TrackLuaWrapper.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/TrackLuaWrapper.kt new file mode 100644 index 00000000..b4d17e1a --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/lua/sfx/TrackLuaWrapper.kt @@ -0,0 +1,83 @@ +package com.github.minigdx.tiny.lua.sfx + +import com.github.minigdx.tiny.lua.Note +import com.github.minigdx.tiny.lua.WrapperLuaTable +import com.github.minigdx.tiny.sound.Music +import com.github.minigdx.tiny.sound.MusicalNote +import com.github.minigdx.tiny.sound.MusicalSequence +import org.luaj.vm2.LuaTable + +class TrackLuaWrapper( + private val music: Music, + private val track: MusicalSequence.Track, +) : WrapperLuaTable() { + init { + wrap( + "index", + { valueOf(track.index) }, + ) + + wrap( + "instrument", + { valueOf(track.instrumentIndex) }, + { + val index = it.checkint() + val instrument = music.instruments.getOrNull(index) + if (instrument != null) { + track.instrumentIndex = index + track.instrument = instrument + } + }, + ) + + wrap( + "volume", + { valueOf(track.volume.toDouble()) }, + { track.volume = it.todouble().toFloat().coerceIn(0f, 1f) }, + ) + + wrap( + "mute", + { valueOf(track.mute) }, + { track.mute = it.checkboolean() }, + ) + + wrap("beats") { + val result = LuaTable() + track.beats.sortedBy { it.beat } + .map { + LuaTable().apply { + this.set("note", it.note?.name?.let { valueOf(it) } ?: NIL) + this.set("notei", it.note?.index?.let { valueOf(it) } ?: NIL) + this.set("octave", it.note?.octave?.let { valueOf(it) } ?: NIL) + this.set("volume", valueOf(it.volume.toDouble())) + this.set("beat", valueOf(it.beat.toDouble())) + this.set("duration", valueOf(it.duration.toDouble())) + } + }.forEachIndexed { index, value -> + result.insert(index + 1, value) + } + result + } + + function1("set_note") { arg -> + val beat = arg["beat"].checkint() + val noteName = arg["note"].tojstring() + val note = Note.Companion.fromName(noteName) + val volume = arg["volume"].optdouble(1.0).toFloat().coerceIn(0f, 1f) + val duration = arg["duration"].optdouble(1.0).toFloat() + + if (beat in track.beats.indices) { + track.beats[beat] = MusicalNote(note, beat.toFloat(), duration, volume) + } + NONE + } + + function0("clear") { + track.beats.indices.forEach { i -> + track.beats[i] = MusicalNote(null, i.toFloat(), 1f, 1f) + } + NONE + } + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/Platform.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/Platform.kt index edc829a6..3494ab15 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/Platform.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/Platform.kt @@ -7,6 +7,7 @@ import com.github.minigdx.tiny.file.SourceStream import com.github.minigdx.tiny.input.InputHandler import com.github.minigdx.tiny.input.InputManager import com.github.minigdx.tiny.platform.performance.PerformanceMonitor +import com.github.minigdx.tiny.render.VirtualFrameBuffer import com.github.minigdx.tiny.sound.SoundManager import kotlinx.coroutines.CoroutineDispatcher @@ -46,6 +47,11 @@ interface Platform { */ fun screenshot() = Unit + /** + * Clear the recording frame cache. + */ + fun clearRecordingCache() = Unit + /** * Write an image from a frame using index as colors */ @@ -121,4 +127,9 @@ interface Platform { ) fun saveWave(sound: FloatArray) + + /** + * Notify that a new frame has been rendered + */ + fun newFrameRendered(virtualFrameBuffer: VirtualFrameBuffer) = Unit } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/WindowManager.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/WindowManager.kt index d0eba041..030ee400 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/WindowManager.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/platform/WindowManager.kt @@ -15,11 +15,34 @@ class WindowManager( /** * Width of the window on the screen (resolution dependent) */ - val screenWidth: Int, + screenWidth: Int, /** * Height of the window on the screen (resolution dependent) */ - val screenHeight: Int, - val ratioWidth: Int = screenWidth / windowWidth, - val ratioHeight: Int = screenHeight / windowHeight, -) + screenHeight: Int, +) { + var screenWidth: Int = screenWidth + private set + + var screenHeight: Int = screenHeight + private set + + var ratioWidth: Int = screenWidth / windowWidth + private set + + var ratioHeight: Int = screenHeight / windowHeight + private set + + /** + * Update screen dimensions when DPI changes (e.g., moving between Retina and standard displays). + */ + fun updateScreenDimensions( + newWidth: Int, + newHeight: Int, + ) { + screenWidth = newWidth + screenHeight = newHeight + ratioWidth = newWidth / windowWidth + ratioHeight = newHeight / windowHeight + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/DefaultVirtualFrameBuffer.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/DefaultVirtualFrameBuffer.kt index 7c8ef024..f39628fb 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/DefaultVirtualFrameBuffer.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/DefaultVirtualFrameBuffer.kt @@ -182,7 +182,7 @@ class DefaultVirtualFrameBuffer( invalidateCachedReadFrame() updateDepthIndex(source) - val palColor = parameters.blender.palette()[color] + val palColor = parameters.blender.color(color) val key = spriteBatchManager.createKey() key.set( @@ -258,7 +258,7 @@ class DefaultVirtualFrameBuffer( width, height, filled = filled, - color = parameters.blender.palette()[color], + color = parameters.blender.color(color), dither = parameters.blender.dithering, depth = currentDepth, ) @@ -285,7 +285,7 @@ class DefaultVirtualFrameBuffer( y1 - parameters.camera.y, x2 - parameters.camera.x, y2 - parameters.camera.y, - color = parameters.blender.palette()[color], + color = parameters.blender.color(color), dither = parameters.blender.dithering, depth = currentDepth, ) @@ -307,7 +307,7 @@ class DefaultVirtualFrameBuffer( centerY - parameters.camera.y, radius, filled = filled, - color = parameters.blender.palette()[color], + color = parameters.blender.color(color), dither = parameters.blender.dithering, depth = currentDepth, ) @@ -325,7 +325,7 @@ class DefaultVirtualFrameBuffer( val instance = primitiveBatchManager.createInstance().setPoint( x - parameters.camera.x, y - parameters.camera.y, - color = parameters.blender.palette()[color], + color = parameters.blender.color(color), dither = parameters.blender.dithering, depth = currentDepth, ) @@ -362,7 +362,7 @@ class DefaultVirtualFrameBuffer( y2 - parameters.camera.y, x3 - parameters.camera.x, y3 - parameters.camera.y, - parameters.blender.palette()[color], + parameters.blender.color(color), parameters.blender.dithering, filled, depth = currentDepth, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/PrimitiveBatchStage.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/PrimitiveBatchStage.kt index 3701c3bc..45e39dd1 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/PrimitiveBatchStage.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/PrimitiveBatchStage.kt @@ -351,10 +351,10 @@ class PrimitiveBatchStage( int a = imod(x, 4); int b = imod(y, 4) * 4; int bitPosition = a + b; - - float powerOfTwo = pow(2.0, float(bitPosition)); - int bit = int(floor(mod(float(pattern) / powerOfTwo, 2.0))); - + + // Use bitwise shift to extract bit at position + int bit = (pattern >> bitPosition) & 1; + return bit > 0; } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/SpriteBatchStage.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/SpriteBatchStage.kt index bf242302..7d28f23d 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/SpriteBatchStage.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/render/gl/SpriteBatchStage.kt @@ -186,9 +186,9 @@ class SpriteBatchStage( int b = imod(y, 4) * 4; int bitPosition = a + b; - float powerOfTwo = pow(2.0, float(bitPosition)); - int bit = int(floor(mod(float(pattern) / powerOfTwo, 2.0))); - + // Use bitwise shift to extract bit at position + int bit = (pattern >> bitPosition) & 1; + return bit > 0; } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt index 48c02b1c..6e87e019 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/GameScript.kt @@ -5,8 +5,8 @@ import com.github.minigdx.tiny.engine.GameOptions import com.github.minigdx.tiny.engine.GameResourceAccess import com.github.minigdx.tiny.input.InputHandler import com.github.minigdx.tiny.log.Logger +import com.github.minigdx.tiny.lua.ConsoleLib import com.github.minigdx.tiny.lua.CtrlLib -import com.github.minigdx.tiny.lua.DebugLib import com.github.minigdx.tiny.lua.FloppyLib import com.github.minigdx.tiny.lua.GfxLib import com.github.minigdx.tiny.lua.JuiceLib @@ -19,6 +19,7 @@ import com.github.minigdx.tiny.lua.ShapeLib import com.github.minigdx.tiny.lua.SoundLib import com.github.minigdx.tiny.lua.SprLib import com.github.minigdx.tiny.lua.StdLib +import com.github.minigdx.tiny.lua.TextLib import com.github.minigdx.tiny.lua.TinyBaseLib import com.github.minigdx.tiny.lua.TinyLib import com.github.minigdx.tiny.lua.Vec2Lib @@ -98,7 +99,7 @@ class GameScript( load(SfxLib(resourceAccess, virtualSoundBoard, platform, playSound = !forValidation)) load(SoundLib(resourceAccess, virtualSoundBoard, playSound = !forValidation)) load(ShapeLib(gameOptions, virtualFrameBuffer)) - load(DebugLib(logger)) + load(ConsoleLib(logger)) load(KeysLib()) load(MathLib()) load(Vec2Lib()) @@ -106,6 +107,7 @@ class GameScript( load(sprLib) load(JuiceLib()) load(NotesLib()) + load(TextLib(gameOptions, resourceAccess, virtualFrameBuffer)) load(FloppyLib(platform = platform, logger = logger)) LoadState.install(this) diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt index 22cd1f51..37b9c6d0 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceFactory.kt @@ -132,16 +132,19 @@ class ResourceFactory( fun bootscript(name: String) = script(0, name, BOOT_GAMESCRIPT) + fun customBootScript(name: String) = script(0, name, BOOT_GAMESCRIPT, forceFromGameDirectory = true) + private fun script( index: Int, name: String, resourceType: ResourceType, + forceFromGameDirectory: Boolean = false, ): Flow { var version = 0 return vfs.watch( platform.createByteArrayStream( name = name, - canUseJarPrefix = !protectedResources.contains(resourceType), + canUseJarPrefix = forceFromGameDirectory || !protectedResources.contains(resourceType), ), ).map { content -> GameScript( @@ -176,6 +179,13 @@ class ResourceFactory( return spritesheet(0, name, BOOT_SPRITESHEET) } + fun fontSpritesheet( + index: Int, + name: String, + ): Flow { + return spritesheet(index, name, ResourceType.FONT_SPRITESHEET) + } + private fun spritesheet( index: Int, name: String, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceType.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceType.kt index 9878a2fe..23b04b35 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceType.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/resources/ResourceType.kt @@ -9,4 +9,5 @@ enum class ResourceType { GAME_LEVEL, GAME_SOUND, PRIMITIVE_SPRITESHEET, + FONT_SPRITESHEET, } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/DrumSynthesizer.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/DrumSynthesizer.kt new file mode 100644 index 00000000..a5f713d3 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/DrumSynthesizer.kt @@ -0,0 +1,157 @@ +package com.github.minigdx.tiny.sound + +import kotlin.math.PI +import kotlin.math.exp +import kotlin.math.ln +import kotlin.math.roundToInt +import kotlin.math.sin +import kotlin.random.Random + +/** + * Stateless drum synthesizer that generates different drum sounds + * based on the pitch class derived from the input frequency. + * + * Note-to-drum mapping: + * - C (pitch class 0) = bass drum + * - D (pitch class 2) = snare + * - E (pitch class 4) = hi-hat closed + * - F (pitch class 5) = hi-hat open + * - G (pitch class 7) = crash cymbal + * - A (pitch class 9) = tom 1 + * - B (pitch class 11) = tom 2 + * + * Sharps/flats map to the nearest natural note. + */ +object DrumSynthesizer { + private const val TWO_PI = PI.toFloat() * 2f + private const val C0_FREQ = 16.3516f + + /** + * Determines which drum part to play from the pitch class of [freq], + * then synthesises one sample at the given [time] (in seconds). + * + * @param freq The note frequency in Hz (used to detect pitch class). + * @param time Elapsed time in seconds since note-on. + * @param random A [Random] instance for noise generation. + * @return A sample value clamped to [-1, 1]. + */ + fun generate( + freq: Float, + time: Float, + random: Random, + ): Float { + val drumPart = pitchClassToDrum(freq) + return when (drumPart) { + DrumPart.BASS -> bassDrum(time) + DrumPart.SNARE -> snare(time, random) + DrumPart.HIHAT_CLOSED -> hihatClosed(time, random) + DrumPart.HIHAT_OPEN -> hihatOpen(time, random) + DrumPart.CRASH -> crash(time, random) + DrumPart.TOM1 -> tom1(time) + DrumPart.TOM2 -> tom2(time) + }.coerceIn(-1f, 1f) + } + + private enum class DrumPart { + BASS, + SNARE, + HIHAT_CLOSED, + HIHAT_OPEN, + CRASH, + TOM1, + TOM2, + } + + /** + * Derives the pitch class (0..11) from a frequency and maps it + * to the nearest natural note drum part. + */ + private fun pitchClassToDrum(freq: Float): DrumPart { + if (freq <= 0f) return DrumPart.BASS + val semitones = 12f * (ln(freq / C0_FREQ) / ln(2f)) + val pitchClass = ((semitones.roundToInt() % 12) + 12) % 12 + return when (pitchClass) { + 0, 1 -> DrumPart.BASS // C, C# + 2, 3 -> DrumPart.SNARE // D, D# + 4 -> DrumPart.HIHAT_CLOSED // E + 5, 6 -> DrumPart.HIHAT_OPEN // F, F# + 7, 8 -> DrumPart.CRASH // G, G# + 9, 10 -> DrumPart.TOM1 // A, A# + 11 -> DrumPart.TOM2 // B + else -> DrumPart.BASS + } + } + + // ---- Individual drum synthesis functions ---- + + /** Bass drum: sine with pitch sweep 150->50 Hz + click transient, fast decay. */ + private fun bassDrum(time: Float): Float { + val decay = exp(-time * 15f) + val sweepFreq = 50f + 100f * exp(-time * 40f) + val body = sin(TWO_PI * sweepFreq * time) * decay + val click = exp(-time * 200f) * 0.8f + return body + click + } + + /** Snare: 30% sine body at 180 Hz + 70% white noise, medium-fast decay. */ + private fun snare( + time: Float, + random: Random, + ): Float { + val bodyDecay = exp(-time * 20f) + val noiseDecay = exp(-time * 15f) + val body = sin(TWO_PI * 180f * time) * bodyDecay * 0.3f + val noise = (random.nextFloat() * 2f - 1f) * noiseDecay * 0.7f + return body + noise + } + + /** Hi-hat closed: metallic inharmonic tones + noise, very short decay (~17ms). */ + private fun hihatClosed( + time: Float, + random: Random, + ): Float { + val decay = exp(-time * 60f) // ~17ms effective duration + val metallic = sin(TWO_PI * 3527f * time) * sin(TWO_PI * 4735f * time) + val noise = random.nextFloat() * 2f - 1f + return (metallic * 0.6f + noise * 0.4f) * decay + } + + /** Hi-hat open: same metallic character, longer decay (~125ms). */ + private fun hihatOpen( + time: Float, + random: Random, + ): Float { + val decay = exp(-time * 8f) // ~125ms effective duration + val metallic = sin(TWO_PI * 3527f * time) * sin(TWO_PI * 4735f * time) + val noise = random.nextFloat() * 2f - 1f + return (metallic * 0.6f + noise * 0.4f) * decay + } + + /** Crash cymbal: three metallic frequencies + dominant noise, long decay. */ + private fun crash( + time: Float, + random: Random, + ): Float { + val decay = exp(-time * 3f) + val m1 = sin(TWO_PI * 4200f * time) + val m2 = sin(TWO_PI * 5386f * time) + val m3 = sin(TWO_PI * 3750f * time) + val metallic = (m1 + m2 + m3) / 3f + val noise = random.nextFloat() * 2f - 1f + return (metallic * 0.3f + noise * 0.7f) * decay + } + + /** Tom 1: sine with pitch sweep 200->120 Hz, medium decay. */ + private fun tom1(time: Float): Float { + val decay = exp(-time * 10f) + val sweepFreq = 120f + 80f * exp(-time * 25f) + return sin(TWO_PI * sweepFreq * time) * decay + } + + /** Tom 2: sine with pitch sweep 150->80 Hz, slightly longer decay. */ + private fun tom2(time: Float): Float { + val decay = exp(-time * 8f) + val sweepFreq = 80f + 70f * exp(-time * 20f) + return sin(TWO_PI * sweepFreq * time) * decay + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Effect.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Effect.kt index df9511f9..e52745f4 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Effect.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Effect.kt @@ -59,3 +59,30 @@ class Vibrato( return frequency + vibrato } } + +/** + * Tremolo effect — amplitude modulation using a low-frequency oscillator (LFO). + * + * Unlike [Modulation] which modulates frequency, tremolo modulates the amplitude + * of the signal. Typical LFO rates are 2–10 Hz. + * + * @param frequency LFO rate in Hz + * @param depth Modulation depth: 0.0 = no effect, 1.0 = full tremolo + */ +@Serializable +class Tremolo( + var frequency: Frequency = 0f, + var depth: Percent = 0f, +) { + var active: Boolean = false + + fun apply( + time: Seconds, + sample: Float, + ): Float { + if (!active || depth == 0f) return sample + // LFO oscillates between (1-depth) and 1.0 + val lfo = (1.0f - depth) + depth * ((sin(TWO_PI * frequency * time) + 1.0f) * 0.5f) + return sample * lfo + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Envelop.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Envelop.kt index 368b53e9..2b90f516 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Envelop.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Envelop.kt @@ -2,6 +2,7 @@ package com.github.minigdx.tiny.sound import com.github.minigdx.tiny.Percent import com.github.minigdx.tiny.Sample +import kotlin.math.max class Envelop( internal val attack0: () -> Sample, @@ -13,6 +14,10 @@ class Envelop( * Return the multiplier to apply to a sample value regarding the progression of the sound. * The [noteOn] phase will apply the [attack] then the [decay] then the [sustain] * and keep the [sustain] until the [noteOff]. + * + * Uses quadratic curves for more natural-sounding envelopes: + * - Attack uses x^2 (slow start, fast finish) + * - Decay uses inverse-square (fast drop, slow tail toward sustain) */ fun noteOn(progress: Sample): Percent { val attack = attack0.invoke() @@ -22,21 +27,23 @@ class Envelop( return when { progress < 0 -> 0.0f progress <= attack -> { - // Attack phase: 0.0 to 1.0 over attack samples + // Attack phase: 0.0 to 1.0 with quadratic curve (slow start, fast finish) if (attack == 0) { 1.0f } else { - progress.toFloat() / attack.toFloat() + val linear = progress.toFloat() / attack.toFloat() + linear * linear } } progress <= attack + decay -> { - // Decay phase: 1.0 to sustain over decay samples + // Decay phase: 1.0 to sustain with inverse-square curve (fast drop, slow tail) if (decay == 0) { sustain } else { val decayProgress = progress - attack - val decayAmount = 1.0f - sustain - 1.0f - (decayAmount * decayProgress.toFloat() / decay.toFloat()) + val linear = decayProgress.toFloat() / decay.toFloat() + val remaining = 1.0f - linear + sustain + (1.0f - sustain) * remaining * remaining } } else -> { @@ -49,19 +56,23 @@ class Envelop( /** * Return the multiplier to apply to a sample value regarding the progression of the sound. * The [noteOff] will apply the [attack] then the [decay] then right away the [release]. + * + * Enforces a minimum release duration of ~2ms (88 samples at 44100 Hz) + * to prevent audible clicks from abrupt note endings. + * + * Uses quadratic curve (fast drop, slow tail) for natural release. */ fun noteOff(progress: Sample): Percent { val sustain = sustain0.invoke() - val release = release0.invoke() + val release = max(MIN_RELEASE_SAMPLES, release0.invoke()) return when { + progress < 0 -> 0.0f progress <= release -> { - // Release phase: sustain to 0.0 over release samples - if (release == 0 || progress < 0) { - 0.0f - } else { - sustain * (1.0f - progress.toFloat() / release.toFloat()) - } + // Release phase: sustain to 0.0 with quadratic curve (fast drop, slow tail) + val linear = progress.toFloat() / release.toFloat() + val remaining = 1.0f - linear + sustain * remaining * remaining } else -> { // After release: silence @@ -69,4 +80,9 @@ class Envelop( } } } + + companion object { + // ~2ms at 44100 Hz - minimum release to prevent clicks + const val MIN_RELEASE_SAMPLES = 88 + } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Harmonizer.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Harmonizer.kt index 243c6588..8435d041 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Harmonizer.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Harmonizer.kt @@ -9,12 +9,16 @@ import com.github.minigdx.tiny.lua.Note * with the fundamental frequency of a note. This creates richer, more complex sounds * than simple sine waves. * - * Each harmonic is a multiple of the fundamental frequency (2x, 3x, 4x, etc.) and + * Each harmonic is a multiple of the fundamental frequency (1x, 2x, 3x, etc.) and * has its own amplitude weight defined in the harmonics array. * - * @param harmonics Array of relative amplitudes for each harmonic. Index 0 represents - * the first harmonic (2x fundamental), index 1 represents the second - * harmonic (3x fundamental), etc. Values typically range from 0.0 to 1.0. + * The output is normalized so that the sum of harmonics never exceeds [-1, 1], + * preventing clipping in downstream stages. + * + * @param harmonics0 Array of relative amplitudes for each harmonic. Index 0 represents + * the fundamental (1x frequency), index 1 represents the 2nd harmonic + * (2x frequency), index 2 represents the 3rd harmonic (3x frequency), etc. + * Values typically range from 0.0 to 1.0. */ class Harmonizer( val harmonics0: () -> FloatArray, @@ -24,11 +28,13 @@ class Harmonizer( * Each harmonic frequency is calculated as a multiple of the fundamental frequency, * and the generator function is called to produce the actual waveform value for each frequency. * + * The result is normalized by the total harmonic amplitude to prevent exceeding [-1, 1]. + * * @param note The musical note that provides the fundamental frequency * @param sample The current sample number (used for time-based calculations) - * @param generator A function that generates waveform values given a frequency and harmonic number. - * Takes (frequency, harmonicNumber) and returns the waveform sample value. - * @return The combined sample value of the fundamental frequency and all its harmonics + * @param generator A function that generates waveform values given a frequency and sample index. + * Takes (frequency, sampleIndex) and returns the waveform sample value. + * @return The combined and normalized sample value */ fun generate( note: Note, @@ -39,16 +45,18 @@ class Harmonizer( val harmonics = harmonics0.invoke() var sampleValue = 0f + var totalAmplitude = 0f harmonics.forEachIndexed { index, relativeAmplitude -> - // Harmonic numbers start at 1 (fundamental is implied to be 1x) - // So index 0 = 2nd harmonic (2x), index 1 = 3rd harmonic (3x), etc. val harmonicNumber = index + 1 val harmonicFreq = fundamentalFreq * harmonicNumber val value = generator.invoke(harmonicFreq, sample) - // Weight the harmonic contribution by its relative amplitude sampleValue += relativeAmplitude * value + totalAmplitude += relativeAmplitude } - return sampleValue + + // Normalize to prevent exceeding [-1, 1] + val normFactor = if (totalAmplitude > 1.0f) 1.0f / totalAmplitude else 1.0f + return sampleValue * normFactor } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Instrument.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Instrument.kt index eae09bae..da77f9aa 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Instrument.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Instrument.kt @@ -2,7 +2,7 @@ package com.github.minigdx.tiny.sound import com.github.minigdx.tiny.Percent import com.github.minigdx.tiny.Seconds -import com.github.minigdx.tiny.lua.Note +import com.github.minigdx.tiny.sound.Instrument.WaveType.DRUM import com.github.minigdx.tiny.sound.Instrument.WaveType.NOISE import com.github.minigdx.tiny.sound.Instrument.WaveType.PULSE import com.github.minigdx.tiny.sound.Instrument.WaveType.SAW_TOOTH @@ -13,7 +13,6 @@ import com.github.minigdx.tiny.sound.SoundManager.Companion.SAMPLE_RATE import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlin.math.PI -import kotlin.math.abs import kotlin.math.exp import kotlin.math.max import kotlin.math.sin @@ -61,9 +60,18 @@ class Instrument( * Will be applied in the order configured. */ val modulations: List = listOf( - Sweep(Note.A5.frequency, 1f), + Sweep(440f, 1f), Vibrato(0f, 0f), ), + /** + * Duty cycle for the PULSE wave type. + * 0.5 = square wave, 0.25 = nasal, 0.125 = thin/reedy. + */ + var dutyCycle: Percent = 0.5f, + /** + * Tremolo effect (amplitude modulation). + */ + val tremolo: Tremolo = Tremolo(), ) { enum class WaveType { SAW_TOOTH, @@ -72,9 +80,10 @@ class Instrument( SINE, NOISE, SQUARE, + DRUM, } - // Last output generated. Used by the [NOISE] wave type + // State for NOISE wave type - low-pass filter @Transient private var lastOutput: Float = 0.0f @@ -84,6 +93,16 @@ class Instrument( @Transient private var cachedAlpha: Float = 0.0f + @Transient + private val random: Random = Random(42) + + // DC blocker state for NOISE + @Transient + private var dcBlockerPrev: Float = 0f + + @Transient + private var dcBlockerOut: Float = 0f + fun generate( freq: Float, time: Float, @@ -92,36 +111,110 @@ class Instrument( val harmonicFreq = modulations.filter { it.active } .fold(freq) { acc, modulation -> modulation.apply(time, acc) } - return when (this.wave) { + val sample = when (this.wave) { TRIANGLE -> { - val angle: Float = sin(TWO_PI * harmonicFreq * time) - val phase = (angle + 1.0) % 1.0 // Normalize sinValue to the range [0, 1] - return (if (phase < 0.5) 4.0 * phase - 1.0 else 3.0 - 4.0 * phase).toFloat() + val phase = (harmonicFreq * time) % 1.0f + if (phase < 0.5f) { + (4.0f * phase) - 1.0f + } else { + 3.0f - (4.0f * phase) + } } SINE -> sin(TWO_PI * harmonicFreq * time) + SQUARE -> { val value = sin(TWO_PI * harmonicFreq * time) - return if (value > 0f) { - 1f + if (value > 0f) 1f else -1f + } + + PULSE -> { + val phase = (harmonicFreq * time) % 1.0f + val dc = dutyCycle + val raw = if (phase < dc) 1.0f else -1.0f + // Remove DC offset for non-50% duty cycles + val dcOffset = (2.0f * dc) - 1.0f + raw - dcOffset + } + + SAW_TOOTH -> { + val phase = (harmonicFreq * time) % 1.0f + (2.0f * phase) - 1.0f + } + + NOISE -> { + val alpha = + if (lastFrequencyUsed == harmonicFreq) { + cachedAlpha + } else { + val safeCutoff = max(1f, harmonicFreq) + val wc = TWO_PI * safeCutoff / SAMPLE_RATE + val x = exp(-wc) + cachedAlpha = 1.0f - x + lastFrequencyUsed = harmonicFreq + cachedAlpha + } + val white = random.nextFloat() * 2f - 1f + val filtered = alpha * white + (1.0f - alpha) * lastOutput + lastOutput = filtered + + // DC blocker (single-pole high-pass, ~20 Hz cutoff) + val dcAlpha = 0.997f + dcBlockerOut = dcAlpha * (dcBlockerOut + filtered - dcBlockerPrev) + dcBlockerPrev = filtered + dcBlockerOut + } + + DRUM -> DrumSynthesizer.generate(harmonicFreq, time, random) + } + + return tremolo.apply(time, sample) + } + + /** + * Return only the active modulations, to avoid filtering per sample. + */ + fun activeModulations(): List = modulations.filter { it.active } + + /** + * Generate a sample with pre-filtered active modulations to avoid + * per-sample list allocation. + */ + fun generate( + freq: Float, + time: Float, + activeModulations: List, + ): Float { + val harmonicFreq = activeModulations.fold(freq) { acc, modulation -> modulation.apply(time, acc) } + + val sample = when (this.wave) { + TRIANGLE -> { + val phase = (harmonicFreq * time) % 1.0f + if (phase < 0.5f) { + (4.0f * phase) - 1.0f } else { - -1f + 3.0f - (4.0f * phase) } } - PULSE -> { - val angle = sin(TWO_PI * harmonicFreq * time) + SINE -> sin(TWO_PI * harmonicFreq * time) + + SQUARE -> { + val value = sin(TWO_PI * harmonicFreq * time) + if (value > 0f) 1f else -1f + } - val t = angle % 1 - val k = abs(2.0 * ((angle / 128.0) % 1.0) - 1.0) - val u = (t + 0.5 * k) % 1.0 - val ret = abs(4.0 * u - 2.0) - abs(8.0 * t - 4.0) - return (ret / 6.0).toFloat() + PULSE -> { + val phase = (harmonicFreq * time) % 1.0f + val dc = dutyCycle + val raw = if (phase < dc) 1.0f else -1.0f + val dcOffset = (2.0f * dc) - 1.0f + raw - dcOffset } SAW_TOOTH -> { - val angle: Float = sin(TWO_PI * harmonicFreq * time) - return (angle * 2f) - 1f + val phase = (harmonicFreq * time) % 1.0f + (2.0f * phase) - 1.0f } NOISE -> { @@ -132,18 +225,44 @@ class Instrument( val safeCutoff = max(1f, harmonicFreq) val wc = TWO_PI * safeCutoff / SAMPLE_RATE val x = exp(-wc) - // Cache values cachedAlpha = 1.0f - x lastFrequencyUsed = harmonicFreq - cachedAlpha } - val white = Random.nextFloat() * 2f - 1f - val result = alpha * white + (1.0f - alpha) * lastOutput - lastOutput = result - return result + val white = random.nextFloat() * 2f - 1f + val filtered = alpha * white + (1.0f - alpha) * lastOutput + lastOutput = filtered + + val dcAlpha = 0.997f + dcBlockerOut = dcAlpha * (dcBlockerOut + filtered - dcBlockerPrev) + dcBlockerPrev = filtered + dcBlockerOut } + + DRUM -> DrumSynthesizer.generate(harmonicFreq, time, random) } + + return tremolo.apply(time, sample) + } + + /** + * Create a copy of this instrument with fresh (reset) transient state. + * Used to isolate mutable state (NOISE filter, DC blocker) when synthesizing in parallel. + */ + fun copyWithFreshState(): Instrument { + return Instrument( + index = index, + name = name, + wave = wave, + attack = attack, + decay = decay, + sustain = sustain, + release = release, + harmonics = harmonics.copyOf(), + modulations = modulations, + dutyCycle = dutyCycle, + tremolo = tremolo, + ) } companion object { diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/InstrumentPlayer.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/InstrumentPlayer.kt index f45e86bd..b2f09025 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/InstrumentPlayer.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/InstrumentPlayer.kt @@ -39,7 +39,10 @@ class InstrumentPlayer(private val instrument: Instrument) { private val harmonizer = Harmonizer({ instrument.harmonics }) - private val oscillator = Oscillator({ instrument.wave }) + private val oscillator = Oscillator( + waveType0 = { instrument.wave }, + dutyCycle0 = { instrument.dutyCycle }, + ) private val notesOn = mutableSetOf() private val notesOff = mutableSetOf() diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Music.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Music.kt index 4d875fb6..1e77d8fb 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Music.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Music.kt @@ -14,10 +14,10 @@ class Music( violon, obos, drum, - custom1, - custom2, - custom3, - custom4, + bass, + lead, + pluck, + snare, null, null, null, @@ -35,7 +35,30 @@ class Music( }, ) { fun serialize(): String { - return serialize(this) + val lightSequences = sequences.map { seq -> + val hasConfig = seq.configuration != null + val allSilence = seq.tracks.all { track -> track.beats.all { it.note == null } } + if (hasConfig || allSilence) { + MusicalSequence( + index = seq.index, + tracks = Array(4) { i -> + MusicalSequence.Track( + index = seq.tracks[i].index, + instrumentIndex = seq.tracks[i].instrumentIndex, + mute = seq.tracks[i].mute, + volume = seq.tracks[i].volume, + ).also { it.beats.clear() } + }, + tempo = seq.tempo, + name = seq.name, + configuration = seq.configuration, + ) + } else { + seq + } + }.toTypedArray() + val lightCopy = Music(instruments, musicalBars, lightSequences) + return serialize(lightCopy) } companion object { @@ -86,65 +109,66 @@ val obos = decay = 0.1f, sustain = 0.9f, release = 0.05f, - harmonics = floatArrayOf(1f, 0.05f, 0.01f), + harmonics = floatArrayOf(1f, 0.05f, 0.01f, 0f, 0f, 0f, 0f), ) val drum = Instrument( index = 3, name = "drum", - wave = Instrument.WaveType.NOISE, - attack = 0.1f, - decay = 0.1f, - sustain = 0.9f, + wave = Instrument.WaveType.DRUM, + attack = 0.001f, + decay = 0.01f, + sustain = 1.0f, release = 0.05f, - harmonics = floatArrayOf(1f), + harmonics = floatArrayOf(1f, 0f, 0f, 0f, 0f, 0f, 0f), ) -val custom1 = +val bass = Instrument( index = 4, - name = "custom1", + name = "bass", wave = Instrument.WaveType.PULSE, - attack = 0.1f, - decay = 0.1f, - sustain = 0.9f, - release = 0.05f, - harmonics = floatArrayOf(1f, 0.05f, 0.01f), + dutyCycle = 0.25f, + attack = 0.005f, + decay = 0.15f, + sustain = 0.6f, + release = 0.1f, + harmonics = floatArrayOf(1.0f, 0.5f, 0.25f, 0.12f, 0.06f, 0.0f, 0.0f), ) -val custom2 = +val lead = Instrument( index = 5, - name = "custom2", - wave = Instrument.WaveType.SAW_TOOTH, - attack = 0.1f, - decay = 0.1f, - sustain = 0.9f, - release = 0.05f, - harmonics = floatArrayOf(1f, 0.05f, 0.01f), + name = "lead", + wave = Instrument.WaveType.SQUARE, + attack = 0.01f, + decay = 0.08f, + sustain = 0.85f, + release = 0.15f, + harmonics = floatArrayOf(1.0f, 0.0f, 0.45f, 0.0f, 0.25f, 0.0f, 0.15f), ) -val custom3 = +val pluck = Instrument( index = 6, - name = "custom3", + name = "pluck", wave = Instrument.WaveType.TRIANGLE, - attack = 0.1f, - decay = 0.1f, - sustain = 0.9f, - release = 0.05f, - harmonics = floatArrayOf(1f, 0.05f, 0.01f), + attack = 0.002f, + decay = 0.2f, + sustain = 0.05f, + release = 0.08f, + harmonics = floatArrayOf(1.0f, 0.4f, 0.3f, 0.15f, 0.08f, 0.04f, 0.02f), ) -val custom4 = +val snare = Instrument( index = 7, - name = "custom4", - wave = Instrument.WaveType.SQUARE, - attack = 0.1f, - decay = 0.1f, - sustain = 0.9f, + name = "snare", + wave = Instrument.WaveType.NOISE, + attack = 0.001f, + decay = 0.08f, + sustain = 0.1f, release = 0.05f, - harmonics = floatArrayOf(1f, 0.05f, 0.01f), + harmonics = floatArrayOf(1.0f, 0.8f, 0.5f, 0.3f, 0f, 0f, 0f), ) diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicConfiguration.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicConfiguration.kt new file mode 100644 index 00000000..7a47fda9 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicConfiguration.kt @@ -0,0 +1,25 @@ +package com.github.minigdx.tiny.sound + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable + +@OptIn(ExperimentalSerializationApi::class) +@Serializable +data class MusicConfiguration( + @EncodeDefault val root: String = "C", + @EncodeDefault val scaleName: String = "Major", + @EncodeDefault val progressionName: String = "Classic", + @EncodeDefault val leadStyle: String = "Stepwise", + @EncodeDefault val drumPattern: String = "Rock", + @EncodeDefault val chordInstrument: Int = 0, + @EncodeDefault val bassInstrument: Int = 1, + @EncodeDefault val leadInstrument: Int = 2, + @EncodeDefault val drumInstrument: Int = 3, + @EncodeDefault val chordVolume: Float = 0.3f, + @EncodeDefault val bassVolume: Float = 0.4f, + @EncodeDefault val leadVolume: Float = 0.25f, + @EncodeDefault val drumVolume: Float = 0.35f, + @EncodeDefault val bpm: Int = 120, + @EncodeDefault val seed: Long = 42L, +) diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicGenerator.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicGenerator.kt new file mode 100644 index 00000000..08adf775 --- /dev/null +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicGenerator.kt @@ -0,0 +1,359 @@ +package com.github.minigdx.tiny.sound + +import com.github.minigdx.tiny.lua.Note +import kotlin.random.Random + +object MusicGenerator { + private val NOTE_NAMES = listOf("C", "Cs", "D", "Ds", "E", "F", "Fs", "G", "Gs", "A", "As", "B") + + val SCALE_NAMES = listOf("Major", "Minor", "Penta Maj", "Penta Min", "Dorian", "Mixolydian") + + val SCALES: Map> = mapOf( + "Major" to listOf(0, 2, 4, 5, 7, 9, 11), + "Minor" to listOf(0, 2, 3, 5, 7, 8, 10), + "Penta Maj" to listOf(0, 2, 4, 7, 9), + "Penta Min" to listOf(0, 3, 5, 7, 10), + "Dorian" to listOf(0, 2, 3, 5, 7, 9, 10), + "Mixolydian" to listOf(0, 2, 4, 5, 7, 9, 10), + ) + + val PROGRESSION_NAMES = listOf("Classic", "Melancholy", "Dreamy", "Tense", "Upbeat", "Cycle") + + val PROGRESSIONS: Map> = mapOf( + "Classic" to listOf(1, 5, 6, 4), + "Melancholy" to listOf(6, 4, 1, 5), + "Dreamy" to listOf(1, 4, 6, 5), + "Tense" to listOf(1, 7, 6, 5), + "Upbeat" to listOf(1, 4, 5, 4), + "Cycle" to listOf(2, 5, 1, 4), + ) + + val DRUM_PATTERN_NAMES = listOf("Rock", "Dance", "Halftime", "Funky", "March", "Sparse") + + val DRUM_PATTERNS: Map = mapOf( + "Rock" to DrumPattern( + kick = intArrayOf(1, 0, 0, 0, 1, 0, 0, 0), + snare = intArrayOf(0, 0, 1, 0, 0, 0, 1, 0), + hihat = intArrayOf(1, 1, 1, 1, 1, 1, 1, 1), + ), + "Dance" to DrumPattern( + kick = intArrayOf(1, 0, 1, 0, 1, 0, 1, 0), + snare = intArrayOf(0, 0, 1, 0, 0, 0, 1, 0), + hihat = intArrayOf(0, 1, 0, 1, 0, 1, 0, 1), + ), + "Halftime" to DrumPattern( + kick = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0), + snare = intArrayOf(0, 0, 0, 0, 1, 0, 0, 0), + hihat = intArrayOf(1, 0, 1, 0, 1, 0, 1, 0), + ), + "Funky" to DrumPattern( + kick = intArrayOf(1, 0, 0, 1, 0, 0, 1, 0), + snare = intArrayOf(0, 0, 1, 0, 0, 1, 0, 0), + hihat = intArrayOf(1, 1, 0, 1, 1, 0, 1, 1), + ), + "March" to DrumPattern( + kick = intArrayOf(1, 0, 1, 0, 1, 0, 1, 0), + snare = intArrayOf(0, 1, 0, 1, 0, 1, 0, 1), + hihat = intArrayOf(0, 0, 0, 0, 0, 0, 0, 0), + ), + "Sparse" to DrumPattern( + kick = intArrayOf(1, 0, 0, 0, 0, 0, 0, 0), + snare = intArrayOf(0, 0, 0, 0, 1, 0, 0, 0), + hihat = intArrayOf(0, 0, 1, 0, 0, 0, 1, 0), + ), + ) + + val LEAD_STYLES = listOf("Stepwise", "Arpeggiated", "Bouncy", "Sparse", "Random") + + data class DrumPattern( + val kick: IntArray, + val snare: IntArray, + val hihat: IntArray, + ) + + private fun noteNameToIndex(name: String): Int { + val idx = NOTE_NAMES.indexOf(name) + return if (idx >= 0) idx else 0 + } + + private fun semitoneToNote(semitone: Int): Note { + val clamped = semitone.coerceIn(0, 95) + return Note.fromIndex(clamped) + } + + private fun buildScaleNotes( + rootName: String, + scale: List, + octave: Int, + ): List { + val rootSemi = noteNameToIndex(rootName) + octave * 12 + return scale.map { rootSemi + it } + } + + private fun chordRootSemitone( + rootName: String, + scale: List, + degree: Int, + octave: Int, + ): Int { + val rootSemi = noteNameToIndex(rootName) + octave * 12 + val idx = ((degree - 1) % scale.size) + return rootSemi + scale[idx] + } + + private fun buildChordNotes( + rootName: String, + scale: List, + degree: Int, + octave: Int, + ): List { + val root = chordRootSemitone(rootName, scale, degree, octave) + val rootSemi = noteNameToIndex(rootName) + octave * 12 + val thirdIdx = ((degree - 1 + 2) % scale.size) + val fifthIdx = ((degree - 1 + 4) % scale.size) + var third = rootSemi + scale[thirdIdx] + var fifth = rootSemi + scale[fifthIdx] + if (third <= root) third += 12 + if (fifth <= root) fifth += 12 + return listOf(root, third, fifth) + } + + /** + * Returns the step direction (-1 or +1) to move [pos] toward index 0 (root) + * using the shortest path in a circular scale of [scaleSize] notes. + * Returns 0 if already at root. + */ + private fun stepTowardRoot( + pos: Int, + scaleSize: Int, + ): Int { + if (pos == 0) return 0 + return if (pos <= scaleSize / 2) -1 else 1 + } + + private fun clearTrack(track: MusicalSequence.Track) { + track.beats.indices.forEach { i -> + track.beats[i] = MusicalNote(null, i.toFloat(), 1f, 1f) + } + } + + fun generate( + sequence: MusicalSequence, + config: MusicConfiguration, + ) { + sequence.tempo = config.bpm + + val track0 = sequence.tracks[0] + val track1 = sequence.tracks[1] + val track2 = sequence.tracks[2] + val track3 = sequence.tracks[3] + + generateChords(track0, config) + generateBass(track1, config) + generateLead(track2, config, Random(config.seed)) + generateDrums(track3, config) + } + + private fun generateChords( + track: MusicalSequence.Track, + config: MusicConfiguration, + ) { + val scale = SCALES[config.scaleName] ?: SCALES["Major"]!! + val progression = PROGRESSIONS[config.progressionName] ?: PROGRESSIONS["Classic"]!! + clearTrack(track) + track.instrumentIndex = config.chordInstrument + track.volume = config.chordVolume + + for (bar in 0..3) { + val degree = progression[bar % progression.size] + val chord = buildChordNotes(config.root, scale, degree, 3) + for (i in 0..7) { + val beat = bar * 8 + i + if (beat < 33) { + val noteIdx = i % chord.size + // Gentle volume fade in last bar for smooth looping + val fadeOut = if (bar == 3 && i >= 5) (8f - i) / 3f else 1f + track.beats[beat] = MusicalNote( + semitoneToNote(chord[noteIdx]), + beat.toFloat(), + 1f, + 0.5f * fadeOut, + ) + } + } + } + } + + private fun generateBass( + track: MusicalSequence.Track, + config: MusicConfiguration, + ) { + val scale = SCALES[config.scaleName] ?: SCALES["Major"]!! + val progression = PROGRESSIONS[config.progressionName] ?: PROGRESSIONS["Classic"]!! + clearTrack(track) + track.instrumentIndex = config.bassInstrument + track.volume = config.bassVolume + + for (bar in 0..3) { + val degree = progression[bar % progression.size] + val root = chordRootSemitone(config.root, scale, degree, 2) + for (i in 0..7) { + val beat = bar * 8 + i + if (beat < 33) { + when (i) { + 0, 4 -> { + // Fade the second root hit in last bar for smooth looping + val fadeOut = if (bar == 3 && i == 4) 0.7f else 1f + track.beats[beat] = MusicalNote( + semitoneToNote(root), + beat.toFloat(), + 1f, + 0.7f * fadeOut, + ) + } + 2, 6 -> { + val fifthIdx = ((degree - 1 + 4) % scale.size) + val fifth = noteNameToIndex(config.root) + 2 * 12 + scale[fifthIdx] + // Fade the last fifth in last bar for smooth looping + val fadeOut = if (bar == 3 && i == 6) 0.4f else 1f + track.beats[beat] = MusicalNote( + semitoneToNote(fifth), + beat.toFloat(), + 1f, + 0.5f * fadeOut, + ) + } + } + } + } + } + } + + private fun generateLead( + track: MusicalSequence.Track, + config: MusicConfiguration, + random: Random, + ) { + val scale = SCALES[config.scaleName] ?: SCALES["Major"]!! + val style = config.leadStyle + clearTrack(track) + track.instrumentIndex = config.leadInstrument + track.volume = config.leadVolume + + val scaleNotes = buildScaleNotes(config.root, scale, 4) + var pos = random.nextInt(scaleNotes.size) + + // Leave beat 32 silent for clean looping (avoids doubled note with beat 0) + val lastBeat = 31 + // Start guiding melody back toward root for musical resolution + val resolveStart = 28 + + for (beat in 0..lastBeat) { + var play = false + val resolving = beat >= resolveStart + + when (style) { + "Stepwise" -> { + play = true + pos += if (resolving) { + stepTowardRoot(pos, scaleNotes.size) + } else { + listOf(-1, 0, 1).random(random) + } + } + "Arpeggiated" -> { + play = true + pos += if (resolving) { + stepTowardRoot(pos, scaleNotes.size) + } else { + listOf(1, 2).random(random) + } + } + "Bouncy" -> { + play = true + pos += if (resolving) { + stepTowardRoot(pos, scaleNotes.size) + } else { + listOf(-2, -1, 1, 2, 3).random(random) + } + } + "Sparse" -> { + play = if (resolving) { + beat % 2 == 0 + } else { + (beat % 2 == 0) && (random.nextFloat() > 0.3f) + } + if (play) { + pos += if (resolving) { + stepTowardRoot(pos, scaleNotes.size) + } else { + listOf(-1, 0, 1).random(random) + } + } + } + "Random" -> { + play = if (resolving) true else random.nextFloat() > 0.25f + if (play) { + if (resolving) { + pos += stepTowardRoot(pos, scaleNotes.size) + } else { + pos = random.nextInt(scaleNotes.size) + } + } + } + } + + if (pos < 0) pos += scaleNotes.size + if (pos >= scaleNotes.size) pos %= scaleNotes.size + + if (play) { + var semi = scaleNotes[pos] + if (semi > 95) semi -= 12 + if (semi < 0) semi += 12 + val baseVolume = 0.4f + random.nextFloat() * 0.2f + // Fade volume in last 2 beats for smooth loop transition + val loopFade = if (beat >= 30) (lastBeat + 1f - beat) / 2f else 1f + track.beats[beat] = MusicalNote( + semitoneToNote(semi), + beat.toFloat(), + 1f, + baseVolume * loopFade, + ) + } + } + } + + private fun generateDrums( + track: MusicalSequence.Track, + config: MusicConfiguration, + ) { + val pattern = DRUM_PATTERNS[config.drumPattern] ?: DRUM_PATTERNS["Rock"]!! + clearTrack(track) + track.instrumentIndex = config.drumInstrument + track.volume = config.drumVolume + + val kickNote = Note.fromName("C2") + val snareNote = Note.fromName("C4") + val hihatNote = Note.fromName("C6") + + for (bar in 0..3) { + for (i in 0..7) { + val beat = bar * 8 + i + val pi = i % pattern.kick.size + if (beat < 33) { + when { + pattern.kick[pi] == 1 -> { + track.beats[beat] = MusicalNote(kickNote, beat.toFloat(), 1f, 0.7f) + } + pattern.snare[pi] == 1 -> { + track.beats[beat] = MusicalNote(snareNote, beat.toFloat(), 1f, 0.6f) + } + pattern.hihat[pi] == 1 -> { + track.beats[beat] = MusicalNote(hihatNote, beat.toFloat(), 1f, 0.35f) + } + } + } + } + } + } +} diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalBar.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalBar.kt index 933860b5..415ec757 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalBar.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalBar.kt @@ -27,6 +27,11 @@ class MusicalBar( * BPM (Beats Per Minute) of the bar. */ var tempo: BPM = 120, + /** + * Name of the SFX + */ + var name: String? = "SFX $index", + var volume: Percent = 1.0f, ) { val beats: MutableList = mutableListOf() diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalSequence.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalSequence.kt index cc02d2b8..ba230bea 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalSequence.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/MusicalSequence.kt @@ -10,6 +10,8 @@ class MusicalSequence( val index: Int, val tracks: Array = Array(4) { Track(it, 0) }, var tempo: BPM = 120, + var name: String? = null, + var configuration: MusicConfiguration? = null, ) { @Serializable class Track( diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Oscillator.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Oscillator.kt index 085b8102..b8fa4715 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Oscillator.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/Oscillator.kt @@ -15,13 +15,22 @@ import kotlin.random.Random * The oscillator supports various wave types including sine, triangle, square, sawtooth, pulse, and noise. * Each wave type produces a different harmonic content and timbre. * - * @param waveType The type of waveform to generate (SINE, TRIANGLE, SQUARE, SAW_TOOTH, PULSE, NOISE) + * @param waveType0 The type of waveform to generate (SINE, TRIANGLE, SQUARE, SAW_TOOTH, PULSE, NOISE) + * @param dutyCycle0 The duty cycle for the PULSE wave type (0.0 to 1.0, default 0.5) */ -class Oscillator(val waveType0: () -> Instrument.WaveType) { +class Oscillator( + val waveType0: () -> Instrument.WaveType, + val dutyCycle0: () -> Float = { 0.5f }, +) { // State for NOISE wave type - implements a low-pass filter private var lastOutput: Float = 0.0f private var lastFrequencyUsed: Float = 0.0f private var cachedAlpha: Float = 0.0f + private val random: Random = Random(42) + + // DC blocker state for NOISE + private var dcBlockerPrev: Float = 0f + private var dcBlockerOut: Float = 0f /** * Generates a single audio sample value for the given frequency at the specified sample position. @@ -46,13 +55,10 @@ class Oscillator(val waveType0: () -> Instrument.WaveType) { } Instrument.WaveType.TRIANGLE -> { - // Generate proper triangle wave using phase val phase = (frequency * time) % 1.0f if (phase < 0.5f) { - // Rising edge: -1 to 1 (4.0f * phase) - 1.0f } else { - // Falling edge: 1 to -1 3.0f - (4.0f * phase) } } @@ -63,39 +69,43 @@ class Oscillator(val waveType0: () -> Instrument.WaveType) { } Instrument.WaveType.SAW_TOOTH -> { - // Generate proper sawtooth wave using phase instead of sine val phase = (frequency * time) % 1.0f (2.0f * phase) - 1.0f } Instrument.WaveType.PULSE -> { - // Generate pulse wave with variable duty cycle val phase = (frequency * time) % 1.0f - val dutyCycle = 0.25f // 25% duty cycle - if (phase < dutyCycle) 1.0f else -1.0f + val dc = dutyCycle0() + val raw = if (phase < dc) 1.0f else -1.0f + // Remove DC offset for non-50% duty cycles + val dcOffset = (2.0f * dc) - 1.0f + raw - dcOffset } Instrument.WaveType.NOISE -> { - // Filtered noise implementation using a low-pass filter val alpha = if (lastFrequencyUsed == frequency) { cachedAlpha } else { val safeCutoff = max(1f, frequency) val wc = TWO_PI * safeCutoff / SAMPLE_RATE val x = exp(-wc) - // Cache values for performance cachedAlpha = 1.0f - x lastFrequencyUsed = frequency cachedAlpha } - // Generate white noise using time-based seeding for better randomness - val seed = (time * 12345.0f + progress * 67890.0f).toLong() - val white = Random(seed).nextFloat() * 2f - 1f - val result = alpha * white + (1.0f - alpha) * lastOutput - lastOutput = result - result + val white = random.nextFloat() * 2f - 1f + val filtered = alpha * white + (1.0f - alpha) * lastOutput + lastOutput = filtered + + // DC blocker (single-pole high-pass, ~20 Hz cutoff) + val dcAlpha = 0.997f + dcBlockerOut = dcAlpha * (dcBlockerOut + filtered - dcBlockerPrev) + dcBlockerPrev = filtered + dcBlockerOut } + + Instrument.WaveType.DRUM -> DrumSynthesizer.generate(frequency, time, random) } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundHandler.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundHandler.kt index ae0a7b08..33f1f52f 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundHandler.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundHandler.kt @@ -22,6 +22,11 @@ interface SoundHandler { */ fun stop() + /** + * Check if the sound is currently playing. + */ + fun isPlaying(): Boolean + /** * Generate the next chunk. */ diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundManager.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundManager.kt index 5b5ca11a..743429c5 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundManager.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/SoundManager.kt @@ -25,11 +25,19 @@ class DefaultSoundBoard(private val soundManager: SoundManager) : VirtualSoundBo return soundManager.createSoundHandler(buffer) } + override fun createHandler(buffer: FloatArray): SoundHandler { + return soundManager.createSoundHandler(buffer) + } + override fun convert(bar: MusicalBar): FloatArray { val buffer = soundManager.convert(bar) return buffer } + override fun convert(sequence: MusicalSequence): FloatArray { + return soundManager.convert(sequence) + } + override fun noteOn( note: Note, instrument: Instrument, @@ -47,6 +55,11 @@ abstract class SoundManager { open fun destroy() = Unit + /** + * Configurable master volume. Defaults to [DEFAULT_MASTER_VOLUME]. + */ + var masterVolume: Float = DEFAULT_MASTER_VOLUME + /** * @param buffer byte array representing the sound. Each sample is represented with a float from -1.0f to 1.0f */ @@ -60,6 +73,7 @@ abstract class SoundManager { defaultInstrument = bar.instrument, beats = bar.beats, tempo = bar.tempo, + volume = bar.volume, ) } @@ -98,36 +112,44 @@ abstract class SoundManager { if (tracks.isEmpty()) return floatArrayOf() val resultSize = tracks.maxOf { it.size } - val result = FloatArray(resultSize) { 0f } + val result = FloatArray(resultSize) // Calculate RMS values for each track to properly scale them during mixing - val trackRmsValues = tracks.map { calculateRms(it) } + val trackRmsValues = FloatArray(tracks.size) { calculateRms(tracks[it]) } val totalRms = trackRmsValues.sum() - // Mix tracks with RMS-based scaling to prevent saturation - result.indices.forEach { index -> - var mixedSample = 0f - - // If total RMS is significant, use it for scaling - if (totalRms > 0.001f) { - tracks.forEachIndexed { trackIndex, track -> - val sample = track.getOrNull(index) ?: 0f - // Scale each sample based on its track's contribution to the total RMS - val scaleFactor = if (trackRmsValues[trackIndex] > 0.001f) { - // Normalize by the track's RMS value relative to the total RMS - 1f / (trackRmsValues.size * (trackRmsValues[trackIndex] / totalRms)) - } else { - 1f - } - mixedSample += sample * scaleFactor + // Step 6: Pre-compute scale factors and use indexed loops + if (totalRms > 0.001f) { + val scaleFactors = FloatArray(tracks.size) { t -> + if (trackRmsValues[t] > 0.001f) { + 1f / (tracks.size * (trackRmsValues[t] / totalRms)) + } else { + 1f } - } else { - // Fallback to simple averaging if RMS values are too small - mixedSample = tracks.mapNotNull { it.getOrNull(index) }.sum() / tracks.size.toFloat() } - // Ensure the final sample is within the valid range - result[index] = max(-1f, min(1f, mixedSample)) + for (index in result.indices) { + var mixedSample = 0f + for (t in tracks.indices) { + val track = tracks[t] + if (index < track.size) { + mixedSample += track[index] * scaleFactors[t] + } + } + result[index] = softClip(mixedSample) + } + } else { + val invSize = 1f / tracks.size.toFloat() + for (index in result.indices) { + var mixedSample = 0f + for (t in tracks.indices) { + val track = tracks[t] + if (index < track.size) { + mixedSample += track[index] + } + } + result[index] = softClip(mixedSample * invSize) + } } return result } @@ -159,12 +181,26 @@ abstract class SoundManager { val secondsPerBeat = 60f / tempo - var result = floatArrayOf() + // Step 1: Pre-allocate result array to avoid O(n^2) copyOf calls + var resultSize = 0 + for (b in beats) { + val instrument = b.instrument ?: defaultInstrument + val effectiveRelease = max(instrument.release, MIN_RELEASE_SECONDS) + val noteDurationInSeconds = (b.duration * secondsPerBeat) + effectiveRelease + val numberOfSamples = (noteDurationInSeconds * SAMPLE_RATE).roundToInt() + val minimumSize = (b.beat * secondsPerBeat * SAMPLE_RATE).roundToInt() + numberOfSamples + if (minimumSize > resultSize) resultSize = minimumSize + } + val result = FloatArray(resultSize) for (b in beats) { + // Step 7: Skip silent notes early + if (b.note == null && b.volume == 0f) continue + val instrument = b.instrument ?: defaultInstrument // Duration of the note added to the duration of the release. - val noteDurationInSeconds = (b.duration * secondsPerBeat) + instrument.release + val effectiveRelease = max(instrument.release, MIN_RELEASE_SECONDS) + val noteDurationInSeconds = (b.duration * secondsPerBeat) + effectiveRelease val numberOfSamples = (noteDurationInSeconds * SAMPLE_RATE).roundToInt() val maxPossibleAmplitude = instrument.harmonics.sum() @@ -174,80 +210,99 @@ abstract class SoundManager { val fundamentalFreq = b.note?.frequency ?: 0f + // Step 2: Pre-filter active modulations once per note + val activeModulations = instrument.activeModulations() + // Step 3: Precompute envelope parameters once per note + val envelope = PrecomputedEnvelope(numberOfSamples, instrument) + // Step 5: Get harmonics array for indexed loop + val harmonics = instrument.harmonics + for (i in 0.. - val harmonicNumber = index + 1 - val harmonicFreq = fundamentalFreq * harmonicNumber - - sampleValue += relativeAmplitude * instrument.generate(harmonicFreq, time) + // Step 5: Indexed loop + skip zero harmonics + for (h in harmonics.indices) { + val amp = harmonics[h] + if (amp == 0f) continue + sampleValue += amp * instrument.generate(fundamentalFreq * (h + 1), time, activeModulations) } - sampleValue *= envelopeFilter(i, numberOfSamples, instrument) + // Step 3: Use precomputed envelope + sampleValue *= envelope.evaluate(i) sampleValue *= normalizationFactor * b.volume * volume - sampleValue *= MASTER_VOLUME + sampleValue *= masterVolume } - buffer[i] = max(-1.0f, min(1.0f, sampleValue)) + // Step 4: No softClip per sample — only at final mix + buffer[i] = sampleValue + } + + // Apply safety fade-out to prevent clicks at buffer boundaries + val fadeOutSamples = min(FADE_OUT_SAMPLES, numberOfSamples) + for (i in 0 until fadeOutSamples) { + val fadeIndex = numberOfSamples - fadeOutSamples + i + if (fadeIndex >= 0 && fadeIndex < buffer.size) { + val fadeFactor = 1.0f - (i.toFloat() / fadeOutSamples.toFloat()) + buffer[fadeIndex] *= fadeFactor + } } // Put the buffer at the time of the beat in the result (without the release). val startIndex = (b.beat * secondsPerBeat * SAMPLE_RATE).roundToInt() val endIndex = startIndex + numberOfSamples - 1 - val minimumSize = startIndex + numberOfSamples - // Adjust the size of result if the buffer finish after the actual result. - if (minimumSize > result.size) { - result = result.copyOf(minimumSize) - } - - // Additive synthesis of the buffer into the result. + // Step 4: No softClip on additive mix — just accumulate var index = 0 for (i in startIndex until endIndex) { - result[i] = min(max(-1f, result[i] + buffer[index++]), 1f) + result[i] += buffer[index++] } } return result } - private fun envelopeFilter( - currentSample: Int, - totalSamples: Int, - instrument: Instrument, - ): Float { - // Get the number of samples for each phase. Avoid empty phase - val attackSamples = max(1f, instrument.attack * SAMPLE_RATE) - val decaySamples = max(1f, instrument.decay * SAMPLE_RATE) - val releaseSamples = max(1f, instrument.release * SAMPLE_RATE) - val sustainLevel = min(1f, max(instrument.sustain, 0f)) - - val releaseStartSample = totalSamples - (instrument.release * SAMPLE_RATE) - // Ensure that phases can't finish AFTER the release. - val attackEndSample = min(attackSamples, releaseStartSample) - val decayEndSample = min(attackEndSample + decaySamples, releaseStartSample) - - val multiplier = if (currentSample < attackEndSample) { - // Attack: going from 0 to 1.0 - currentSample.toFloat() / attackSamples - } else if (currentSample < decayEndSample) { - // Decay: going from 1.0 to sustain level - val decayProgress = (currentSample - attackEndSample) / decaySamples - 1.0f - decayProgress * (1.0f - sustainLevel) - } else if (currentSample < releaseStartSample) { - // Sustain level - sustainLevel - } else { - // Release: going from sustain to 0f - val releaseProgress = (currentSample - releaseStartSample) / releaseSamples - sustainLevel * (1.0f - min(1.0f, releaseProgress)) + /** + * Pre-computed envelope parameters to avoid redundant calculations per sample. + * All phase boundaries are computed once per note. + */ + private class PrecomputedEnvelope(totalSamples: Int, instrument: Instrument) { + private val attackSamples: Float = max(1f, instrument.attack * SAMPLE_RATE) + private val decaySamples: Float = max(1f, instrument.decay * SAMPLE_RATE) + private val sustainLevel: Float = min(1f, max(instrument.sustain, 0f)) + private val releaseSamples: Float + private val releaseStartSample: Float + private val attackEndSample: Float + private val decayEndSample: Float + + init { + val effectiveRelease = max(MIN_RELEASE_SECONDS, instrument.release) + releaseSamples = max(1f, effectiveRelease * SAMPLE_RATE) + releaseStartSample = totalSamples - (effectiveRelease * SAMPLE_RATE) + attackEndSample = min(attackSamples, releaseStartSample) + decayEndSample = min(attackEndSample + decaySamples, releaseStartSample) } - return max(0.0f, min(1.0f, multiplier)) + fun evaluate(currentSample: Int): Float { + val multiplier = if (currentSample < attackEndSample) { + val linear = currentSample.toFloat() / attackSamples + linear * linear + } else if (currentSample < decayEndSample) { + val linear = (currentSample - attackEndSample) / decaySamples + val remaining = 1.0f - linear + sustainLevel + (1.0f - sustainLevel) * remaining * remaining + } else if (currentSample < releaseStartSample) { + sustainLevel + } else { + val linear = (currentSample - releaseStartSample) / releaseSamples + val remaining = 1.0f - min(1.0f, linear) + sustainLevel * remaining * remaining + } + + return max(0.0f, min(1.0f, multiplier)) + } } abstract fun noteOn( @@ -275,8 +330,37 @@ abstract class SoundManager { companion object { const val SAMPLE_RATE = 44100 - const val MASTER_VOLUME = 0.5f + + const val DEFAULT_MASTER_VOLUME = 0.5f + + @Deprecated("Use instance masterVolume instead", replaceWith = ReplaceWith("masterVolume")) + const val MASTER_VOLUME = DEFAULT_MASTER_VOLUME + const val PI = (kotlin.math.PI).toFloat() const val TWO_PI = 2.0f * PI + + // ~2ms at 44100 Hz - safety fade-out to prevent clicks at buffer boundaries + private const val FADE_OUT_SAMPLES = 88 + + // Minimum release time in seconds (~2ms) to prevent clicks + private const val MIN_RELEASE_SECONDS = 0.002f + + /** + * Fast soft saturation using a Padé approximation of tanh. + * Produces warm compression when signals exceed the dynamic range, + * instead of harsh distortion from hard clipping. + * Quiet signals pass through nearly unaffected. + */ + fun softClip(sample: Float): Float { + val x = sample * 1.5f + return when { + x >= 3f -> 1f + x <= -3f -> -1f + else -> { + val x2 = x * x + x * (27f + x2) / (27f + 9f * x2) + } + } + } } } diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/VirtualSoundBoard.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/VirtualSoundBoard.kt index 39bce05c..f4059fcc 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/VirtualSoundBoard.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/sound/VirtualSoundBoard.kt @@ -18,8 +18,15 @@ interface VirtualSoundBoard { */ fun prepare(track: MusicalSequence.Track): SoundHandler + /** + * Create a sound handler from a pre-computed audio buffer. + */ + fun createHandler(buffer: FloatArray): SoundHandler + fun convert(bar: MusicalBar): FloatArray + fun convert(sequence: MusicalSequence): FloatArray + fun noteOn( note: Note, instrument: Instrument, diff --git a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/util/MutableFixedSizeList.kt b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/util/MutableFixedSizeList.kt index f420d877..de2e766f 100644 --- a/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/util/MutableFixedSizeList.kt +++ b/tiny-engine/src/commonMain/kotlin/com/github/minigdx/tiny/util/MutableFixedSizeList.kt @@ -19,7 +19,7 @@ import kotlin.math.max * @param maxSize The maximum number of elements this list can hold. Must be non-negative. */ class MutableFixedSizeList(val maxSize: Int) : MutableList { - private val delegate: MutableList = ArrayList(maxSize) + private val delegate: MutableList = ArrayDeque(maxSize) override val size: Int get() { diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/engine/GameConfigTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/engine/GameConfigTest.kt new file mode 100644 index 00000000..d323ca84 --- /dev/null +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/engine/GameConfigTest.kt @@ -0,0 +1,121 @@ +package com.github.minigdx.tiny.engine + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class GameConfigTest { + @Test + fun parse_v1_config_with_all_fields() { + val json = """ + { + "version": "V1", + "name": "my-game", + "id": "game-123", + "resolution": { "width": 256, "height": 256 }, + "sprites": { "width": 16, "height": 16 }, + "zoom": 2, + "colors": ["#000000", "#FFFFFF", "#FF0000"], + "scripts": ["game.lua", "utils.lua"], + "spritesheets": ["sprites.png"], + "levels": ["level1.ldtk"], + "sound": "sfx.json", + "hideMouseCursor": true + } + """.trimIndent() + + val config = GameConfig.parse(json) + + assertIs(config) + assertEquals("my-game", config.name) + assertEquals("game-123", config.id) + assertEquals(256, config.resolution.width) + assertEquals(256, config.resolution.height) + assertEquals(16, config.sprites.width) + assertEquals(16, config.sprites.height) + assertEquals(2, config.zoom) + assertEquals(listOf("#000000", "#FFFFFF", "#FF0000"), config.colors) + assertEquals(listOf("game.lua", "utils.lua"), config.scripts) + assertEquals(listOf("sprites.png"), config.spritesheets) + assertEquals(listOf("level1.ldtk"), config.levels) + assertEquals("sfx.json", config.sound) + assertTrue(config.hideMouseCursor) + } + + @Test + fun parse_v1_config_with_defaults() { + val json = """ + { + "version": "V1", + "name": "minimal", + "id": "min-1", + "resolution": { "width": 128, "height": 128 }, + "sprites": { "width": 8, "height": 8 }, + "zoom": 1, + "colors": ["#FFFFFF", "#000000"] + } + """.trimIndent() + + val config = GameConfig.parse(json) + + assertIs(config) + assertEquals(emptyList(), config.scripts) + assertEquals(emptyList(), config.spritesheets) + assertEquals(emptyList(), config.levels) + assertNull(config.sound) + assertEquals(false, config.hideMouseCursor) + } + + @Test + fun toGameOptions_maps_all_fields() { + val config = GameConfigV1( + name = "test", + id = "t-1", + resolution = GameConfigSize(width = 256, height = 128), + sprites = GameConfigSize(width = 16, height = 16), + zoom = 3, + colors = listOf("#FF0000"), + scripts = listOf("game.lua"), + spritesheets = listOf("spr.png"), + levels = listOf("lvl.ldtk"), + sound = "sfx.json", + hideMouseCursor = true, + ) + + val options = config.toGameOptions() + + assertEquals(256, options.width) + assertEquals(128, options.height) + assertEquals(listOf("#FF0000"), options.palette) + assertEquals(16 to 16, options.spriteSize) + assertEquals(listOf("game.lua"), options.gameScripts) + assertEquals(listOf("spr.png"), options.spriteSheets) + assertEquals(listOf("lvl.ldtk"), options.gameLevels) + assertEquals(3, options.zoom) + assertEquals("sfx.json", options.sound) + assertTrue(options.hideMouseCursor) + } + + @Test + fun parse_ignores_unknown_keys() { + val json = """ + { + "version": "V1", + "name": "test", + "id": "t-1", + "resolution": { "width": 128, "height": 128 }, + "sprites": { "width": 8, "height": 8 }, + "zoom": 1, + "colors": ["#FFFFFF"], + "unknownField": "should be ignored", + "anotherUnknown": 42 + } + """.trimIndent() + + val config = GameConfig.parse(json) + assertIs(config) + assertEquals("test", config.name) + } +} diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/input/TouchManagerTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/input/TouchManagerTest.kt index 86cd061f..cf3f4a08 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/input/TouchManagerTest.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/input/TouchManagerTest.kt @@ -90,4 +90,52 @@ class TouchManagerTest { val pos = touchManager.isTouched(TouchSignal.TOUCH1) assertEquals(0f, pos?.x) } + + @Test + fun resetAllState_clears_keys_and_touches() { + val touchManager = TouchManager(10) + + // Press keys and touch + touchManager.onKeyPressed(1) + touchManager.onKeyPressed(2) + touchManager.onTouchDown(TouchSignal.TOUCH1, 5f, 10f) + touchManager.processReceivedEvent() + + // Verify state is set + assertTrue(touchManager.isKeyPressed(1)) + assertTrue(touchManager.isKeyPressed(2)) + assertTrue(touchManager.isAnyKeyPressed) + assertTrue(touchManager.isAnyKeyJustPressed) + assertNotNull(touchManager.isTouched(TouchSignal.TOUCH1)) + assertNotNull(touchManager.isJustTouched(TouchSignal.TOUCH1)) + + // Reset all state + touchManager.resetAllState() + + // Verify all state is cleared + assertFalse(touchManager.isKeyPressed(1)) + assertFalse(touchManager.isKeyPressed(2)) + assertFalse(touchManager.isAnyKeyPressed) + assertFalse(touchManager.isAnyKeyJustPressed) + assertNull(touchManager.isTouched(TouchSignal.TOUCH1)) + assertNull(touchManager.isJustTouched(TouchSignal.TOUCH1)) + } + + @Test + fun resetAllState_clears_pending_events() { + val touchManager = TouchManager(10) + + // Queue events but don't process them + touchManager.onKeyPressed(3) + touchManager.onTouchDown(TouchSignal.TOUCH2, 1f, 2f) + + // Reset all state (including pending events) + touchManager.resetAllState() + + // Process should have nothing to apply + touchManager.processReceivedEvent() + + assertFalse(touchManager.isKeyPressed(3)) + assertNull(touchManager.isTouched(TouchSignal.TOUCH2)) + } } diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/GfxLibTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/GfxLibTest.kt deleted file mode 100644 index ad507ef1..00000000 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/GfxLibTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.github.minigdx.tiny.lua - -class GfxLibTest { - // FIXME: - - /* - private val frameBuffer = FrameBuffer(10, 10, ColorPalette(listOf("#FFFFFF"))) - - private val gameResourceAccess = - mock { - every { frameBuffer } returns this@GfxLibTest.frameBuffer - every { addOp(any()) } returns Unit - } - - private val gameOptions = GameOptions(10, 10, listOf("#FFFFFF"), listOf("game.lua"), emptyList()) - - @Test - fun it_sets_the_clip() { - val clip = GfxLib(gameResourceAccess, gameOptions).clip() - clip.call(valueOf(1), valueOf(2), valueOf(3), valueOf(4)) - - assertEquals(1, frameBuffer.clipper.left) - assertEquals(2, frameBuffer.clipper.top) - assertEquals(1 + 3, frameBuffer.clipper.right) - assertEquals(2 + 4, frameBuffer.clipper.bottom) - } - - @Test - fun it_sets_the_dither() { - val dither = GfxLib(gameResourceAccess, gameOptions).dither() - - dither.call(valueOf(0xA5A5)) - - val a = frameBuffer.blender.mix(byteArrayOf(1), 0, 0, null)?.get(0) ?: 0 - val b = frameBuffer.blender.mix(byteArrayOf(1), 1, 0, null)?.get(0) ?: 0 - val c = frameBuffer.blender.mix(byteArrayOf(1), 0, 1, null)?.get(0) ?: 0 - val d = frameBuffer.blender.mix(byteArrayOf(1), 1, 1, null)?.get(0) ?: 0 - - assertEquals(1, a) - assertEquals(0, b) - assertEquals(0, c) - assertEquals(1, d) - } - - @Test - fun it_sets_the_dither_full_pattern() { - val dither = GfxLib(gameResourceAccess, gameOptions).dither() - - /** - * 1000 -> 8 - * 1100 -> 8 + 4 = 12 = C - * 0010 -> 2 - * 0001 -> 1 - * - * --> 8C21 - */ - dither.call(valueOf(0x8C21)) - - val result = Array(4 * 4) { 0x01 } - for (x in 0 until 4) { - for (y in 0 until 4) { - val index = x + y * 4 - val r = frameBuffer.blender.mix(byteArrayOf(result[index]), x, y, null)?.get(0) ?: 0 - result[index] = r - } - } - - val expected = Array(4 * 4) { i -> - if (i == 0 || i == 4 || i == 5 || i == 10 || i == 15) { - 0x01 - } else { - 0x00 - } - } - result.forEachIndexed { index, value -> - assertEquals(value, expected[index]) - } - } - - @Test - fun it_sets_the_dither_pattern_no_effect() { - val dither = GfxLib(gameResourceAccess, gameOptions).dither() - - dither.call(valueOf(0xFFFF)) - - val a = frameBuffer.blender.mix(byteArrayOf(1), 0, 0, null)?.get(0) ?: 0 - val b = frameBuffer.blender.mix(byteArrayOf(1), 1, 0, null)?.get(0) ?: 0 - val c = frameBuffer.blender.mix(byteArrayOf(1), 0, 1, null)?.get(0) ?: 0 - val d = frameBuffer.blender.mix(byteArrayOf(1), 1, 1, null)?.get(0) ?: 0 - - assertEquals(1, a) - assertEquals(1, b) - assertEquals(1, c) - assertEquals(1, d) - } - - @Test - fun it_reset_the_clip() { - val clip = GfxLib(gameResourceAccess, gameOptions).clip() - clip.call() - - assertEquals(0, frameBuffer.clipper.left) - assertEquals(0, frameBuffer.clipper.top) - assertEquals(10, frameBuffer.clipper.right) - assertEquals(10, frameBuffer.clipper.bottom) - } - - */ -} diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt new file mode 100644 index 00000000..4294f498 --- /dev/null +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/lua/StdLibTest.kt @@ -0,0 +1,97 @@ +package com.github.minigdx.tiny.lua + +import com.github.minigdx.tiny.engine.GameOptions +import com.github.minigdx.tiny.engine.GameResourceAccess +import com.github.minigdx.tiny.render.VirtualFrameBuffer +import dev.mokkery.mock +import org.luaj.vm2.LuaTable +import org.luaj.vm2.LuaValue.Companion.valueOf +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class StdLibTest { + val gameOptions = GameOptions(100, 100) + val gameResourceAccess = mock { } + val virtualFrameBuffer = mock { } + val lib = StdLib(gameOptions, gameResourceAccess, virtualFrameBuffer) + + @Test + fun `new should create instance with access to class fields`() { + val newFunc = lib.new() + + // Create a class table with active = true + val classTable = LuaTable() + classTable.set("active", valueOf(true)) + + // Create instance with one argument + val instance = newFunc.call(classTable) + + // Instance should have access to 'active' field via metatable lookup + val activeValue = instance.get("active") + assertTrue(activeValue.toboolean(), "Instance should have access to 'active' field from class") + } + + @Test + fun `new with two arguments should override class fields`() { + val newFunc = lib.new() + + // Create a class table with active = true + val classTable = LuaTable() + classTable.set("active", valueOf(true)) + + // Create instance with one argument + val default = LuaTable() + default.set("active", valueOf("test")) + val instance = newFunc.call(classTable, default) + + // Instance should have access to 'active' field via metatable lookup + val activeValue = instance.get("active") + assertEquals("test", activeValue.tojstring()) + } + + @Test + fun `new should keep instance neested instance`() { + val newFunc = lib.new() + + // Create a class table with active = true + val classTable = LuaTable() + classTable.set("active", valueOf(true)) + + // Create instance with one argument + val childInstance = newFunc.call(classTable) + + val classPlayerTable = LuaTable() + val defaultPlayer = LuaTable() + defaultPlayer.set("child", childInstance) + + val instance = newFunc.call(classPlayerTable, defaultPlayer) + // Instance should have access to 'active' field via metatable lookup + val activeValue = instance.get("child").get("active") + assertTrue(activeValue.toboolean(), "Nested instance should preserve metatable and access 'active' field") + } + + @Test + fun `merge should copy all keys from source into dest`() { + val mergeFunc = lib.merge() + + val src = LuaTable() + src.set("x", valueOf(1)) + src.set("y", valueOf(2)) + src.set("z", valueOf(3)) + + val dst = LuaTable() + dst.set("a", valueOf(4)) + dst.set("b", valueOf(5)) + + val result = mergeFunc.call(src, dst) + + // Source keys copied into dest + assertEquals(1, result.get("x").toint()) + assertEquals(2, result.get("y").toint()) + assertEquals(3, result.get("z").toint()) + // Original dest keys preserved + assertEquals(4, result.get("a").toint()) + assertEquals(5, result.get("b").toint()) + } +} diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/platform/test/HeadlessPlatform.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/platform/test/HeadlessPlatform.kt index e3eb8d6a..e318d3c0 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/platform/test/HeadlessPlatform.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/platform/test/HeadlessPlatform.kt @@ -121,6 +121,8 @@ class HeadlessPlatform( TODO("Not yet implemented") } + override fun isPlaying(): Boolean = false + override fun nextChunk(samples: Int): FloatData { TODO("Not yet implemented") } diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/EnvelopTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/EnvelopTest.kt index c1d3422f..994e1c27 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/EnvelopTest.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/EnvelopTest.kt @@ -2,24 +2,45 @@ package com.github.minigdx.tiny.sound import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class EnvelopTest { @Test - fun noteOn_attack_phase_increases_linearly() { + fun noteOn_attack_phase_uses_quadratic_curve() { val envelope = Envelop(attack0 = { 100 }, decay0 = { 50 }, sustain0 = { 0.7f }, release0 = { 200 }) + // Quadratic: value = (progress/attack)^2 assertEquals(0.0f, envelope.noteOn(0), 0.001f) - assertEquals(0.25f, envelope.noteOn(25), 0.001f) - assertEquals(0.5f, envelope.noteOn(50), 0.001f) + // 25/100 = 0.25, 0.25^2 = 0.0625 + assertEquals(0.0625f, envelope.noteOn(25), 0.001f) + // 50/100 = 0.5, 0.5^2 = 0.25 + assertEquals(0.25f, envelope.noteOn(50), 0.001f) + // 100/100 = 1.0, 1.0^2 = 1.0 assertEquals(1.0f, envelope.noteOn(100), 0.001f) } + @Test + fun noteOn_attack_is_below_linear() { + val envelope = Envelop(attack0 = { 100 }, decay0 = { 50 }, sustain0 = { 0.7f }, release0 = { 200 }) + + // Quadratic attack should be below the linear ramp at all midpoints + for (progress in 1..99) { + val actual = envelope.noteOn(progress) + val linear = progress.toFloat() / 100f + assertTrue(actual <= linear, "Quadratic attack should be <= linear at progress $progress") + } + } + @Test fun noteOn_decay_phase_decreases_to_sustain() { val envelope = Envelop(attack0 = { 100 }, decay0 = { 50 }, sustain0 = { 0.7f }, release0 = { 200 }) + // At attack end: 1.0 assertEquals(1.0f, envelope.noteOn(100), 0.001f) - assertEquals(0.85f, envelope.noteOn(125), 0.001f) + // Decay uses inverse-square: sustain + (1-sustain) * (1-linear)^2 + // At midpoint (25/50=0.5): 0.7 + 0.3 * (0.5)^2 = 0.7 + 0.075 = 0.775 + assertEquals(0.775f, envelope.noteOn(125), 0.001f) + // At end (50/50=1.0): 0.7 + 0.3 * 0^2 = 0.7 assertEquals(0.7f, envelope.noteOn(150), 0.001f) } @@ -37,8 +58,6 @@ class EnvelopTest { val envelope = Envelop(attack0 = { 0 }, decay0 = { 50 }, sustain0 = { 0.7f }, release0 = { 200 }) assertEquals(1.0f, envelope.noteOn(0), 0.001f) - assertEquals(0.85f, envelope.noteOn(25), 0.001f) - assertEquals(0.7f, envelope.noteOn(50), 0.001f) } @Test @@ -59,14 +78,16 @@ class EnvelopTest { } @Test - fun noteOff_release_phase_decreases_to_zero_from_sustain() { + fun noteOff_release_phase_uses_quadratic_curve() { val envelope = Envelop(attack0 = { 100 }, decay0 = { 50 }, sustain0 = { 0.7f }, release0 = { 200 }) - // noteOff now only handles the release phase from sustain level + // Quadratic release: sustain * (1 - progress/release)^2 assertEquals(0.7f, envelope.noteOff(0), 0.001f) - assertEquals(0.525f, envelope.noteOff(50), 0.001f) - assertEquals(0.35f, envelope.noteOff(100), 0.001f) - assertEquals(0.175f, envelope.noteOff(150), 0.001f) + // At 50/200 = 0.25: 0.7 * (0.75)^2 = 0.7 * 0.5625 = 0.39375 + assertEquals(0.39375f, envelope.noteOff(50), 0.01f) + // At 100/200 = 0.5: 0.7 * (0.5)^2 = 0.7 * 0.25 = 0.175 + assertEquals(0.175f, envelope.noteOff(100), 0.01f) + // At 200/200 = 1.0: 0.7 * 0^2 = 0.0 assertEquals(0.0f, envelope.noteOff(200), 0.001f) } @@ -80,22 +101,13 @@ class EnvelopTest { } @Test - fun noteOff_zero_release_immediately_silent() { + fun noteOff_zero_release_uses_minimum_release_for_click_prevention() { val envelope = Envelop(attack0 = { 100 }, decay0 = { 50 }, sustain0 = { 0.7f }, release0 = { 0 }) - assertEquals(0.0f, envelope.noteOff(0), 0.001f) - assertEquals(0.0f, envelope.noteOff(1), 0.001f) - assertEquals(0.0f, envelope.noteOff(100), 0.001f) - } - - @Test - fun noteOff_from_sustain_level() { - val envelope = Envelop(attack0 = { 100 }, decay0 = { 0 }, sustain0 = { 0.7f }, release0 = { 200 }) - - // noteOff starts from sustain level and decreases linearly over release time + // With minimum release (88 samples), noteOff at 0 should still return sustain assertEquals(0.7f, envelope.noteOff(0), 0.001f) - assertEquals(0.525f, envelope.noteOff(50), 0.001f) - assertEquals(0.35f, envelope.noteOff(100), 0.001f) + // Should reach 0 at the minimum release duration + assertEquals(0.0f, envelope.noteOff(Envelop.MIN_RELEASE_SAMPLES), 0.001f) } @Test @@ -115,9 +127,10 @@ class EnvelopTest { assertEquals(1.0f, envelope.noteOn(100), 0.001f) assertEquals(1.0f, envelope.noteOn(200), 0.001f) - // noteOff from sustain level (1.0) over release time + // noteOff from sustain level (1.0) with quadratic release assertEquals(1.0f, envelope.noteOff(0), 0.001f) - assertEquals(0.5f, envelope.noteOff(50), 0.001f) + // At 50/100 = 0.5: 1.0 * (0.5)^2 = 0.25 + assertEquals(0.25f, envelope.noteOff(50), 0.001f) assertEquals(0.0f, envelope.noteOff(100), 0.001f) } @@ -135,4 +148,27 @@ class EnvelopTest { assertEquals(0.0f, envelope.noteOff(50), 0.001f) assertEquals(0.0f, envelope.noteOff(100), 0.001f) } + + @Test + fun noteOn_values_always_between_zero_and_one() { + val envelope = Envelop(attack0 = { 100 }, decay0 = { 100 }, sustain0 = { 0.5f }, release0 = { 100 }) + + for (progress in 0..500) { + val value = envelope.noteOn(progress) + assertTrue(value >= 0.0f, "noteOn value should be >= 0 at progress $progress, got $value") + assertTrue(value <= 1.0f, "noteOn value should be <= 1 at progress $progress, got $value") + } + } + + @Test + fun noteOff_values_always_between_zero_and_sustain() { + val sustain = 0.8f + val envelope = Envelop(attack0 = { 100 }, decay0 = { 100 }, sustain0 = { sustain }, release0 = { 200 }) + + for (progress in 0..300) { + val value = envelope.noteOff(progress) + assertTrue(value >= 0.0f, "noteOff value should be >= 0 at progress $progress, got $value") + assertTrue(value <= sustain + 0.001f, "noteOff value should be <= sustain at progress $progress, got $value") + } + } } diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/HarmonizerTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/HarmonizerTest.kt index 64cee04c..159e666e 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/HarmonizerTest.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/HarmonizerTest.kt @@ -3,6 +3,7 @@ package com.github.minigdx.tiny.sound import com.github.minigdx.tiny.lua.Note import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class HarmonizerTest { @Test @@ -15,22 +16,33 @@ class HarmonizerTest { } @Test - fun generate_with_single_harmonic_applies_amplitude() { + fun generate_with_single_harmonic_below_one_no_normalization() { val harmonizer = Harmonizer(harmonics0 = { floatArrayOf(0.5f) }) val generator = { freq: Float, sample: Int -> 2.0f } val result = harmonizer.generate(Note.A4, 0, generator) - // Should be: 0.5 * 2.0 = 1.0 + // totalAmplitude = 0.5 (< 1.0), so no normalization: 0.5 * 2.0 = 1.0 assertEquals(1.0f, result, 0.001f) } @Test - fun generate_with_multiple_harmonics_sums_contributions() { + fun generate_with_multiple_harmonics_summing_to_one_no_normalization() { val harmonizer = Harmonizer(harmonics0 = { floatArrayOf(0.5f, 0.3f, 0.2f) }) val generator = { freq: Float, sample: Int -> 1.0f } val result = harmonizer.generate(Note.A4, 0, generator) - // Should be: (0.5 * 1.0) + (0.3 * 1.0) + (0.2 * 1.0) = 1.0 + // totalAmplitude = 1.0 (not > 1.0), so no normalization: 0.5 + 0.3 + 0.2 = 1.0 + assertEquals(1.0f, result, 0.001f) + } + + @Test + fun generate_normalizes_when_total_amplitude_exceeds_one() { + val harmonizer = Harmonizer(harmonics0 = { floatArrayOf(1.0f, 1.0f) }) + val generator = { freq: Float, sample: Int -> 1.0f } + + val result = harmonizer.generate(Note.A4, 0, generator) + // totalAmplitude = 2.0, normFactor = 0.5 + // raw sum = 1.0 + 1.0 = 2.0, normalized = 2.0 * 0.5 = 1.0 assertEquals(1.0f, result, 0.001f) } @@ -50,10 +62,10 @@ class HarmonizerTest { harmonizer.generate(Note.A4, 42, generator) assertEquals(2, capturedFrequencies.size) - assertEquals(fundamentalFreq * 1, capturedFrequencies[0], 0.001f) // 1st harmonic (2x fundamental) - assertEquals(fundamentalFreq * 2, capturedFrequencies[1], 0.001f) // 2nd harmonic (3x fundamental) - assertEquals(42, capturedSamples[0]) // sample passed to generator - assertEquals(42, capturedSamples[1]) // sample passed to generator + assertEquals(fundamentalFreq * 1, capturedFrequencies[0], 0.001f) // fundamental (1x) + assertEquals(fundamentalFreq * 2, capturedFrequencies[1], 0.001f) // 2nd harmonic (2x) + assertEquals(42, capturedSamples[0]) + assertEquals(42, capturedSamples[1]) } @Test @@ -70,9 +82,9 @@ class HarmonizerTest { harmonizer.generate(Note.C0, 0, generator) assertEquals(3, capturedFrequencies.size) - assertEquals(fundamentalFreq * 1, capturedFrequencies[0], 0.001f) // 1st harmonic (1x fundamental) - assertEquals(fundamentalFreq * 2, capturedFrequencies[1], 0.001f) // 2nd harmonic (2x fundamental) - assertEquals(fundamentalFreq * 3, capturedFrequencies[2], 0.001f) // 3rd harmonic (3x fundamental) + assertEquals(fundamentalFreq * 1, capturedFrequencies[0], 0.001f) // fundamental (1x) + assertEquals(fundamentalFreq * 2, capturedFrequencies[1], 0.001f) // 2nd harmonic (2x) + assertEquals(fundamentalFreq * 3, capturedFrequencies[2], 0.001f) // 3rd harmonic (3x) } @Test @@ -81,7 +93,7 @@ class HarmonizerTest { val generator = { freq: Float, sample: Int -> 2.0f } val result = harmonizer.generate(Note.C0, 0, generator) - // Should be: (0.0 * 2.0) + (1.0 * 2.0) + (0.0 * 2.0) = 2.0 + // totalAmplitude = 1.0 (not > 1.0), no normalization: (0.0 * 2.0) + (1.0 * 2.0) + (0.0 * 2.0) = 2.0 assertEquals(2.0f, result, 0.001f) } @@ -91,7 +103,7 @@ class HarmonizerTest { val generator = { freq: Float, sample: Int -> 1.0f } val result = harmonizer.generate(Note.C0, 0, generator) - // Should be: (0.5 * 1.0) + (-0.3 * 1.0) = 0.2 + // totalAmplitude = 0.5 + (-0.3) = 0.2 (< 1.0), no normalization: 0.5 - 0.3 = 0.2 assertEquals(0.2f, result, 0.001f) } @@ -128,8 +140,10 @@ class HarmonizerTest { } val result = harmonizer.generate(Note.C0, 0, generator) - // Should be: (0.8 * 1) + (0.6 * 2) + (0.4 * 3) = 0.8 + 1.2 + 1.2 = 3.2 - assertEquals(3.2f, result, 0.001f) + // totalAmplitude = 0.8 + 0.6 + 0.4 = 1.8 > 1.0, normFactor = 1/1.8 + // raw sum = (0.8 * 1) + (0.6 * 2) + (0.4 * 3) = 0.8 + 1.2 + 1.2 = 3.2 + // normalized = 3.2 / 1.8 ≈ 1.778 + assertEquals(3.2f / 1.8f, result, 0.01f) } @Test @@ -139,8 +153,9 @@ class HarmonizerTest { val generator = { freq: Float, sample: Int -> 1.0f } val result = harmonizer.generate(Note.C0, 0, generator) - // Should be: 0.1 + 0.2 + 0.3 + ... + 1.0 = sum of 0.1 to 1.0 = 5.5 - assertEquals(5.5f, result, 0.001f) + // totalAmplitude = 0.1 + 0.2 + ... + 1.0 = 5.5 > 1.0, normFactor = 1/5.5 + // raw sum = 5.5, normalized = 5.5 / 5.5 = 1.0 + assertEquals(1.0f, result, 0.001f) } @Test @@ -154,7 +169,20 @@ class HarmonizerTest { } val result = harmonizer.generate(Note.C0, 0, generator) - // Should be: (1.0 * 3.14) + (1.0 * 2.71) = 5.85 - assertEquals(5.85f, result, 0.001f) + // totalAmplitude = 2.0 > 1.0, normFactor = 0.5 + // raw sum = (1.0 * 3.14) + (1.0 * 2.71) = 5.85 + // normalized = 5.85 * 0.5 = 2.925 + assertEquals(2.925f, result, 0.001f) + } + + @Test + fun generate_output_bounded_with_unit_generator() { + // When generator returns values in [-1,1] and harmonics sum > 1, + // the normalizer should ensure output stays in [-1,1] + val harmonizer = Harmonizer(harmonics0 = { floatArrayOf(1.0f, 0.8f, 0.6f, 0.4f) }) + val generator = { freq: Float, sample: Int -> 1.0f } + + val result = harmonizer.generate(Note.A4, 0, generator) + assertTrue(result >= -1.0f && result <= 1.0f, "Normalized output should be bounded, got $result") } } diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/MusicGeneratorTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/MusicGeneratorTest.kt new file mode 100644 index 00000000..32078df5 --- /dev/null +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/MusicGeneratorTest.kt @@ -0,0 +1,369 @@ +package com.github.minigdx.tiny.sound + +import com.github.minigdx.tiny.lua.Note +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class MusicGeneratorTest { + private fun createSequence(): MusicalSequence = MusicalSequence(0) + + @Test + fun determinism_same_config_produces_identical_beats() { + val config = MusicConfiguration(seed = 123L) + val seq1 = createSequence() + val seq2 = createSequence() + + MusicGenerator.generate(seq1, config) + MusicGenerator.generate(seq2, config) + + for (trackIdx in 0..3) { + val beats1 = seq1.tracks[trackIdx].beats + val beats2 = seq2.tracks[trackIdx].beats + assertEquals(beats1.size, beats2.size, "Track $trackIdx beat count mismatch") + for (i in beats1.indices) { + assertEquals( + beats1[i].note, + beats2[i].note, + "Track $trackIdx beat $i note mismatch", + ) + assertEquals( + beats1[i].volume, + beats2[i].volume, + "Track $trackIdx beat $i volume mismatch", + ) + } + } + } + + @Test + fun different_seeds_produce_different_lead_track() { + val config1 = MusicConfiguration(seed = 1L) + val config2 = MusicConfiguration(seed = 999L) + val seq1 = createSequence() + val seq2 = createSequence() + + MusicGenerator.generate(seq1, config1) + MusicGenerator.generate(seq2, config2) + + // Lead track (index 2) should differ with different seeds + val lead1 = seq1.tracks[2].beats.mapNotNull { it.note } + val lead2 = seq2.tracks[2].beats.mapNotNull { it.note } + assertTrue(lead1 != lead2, "Lead tracks should differ with different seeds") + + // Chord track (index 0) should be identical regardless of seed + val chords1 = seq1.tracks[0].beats.map { it.note } + val chords2 = seq2.tracks[0].beats.map { it.note } + assertEquals(chords1, chords2, "Chord tracks should be identical with different seeds") + + // Bass track (index 1) should be identical regardless of seed + val bass1 = seq1.tracks[1].beats.map { it.note } + val bass2 = seq2.tracks[1].beats.map { it.note } + assertEquals(bass1, bass2, "Bass tracks should be identical with different seeds") + + // Drum track (index 3) should be identical regardless of seed + val drums1 = seq3drums(seq1) + val drums2 = seq3drums(seq2) + assertEquals(drums1, drums2, "Drum tracks should be identical with different seeds") + } + + private fun seq3drums(seq: MusicalSequence) = seq.tracks[3].beats.map { it.note } + + @Test + fun each_track_has_33_beats() { + val config = MusicConfiguration() + val seq = createSequence() + MusicGenerator.generate(seq, config) + + for (trackIdx in 0..3) { + assertEquals(33, seq.tracks[trackIdx].beats.size, "Track $trackIdx should have 33 beats") + } + } + + @Test + fun generated_notes_within_valid_range() { + val config = MusicConfiguration(seed = 42L) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + for (trackIdx in 0..3) { + seq.tracks[trackIdx].beats.forEach { beat -> + if (beat.note != null) { + val note = beat.note!! + assertTrue(note.index in 1..108, "Note index ${note.index} out of range") + assertTrue(beat.volume in 0f..1f, "Volume ${beat.volume} out of range") + } + } + } + } + + @Test + fun chord_track_uses_configured_instrument_and_volume() { + val config = MusicConfiguration(chordInstrument = 5, chordVolume = 0.7f) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + assertEquals(5, seq.tracks[0].instrumentIndex) + assertEquals(0.7f, seq.tracks[0].volume) + } + + @Test + fun bass_track_uses_configured_instrument_and_volume() { + val config = MusicConfiguration(bassInstrument = 4, bassVolume = 0.6f) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + assertEquals(4, seq.tracks[1].instrumentIndex) + assertEquals(0.6f, seq.tracks[1].volume) + } + + @Test + fun lead_track_uses_configured_instrument_and_volume() { + val config = MusicConfiguration(leadInstrument = 2, leadVolume = 0.5f) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + assertEquals(2, seq.tracks[2].instrumentIndex) + assertEquals(0.5f, seq.tracks[2].volume) + } + + @Test + fun drum_track_uses_configured_instrument_and_volume() { + val config = MusicConfiguration(drumInstrument = 3, drumVolume = 0.8f) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + assertEquals(3, seq.tracks[3].instrumentIndex) + assertEquals(0.8f, seq.tracks[3].volume) + } + + @Test + fun tempo_is_set_from_config() { + val config = MusicConfiguration(bpm = 140) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + assertEquals(140, seq.tempo) + } + + @Test + fun drum_pattern_places_correct_notes() { + val config = MusicConfiguration(drumPattern = "Rock") + val seq = createSequence() + MusicGenerator.generate(seq, config) + + val drums = seq.tracks[3] + // Rock pattern: kick on 0,4,8,12,... snare on 2,6,10,14,... hihat on all others + val kickNote = Note.fromName("C2") + val snareNote = Note.fromName("C4") + val hihatNote = Note.fromName("C6") + + assertEquals(kickNote, drums.beats[0].note, "Beat 0 should be kick") + assertEquals(hihatNote, drums.beats[1].note, "Beat 1 should be hihat") + assertEquals(snareNote, drums.beats[2].note, "Beat 2 should be snare") + assertEquals(hihatNote, drums.beats[3].note, "Beat 3 should be hihat") + assertEquals(kickNote, drums.beats[4].note, "Beat 4 should be kick") + } + + @Test + fun all_scales_produce_valid_output() { + for (scaleName in MusicGenerator.SCALE_NAMES) { + val config = MusicConfiguration(scaleName = scaleName, seed = 42L) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + // All tracks should have notes + for (trackIdx in 0..3) { + val hasNotes = seq.tracks[trackIdx].beats.any { it.note != null } + assertTrue(hasNotes, "Scale '$scaleName' track $trackIdx should have notes") + } + } + } + + @Test + fun all_progressions_produce_valid_output() { + for (progName in MusicGenerator.PROGRESSION_NAMES) { + val config = MusicConfiguration(progressionName = progName, seed = 42L) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + val hasChords = seq.tracks[0].beats.any { it.note != null } + assertTrue(hasChords, "Progression '$progName' should produce chord notes") + } + } + + @Test + fun serialization_round_trip_with_configuration() { + val config = MusicConfiguration( + root = "D", + scaleName = "Minor", + progressionName = "Melancholy", + leadStyle = "Bouncy", + drumPattern = "Dance", + chordInstrument = 1, + bassInstrument = 4, + leadInstrument = 5, + drumInstrument = 3, + chordVolume = 0.4f, + bassVolume = 0.5f, + leadVolume = 0.3f, + drumVolume = 0.6f, + bpm = 100, + seed = 777L, + ) + + val music = Music() + val seq = music.sequences[0] + MusicGenerator.generate(seq, config) + seq.configuration = config + + // Capture expected notes before serialization + val expectedNotes = seq.tracks.map { track -> + track.beats.map { it.note to it.volume } + } + + // Serialize (clears beats for config-based sequences) + val json = music.serialize() + + // Deserialize + val restored = Music.deserialize(json) + val restoredSeq = restored.sequences[0] + + // Config should be preserved + assertNotNull(restoredSeq.configuration) + assertEquals(config, restoredSeq.configuration) + + // Regenerate from config + MusicGenerator.generate(restoredSeq, restoredSeq.configuration!!) + + // Notes should match + for (trackIdx in 0..3) { + val restoredBeats = restoredSeq.tracks[trackIdx].beats + for (i in expectedNotes[trackIdx].indices) { + if (i < restoredBeats.size) { + assertEquals( + expectedNotes[trackIdx][i].first, + restoredBeats[i].note, + "Track $trackIdx beat $i note mismatch after round-trip", + ) + } + } + } + } + + @Test + fun serialization_without_configuration_preserves_beats() { + val music = Music() + val seq = music.sequences[0] + // Manually set a note without configuration + seq.tracks[0].beats[0] = MusicalNote(Note.C4, 0f, 1f, 0.8f) + + val json = music.serialize() + val restored = Music.deserialize(json) + + assertNull(restored.sequences[0].configuration) + } + + @Test + fun config_based_serialization_reduces_size() { + val config = MusicConfiguration(seed = 42L) + + // Music with config-based sequence + val musicWithConfig = Music() + MusicGenerator.generate(musicWithConfig.sequences[0], config) + musicWithConfig.sequences[0].configuration = config + val jsonWithConfig = musicWithConfig.serialize() + + // Music without config (all beats serialized) + val musicWithoutConfig = Music() + MusicGenerator.generate(musicWithoutConfig.sequences[0], config) + val jsonWithoutConfig = musicWithoutConfig.serialize() + + assertTrue( + jsonWithConfig.length < jsonWithoutConfig.length, + "Config-based serialization should be smaller: " + + "${jsonWithConfig.length} vs ${jsonWithoutConfig.length}", + ) + } + + @Test + fun serialized_json_contains_configuration_fields() { + val config = MusicConfiguration( + root = "D", + scaleName = "Minor", + bpm = 100, + seed = 777L, + ) + + val music = Music() + MusicGenerator.generate(music.sequences[0], config) + music.sequences[0].configuration = config + + val json = music.serialize() + + // Configuration key must be present + assertTrue(json.contains("\"configuration\""), "JSON must contain 'configuration' key") + // Key config fields must be serialized + assertTrue(json.contains("\"root\""), "JSON must contain 'root' field. JSON: $json") + assertTrue(json.contains("\"scaleName\""), "JSON must contain 'scaleName' field") + assertTrue(json.contains("\"seed\""), "JSON must contain 'seed' field") + assertTrue(json.contains("\"bpm\""), "JSON must contain 'bpm' field") + } + + @Test + fun lead_beat_32_is_silent_for_clean_looping() { + for (style in MusicGenerator.LEAD_STYLES) { + val config = MusicConfiguration(leadStyle = style, seed = 42L) + val seq = createSequence() + MusicGenerator.generate(seq, config) + + assertNull( + seq.tracks[2].beats[32].note, + "Lead beat 32 should be silent for '$style' to allow clean looping", + ) + } + } + + @Test + fun tracks_fade_volume_at_end_for_smooth_looping() { + val config = MusicConfiguration() + val seq = createSequence() + MusicGenerator.generate(seq, config) + + // Chord track: last beat (31) should have lower volume than early beats in last bar (24) + val chordBeat24 = seq.tracks[0].beats[24] + val chordBeat31 = seq.tracks[0].beats[31] + assertTrue( + chordBeat31.volume < chordBeat24.volume, + "Chord should fade at end: beat 31 (${chordBeat31.volume}) < beat 24 (${chordBeat24.volume})", + ) + + // Bass track: last fifth (beat 30) should have lower volume than earlier fifth (beat 26) + val bassBeat26 = seq.tracks[1].beats[26] + val bassBeat30 = seq.tracks[1].beats[30] + assertTrue( + bassBeat30.volume < bassBeat26.volume, + "Bass should fade at end: beat 30 (${bassBeat30.volume}) < beat 26 (${bassBeat26.volume})", + ) + } + + @Test + fun unused_sequences_have_no_beats_in_json() { + val music = Music() + // Only generate for sequence 0 + val config = MusicConfiguration(seed = 42L) + MusicGenerator.generate(music.sequences[0], config) + music.sequences[0].configuration = config + + val json = music.serialize() + + // Count occurrences of "note":null - should be minimal + val nullNoteCount = "\"note\":null".toRegex().findAll(json).count() + assertTrue( + nullNoteCount == 0, + "Unused sequences should not serialize null-note beats, found $nullNoteCount", + ) + } +} diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/OscillatorTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/OscillatorTest.kt index 6d7464fd..5db3e147 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/OscillatorTest.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/OscillatorTest.kt @@ -184,6 +184,52 @@ class OscillatorTest { assertEquals(result1, result2, 0.001f, "Same inputs should produce same outputs") } + @Test + fun emit_drum_bass_snare_hihat_produce_bounded_output() { + val oscillator = Oscillator(waveType0 = { Instrument.WaveType.DRUM }) + // C4 = ~261.63 Hz (bass drum), D4 = ~293.66 Hz (snare), E4 = ~329.63 Hz (hi-hat closed) + val frequencies = listOf(261.63f, 293.66f, 329.63f) + for (freq in frequencies) { + for (sample in 0..2000 step 10) { + val result = oscillator.emit(freq, sample) + assertTrue( + result >= -1.0f && result <= 1.0f, + "Drum at freq $freq should be bounded [-1, 1], got $result at sample $sample", + ) + } + } + } + + @Test + fun emit_drum_different_notes_produce_different_sounds() { + // C4 (bass drum) vs D4 (snare) should produce different waveforms + val oscBass = Oscillator(waveType0 = { Instrument.WaveType.DRUM }) + val oscSnare = Oscillator(waveType0 = { Instrument.WaveType.DRUM }) + + val bassSamples = (0..100).map { oscBass.emit(261.63f, it) } + val snareSamples = (0..100).map { oscSnare.emit(293.66f, it) } + + // The two drum parts should not produce identical output + val different = bassSamples.zip(snareSamples).any { (a, b) -> abs(a - b) > 0.01f } + assertTrue(different, "Bass drum and snare should produce different sounds") + } + + @Test + fun emit_drum_sound_decays_over_time() { + val oscillator = Oscillator(waveType0 = { Instrument.WaveType.DRUM }) + // Use C4 (bass drum) - should decay + val earlyEnergy = (0..50).map { abs(oscillator.emit(261.63f, it)) }.average() + + val oscillator2 = Oscillator(waveType0 = { Instrument.WaveType.DRUM }) + // Late samples - well after the drum has decayed + val lateEnergy = (20000..20050).map { abs(oscillator2.emit(261.63f, it)) }.average() + + assertTrue( + earlyEnergy > lateEnergy, + "Drum sound should decay over time: early=$earlyEnergy, late=$lateEnergy", + ) + } + @Test fun emit_all_wave_types_produce_bounded_output() { val waveTypes = listOf( @@ -193,6 +239,7 @@ class OscillatorTest { Instrument.WaveType.SAW_TOOTH, Instrument.WaveType.PULSE, Instrument.WaveType.NOISE, + Instrument.WaveType.DRUM, ) val frequency = 440.0f diff --git a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/SoundManagerTest.kt b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/SoundManagerTest.kt index 8f372fde..afe5b2ca 100644 --- a/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/SoundManagerTest.kt +++ b/tiny-engine/src/commonTest/kotlin/com/github/minigdx/tiny/sound/SoundManagerTest.kt @@ -24,6 +24,8 @@ class SoundManagerTest { override fun stop() {} + override fun isPlaying(): Boolean = false + override fun nextChunk(samples: Int): FloatData { TODO("Not yet implemented") } diff --git a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/Main.kt b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/Main.kt index 6456000c..9a2027d0 100644 --- a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/Main.kt +++ b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/Main.kt @@ -1,12 +1,16 @@ package com.github.minigdx.tiny +import com.github.minigdx.tiny.engine.GameConfig import com.github.minigdx.tiny.engine.GameEngine -import com.github.minigdx.tiny.engine.GameOptions +import com.github.minigdx.tiny.file.AjaxStream import com.github.minigdx.tiny.file.CommonVirtualFileSystem import com.github.minigdx.tiny.log.StdOutLogger import com.github.minigdx.tiny.platform.webgl.WebGlPlatform import kotlinx.browser.document import kotlinx.browser.window +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.dom.appendText import org.w3c.dom.Element import org.w3c.dom.HTMLCanvasElement @@ -20,9 +24,9 @@ fun getRootPath(): String { var rootPath = window.location.protocol + "//" + window.location.host + window.location.pathname rootPath = rootPath.substring(0, rootPath.lastIndexOf('/')) - // Remove the last "/" to a avoid double slash when the engine getting resources. + // Remove the last "/" to avoid double slash when the engine getting resources. if (rootPath.endsWith("/")) { - rootPath.dropLast(1) + rootPath = rootPath.dropLast(1) } return rootPath } @@ -30,10 +34,13 @@ fun getRootPath(): String { fun main() { val rootPath = getRootPath() val tinyGameTag = document.getElementsByTagName("tiny-game") - setupGames(rootPath, tinyGameTag) + + CoroutineScope(Dispatchers.Main).launch { + setupGames(rootPath, tinyGameTag) + } } -fun setupGames( +suspend fun setupGames( rootPath: String, tinyGameTag: HTMLCollection, ) { @@ -64,65 +71,33 @@ fun setupGames( ) } - tinyGameTag.forEachIndexed { index, game -> - - val gameId = game.getAttribute("id")!! - val gameWidth = game.getAttribute("width")?.toInt() ?: 128 - val gameHeight = game.getAttribute("height")?.toInt() ?: 128 - val gameZoom = game.getAttribute("zoom")?.toInt() ?: 1 - val hideMouse = game.getAttribute("mouse")?.toBoolean() ?: false - - val sprWidth = game.getAttribute("spritew")?.toInt() ?: 16 - val sprHeight = game.getAttribute("spriteh")?.toInt() ?: 16 - - val scripts = game.getElementsByTagName("tiny-script").map { script -> - script.getAttribute("name") - }.filterNotNull() - - val levels = game.getElementsByTagName("tiny-level").map { level -> - level.getAttribute("name") - }.filterNotNull() + for (index in 0 until tinyGameTag.length) { + val game = tinyGameTag[index] ?: continue + val gamePath = game.getAttribute("game") ?: "." + val gameRootPath = "$rootPath/$gamePath".trimEnd('/') - val sounds = game.getElementsByTagName("tiny-sound").map { level -> - level.getAttribute("name") - }.filterNotNull() + val logger = StdOutLogger("game-$index") + logger.debug("TINY-JS") { "Boot the game using the URL '$gameRootPath'." } - val spritesheets = game.getElementsByTagName("tiny-spritesheet").map { spritesheet -> - spritesheet.getAttribute("name") - }.filterNotNull() + // Fetch and parse _tiny.json + val configUrl = "$gameRootPath/_tiny.json" + val configBytes = AjaxStream(configUrl).read() + val configJson = configBytes.decodeToString() + val config = GameConfig.parse(configJson) + val gameOptions = config.toGameOptions().copy(gutter = 0 to 0) - val colors = - game.getElementsByTagName("tiny-colors")[0]?.getAttribute("name")?.split(",")?.toList() ?: emptyList() val canvas = document.createElement("canvas") - canvas.setAttribute("width", (gameWidth * gameZoom).toString()) - canvas.setAttribute("height", (gameHeight * gameZoom).toString()) + canvas.setAttribute("width", (gameOptions.width * gameOptions.zoom).toString()) + canvas.setAttribute("height", (gameOptions.height * gameOptions.zoom).toString()) canvas.setAttribute("tabindex", "1") - if (hideMouse) { + if (gameOptions.hideMouseCursor) { canvas.setAttribute("style", "cursor: none;") } game.appendChild(canvas) - val gameOptions = - GameOptions( - width = gameWidth, - height = gameHeight, - palette = colors.ifEmpty { listOf("#FFFFFF", "#000000") }, - gameScripts = scripts, - spriteSheets = spritesheets, - gameLevels = levels, - sound = sounds.firstOrNull(), - zoom = gameZoom, - gutter = 0 to 0, - spriteSize = sprWidth to sprHeight, - hideMouseCursor = hideMouse, - ) - - val logger = StdOutLogger("game-$index") - logger.debug("TINY-JS") { "Boot the game using the URL '$rootPath'." } - GameEngine( gameOptions = gameOptions, - platform = WebGlPlatform(canvas as HTMLCanvasElement, gameOptions, gameId, rootPath), + platform = WebGlPlatform(canvas as HTMLCanvasElement, gameOptions, config.id, gameRootPath), vfs = CommonVirtualFileSystem(), logger = logger, ).main() diff --git a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/AjaxStream.kt b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/AjaxStream.kt index 202c5d9d..1dfca278 100644 --- a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/AjaxStream.kt +++ b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/AjaxStream.kt @@ -28,17 +28,4 @@ class AjaxStream(private val url: String) : SourceStream { jsonFile.send() } } - - override suspend fun exists(): Boolean { - return suspendCoroutine { continuation -> - val jsonFile = XMLHttpRequest() - jsonFile.responseType = XMLHttpRequestResponseType.Companion.ARRAYBUFFER - jsonFile.open("HEAD", url, true) - - jsonFile.onload = { _ -> - continuation.resumeWith(Result.success(jsonFile.status == 200.toShort())) - } - jsonFile.send() - } - } } diff --git a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/ImageDataStream.kt b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/ImageDataStream.kt index 2920018c..bf638ddf 100644 --- a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/ImageDataStream.kt +++ b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/file/ImageDataStream.kt @@ -8,25 +8,9 @@ import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.Image import org.w3c.dom.events.Event import org.w3c.dom.events.EventListener -import org.w3c.xhr.ARRAYBUFFER -import org.w3c.xhr.XMLHttpRequest -import org.w3c.xhr.XMLHttpRequestResponseType import kotlin.coroutines.suspendCoroutine class ImageDataStream(val url: String) : SourceStream { - override suspend fun exists(): Boolean { - return suspendCoroutine { continuation -> - val jsonFile = XMLHttpRequest() - jsonFile.responseType = XMLHttpRequestResponseType.Companion.ARRAYBUFFER - jsonFile.open("HEAD", url, true) - - jsonFile.onload = { _ -> - continuation.resumeWith(Result.success(jsonFile.status == 200.toShort())) - } - jsonFile.send() - } - } - override suspend fun read(): ImageData { return suspendCoroutine { continuation -> diff --git a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/platform/webgl/WebSoundHandler.kt b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/platform/webgl/WebSoundHandler.kt index c5b8bf2d..7cfc7e04 100644 --- a/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/platform/webgl/WebSoundHandler.kt +++ b/tiny-engine/src/jsMain/kotlin/com/github/minigdx/tiny/platform/webgl/WebSoundHandler.kt @@ -4,26 +4,34 @@ import com.github.minigdx.tiny.sound.ChunkGenerator import com.github.minigdx.tiny.sound.SoundHandler import com.github.minigdx.tiny.util.FloatData import web.audio.AudioBufferSourceNode +import web.events.EventHandler class WebSoundHandler( private val chunkGenerator: ChunkGenerator, private val soundManager: WebSoundManager, ) : SoundHandler { private var audioNode: AudioBufferSourceNode? = null + private var playing = false override fun play() { audioNode = soundManager.playChunkGenerator(chunkGenerator) + playing = true + audioNode?.onended = EventHandler { playing = false } } override fun loop() { audioNode = soundManager.playChunkGenerator(chunkGenerator, loop = true) + playing = true } override fun stop() { + playing = false audioNode?.stop() soundManager.removeSoundHandler(this) } + override fun isPlaying(): Boolean = playing + override fun nextChunk(samples: Int): FloatData { return chunkGenerator.generateChunk(samples) } diff --git a/tiny-engine/src/jsMain/resources/index.html b/tiny-engine/src/jsMain/resources/index.html index 6005cf33..2a7c6ca1 100644 --- a/tiny-engine/src/jsMain/resources/index.html +++ b/tiny-engine/src/jsMain/resources/index.html @@ -3,25 +3,12 @@ Tiny - {GAME_NAME} +
- - - - - - - - - - - - - - - +
- + diff --git a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/GlfwPlatform.kt b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/GlfwPlatform.kt index 936df01b..895bad21 100644 --- a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/GlfwPlatform.kt +++ b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/GlfwPlatform.kt @@ -10,6 +10,7 @@ import com.github.minigdx.tiny.file.InputStreamStream import com.github.minigdx.tiny.file.SoundDataSourceStream import com.github.minigdx.tiny.file.SourceStream import com.github.minigdx.tiny.file.VirtualFileSystem +import com.github.minigdx.tiny.graphic.PixelArray import com.github.minigdx.tiny.graphic.PixelFormat.RGBA import com.github.minigdx.tiny.input.InputHandler import com.github.minigdx.tiny.input.InputManager @@ -19,6 +20,7 @@ import com.github.minigdx.tiny.platform.Platform import com.github.minigdx.tiny.platform.SoundData import com.github.minigdx.tiny.platform.WindowManager import com.github.minigdx.tiny.platform.performance.PerformanceMonitor +import com.github.minigdx.tiny.render.VirtualFrameBuffer import com.github.minigdx.tiny.sound.BITS_PER_SAMPLE import com.github.minigdx.tiny.sound.CHANNELS import com.github.minigdx.tiny.sound.IS_BIG_ENDIAN @@ -37,11 +39,11 @@ import kotlinx.coroutines.runBlocking import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW.GLFW_CURSOR import org.lwjgl.glfw.GLFW.GLFW_CURSOR_HIDDEN +import org.lwjgl.glfw.GLFWImage import org.lwjgl.opengl.GL import org.lwjgl.system.MemoryUtil import java.awt.image.BufferedImage import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.TimeUnit import javax.imageio.ImageIO @@ -61,19 +63,43 @@ class GlfwPlatform( private val homeDirectory: File, private val jarResourcePrefix: String = "", ) : Platform { + /** + * Resolve [name] relative to [baseDirectory], ensuring the result stays + * within [baseDirectory] to prevent path traversal. + */ + private fun safeResolve( + baseDirectory: File, + name: String, + ): File { + val resolved = baseDirectory.resolve(name).canonicalFile + val root = baseDirectory.canonicalFile + require(resolved.path.startsWith(root.path + File.separator) || resolved.path == root.path) { + "Path traversal detected: '$name' resolves outside '${root.path}'" + } + return baseDirectory.resolve(name) + } + override val performanceMonitor: PerformanceMonitor = LwjglPerformanceMonitor() private var window: Long = 0 + private lateinit var windowManager: WindowManager + private var lastFrame: Long = getTime() - // Keep 30 seconds at 60 frames per seconds - private val gifFrameCache: MutableFixedSizeList = MutableFixedSizeList(gameOptions.record.toInt() * FPS) + // Keep N seconds at 60 frames per seconds (ByteArray of palette indices per frame) + private val gifFrameCache: MutableFixedSizeList = MutableFixedSizeList(gameOptions.record.toInt() * FPS) - private var lastDraw: ByteArray? = null + private var lastDraw: PixelArray = PixelArray(gameOptions.width, gameOptions.height) private val lwjglInputHandler = LwjglInput(gameOptions) + /** + * Returns the input handler for remote control injection. + * Used by the debug server to inject key events from external programs. + */ + fun remoteInput(): LwjglInput = lwjglInputHandler + private val recordScope = CoroutineScope(Dispatchers.IO) /** @@ -147,6 +173,8 @@ class GlfwPlatform( GLFW.glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN) } + setWindowIcon() + // Make the OpenGL context current GLFW.glfwMakeContextCurrent(window) @@ -154,7 +182,9 @@ class GlfwPlatform( GLFW.glfwSwapInterval(1) // Make the window visible - GLFW.glfwShowWindow(window) + if (!gameOptions.headless) { + GLFW.glfwShowWindow(window) + } // Get the size of the device window val tmpWidth = MemoryUtil.memAllocInt(1) @@ -169,12 +199,18 @@ class GlfwPlatform( GL.createCapabilities(true) - return WindowManager( + windowManager = WindowManager( windowWidth = tmpWidth.get(), windowHeight = tmpHeight.get(), screenWidth = tmpFrameBufferWidth.get(), screenHeight = tmpFrameBufferHeight.get(), ) + + GLFW.glfwSetFramebufferSizeCallback(window) { _, width, height -> + windowManager.updateScreenDimensions(width, height) + } + + return windowManager } override fun initRenderManager(windowManager: WindowManager): Kgl { @@ -206,38 +242,45 @@ class GlfwPlatform( GLFW.glfwTerminate() } - override fun endGameLoop() = Unit + override fun endGameLoop() { + GLFW.glfwSetWindowShouldClose(window, true) + } + + override fun clearRecordingCache() { + gifFrameCache.clear() + } + + override fun newFrameRendered(virtualFrameBuffer: VirtualFrameBuffer) { + virtualFrameBuffer.readFrameBuffer().copyInto(lastDraw) + gifFrameCache.add(lastDraw.pixels.copyOf()) + } override fun record() { val origin = newFile("video", "gif") - logger.info("GLWF") { "Starting to generate GIF in '${origin.absolutePath}' (Wait for it...)" } - val buffer = - mutableListOf().apply { - addAll(gifFrameCache) - } + logger.info("GLFW") { "Starting to generate GIF in '${origin.absolutePath}' (Wait for it...)" } + val buffer = mutableListOf().apply { + addAll(gifFrameCache) + } recordScope.launch { val now = System.currentTimeMillis() - val options = - ImageOptions().apply { - this.setDelay(20, TimeUnit.MILLISECONDS) - } - ByteArrayOutputStream().use { out -> - val encoder = - FastGifEncoder( - out, - gameOptions.width, - gameOptions.height, - 0, - gameOptions.colors(), - ) - - buffer.forEach { img -> - encoder.addImage(img, gameOptions.width, options) + val options = ImageOptions().apply { + this.setDelay(20, TimeUnit.MILLISECONDS) + } + origin.outputStream().buffered().use { out -> + val encoder = FastGifEncoder( + out, + gameOptions.width, + gameOptions.height, + 0, + gameOptions.colors(), + ) + + buffer.forEach { frame -> + encoder.addIndexedImage(frame, gameOptions.width, options) } encoder.finishEncoding() - vfs.save(FileStream(origin), out.toByteArray()) } logger.info("GLFW") { "Screen recorded in '${origin.absolutePath}' in ${System.currentTimeMillis() - now} ms" } } @@ -263,13 +306,63 @@ class GlfwPlatform( } override fun screenshot() { - val buffer = lastDraw ?: return + val buffer = lastDraw.pixels.copyOf() recordScope.launch { writeImage(buffer) } } + fun recordSync(outputFile: File) { + logger.info("GLFW") { "Starting to generate GIF in '${outputFile.absolutePath}' (Wait for it...)" } + val buffer = mutableListOf().apply { + addAll(gifFrameCache) + } + + val now = System.currentTimeMillis() + val options = ImageOptions().apply { + this.setDelay(20, TimeUnit.MILLISECONDS) + } + outputFile.outputStream().buffered().use { out -> + val encoder = FastGifEncoder( + out, + gameOptions.width, + gameOptions.height, + 0, + gameOptions.colors(), + ) + + buffer.forEach { frame -> + encoder.addIndexedImage(frame, gameOptions.width, options) + } + encoder.finishEncoding() + } + logger.info("GLFW") { "Screen recorded in '${outputFile.absolutePath}' in ${System.currentTimeMillis() - now} ms" } + } + + fun screenshotSync(outputFile: File) { + val buffer = lastDraw.pixels.copyOf() + val width = gameOptions.width + val height = gameOptions.height + val image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + + for (y in 0 until height) { + for (x in 0 until width) { + val colorData = gameOptions.colors().getRGBA(buffer[x + y * width].toInt()) + + val r = colorData[0].toInt() and 0xff + val g = colorData[1].toInt() and 0xff + val b = colorData[2].toInt() and 0xff + val a = colorData[3].toInt() and 0xff + val color = (a shl 24) or (r shl 16) or (g shl 8) or b + image.setRGB(x, y, color) + } + } + + ImageIO.write(image, "png", outputFile) + logger.info("GLFW") { "Screenshot saved in '${outputFile.absolutePath}'" } + } + override fun writeImage(buffer: ByteArray) { val origin = newFile("screenshoot", "png") val width = gameOptions.width @@ -331,7 +424,7 @@ class GlfwPlatform( return if (fromJar != null) { InputStreamStream(fromJar) } else { - FileStream(gameDirectory.resolve(name)) + FileStream(safeResolve(gameDirectory, name)) } } @@ -363,7 +456,7 @@ class GlfwPlatform( name: String, data: String, ) { - gameDirectory.resolve(name).outputStream().use { + safeResolve(gameDirectory, name).outputStream().use { it.write(data.toByteArray()) } } @@ -417,12 +510,12 @@ class GlfwPlatform( if (!homeDirectory.exists()) { homeDirectory.mkdirs() } - val file = homeDirectory.resolve(name) + val file = safeResolve(homeDirectory, name) file.writeText(content) } override fun getFromHome(name: String): String? { - val file = homeDirectory.resolve(name) + val file = safeResolve(homeDirectory, name) return if (file.exists()) { file.readText() } else { @@ -430,6 +523,48 @@ class GlfwPlatform( } } + private fun setWindowIcon() { + try { + val iconFileName = gameOptions.icon ?: "icon.png" + val iconFile = safeResolve(gameDirectory, iconFileName) + if (!iconFile.exists()) { + logger.info("GLFW") { "No icon file found at '${iconFile.absolutePath}', using default icon." } + return + } + val image = ImageIO.read(iconFile) ?: return + val isMac = System.getProperty("os.name").lowercase().contains("mac") + if (isMac) { + // AWT Taskbar conflicts with GLFW on macOS when using -XstartOnFirstThread. + // The dock icon can be set via -Xdock:icon JVM argument in the CLI launcher. + logger.info("GLFW") { "Dock icon is set via JVM arguments on macOS." } + } else { + setGlfwWindowIcon(image) + } + } catch (e: Exception) { + logger.info("GLFW") { "Could not set window icon: ${e.message}" } + } + } + + private fun setGlfwWindowIcon(image: BufferedImage) { + val width = image.width + val height = image.height + val rgb = image.getRGB(0, 0, width, height, null, 0, width) + val buffer = MemoryUtil.memAlloc(width * height * 4) + for (pixel in rgb) { + buffer.put(((pixel shr 16) and 0xFF).toByte()) // R + buffer.put(((pixel shr 8) and 0xFF).toByte()) // G + buffer.put((pixel and 0xFF).toByte()) // B + buffer.put(((pixel shr 24) and 0xFF).toByte()) // A + } + buffer.flip() + + val glfwImage = GLFWImage.malloc(1) + glfwImage.position(0).width(width).height(height).pixels(buffer) + GLFW.glfwSetWindowIcon(window, glfwImage) + glfwImage.free() + MemoryUtil.memFree(buffer) + } + companion object { private const val FPS = 60 } diff --git a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/LwjglInput.kt b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/LwjglInput.kt index ee454cdc..62bb2a90 100644 --- a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/LwjglInput.kt +++ b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/platform/glfw/LwjglInput.kt @@ -3,6 +3,7 @@ package com.github.minigdx.tiny.platform.glfw import com.github.minigdx.tiny.input.InputHandler import com.github.minigdx.tiny.input.InputManager import com.github.minigdx.tiny.input.Key +import com.github.minigdx.tiny.input.KeyCode import com.github.minigdx.tiny.input.MouseProject import com.github.minigdx.tiny.input.TouchManager import com.github.minigdx.tiny.input.TouchSignal @@ -21,15 +22,34 @@ import org.lwjgl.glfw.GLFW.glfwSetCursorPosCallback import org.lwjgl.glfw.GLFW.glfwSetInputMode import org.lwjgl.glfw.GLFW.glfwSetKeyCallback import org.lwjgl.glfw.GLFW.glfwSetMouseButtonCallback +import org.lwjgl.glfw.GLFW.glfwSetWindowFocusCallback import org.lwjgl.glfw.GLFWCursorEnterCallback import org.lwjgl.glfw.GLFWCursorPosCallback import org.lwjgl.glfw.GLFWKeyCallback import org.lwjgl.glfw.GLFWMouseButtonCallback +import org.lwjgl.glfw.GLFWWindowFocusCallback import java.nio.DoubleBuffer +import java.util.concurrent.ConcurrentLinkedQueue class LwjglInput(private val projector: MouseProject) : InputHandler, InputManager { private val touchManager = TouchManager(UNKNOWN_KEY) + private sealed class RemoteKeyEvent { + data class Press(val keyCode: KeyCode) : RemoteKeyEvent() + + data class Release(val keyCode: KeyCode) : RemoteKeyEvent() + } + + private val remoteKeyEvents = ConcurrentLinkedQueue() + + fun injectKeyPress(keyCode: KeyCode) { + remoteKeyEvents.add(RemoteKeyEvent.Press(keyCode)) + } + + fun injectKeyRelease(keyCode: KeyCode) { + remoteKeyEvents.add(RemoteKeyEvent.Release(keyCode)) + } + private var window: Long = 0 private val b1: DoubleBuffer = BufferUtils.createDoubleBuffer(1) @@ -128,6 +148,20 @@ class LwjglInput(private val projector: MouseProject) : InputHandler, InputManag } }, ) + glfwSetWindowFocusCallback( + windowAddress, + object : GLFWWindowFocusCallback() { + override fun invoke( + window: Long, + focused: Boolean, + ) { + if (!focused) { + touchManager.resetAllState() + mousePositionDirty = true + } + } + }, + ) } override fun record() { @@ -149,7 +183,18 @@ class LwjglInput(private val projector: MouseProject) : InputHandler, InputManag } } - override fun reset() = touchManager.processReceivedEvent() + override fun reset() { + // Drain remote key events into TouchManager (thread-safe: polled on game loop thread) + var event = remoteKeyEvents.poll() + while (event != null) { + when (event) { + is RemoteKeyEvent.Press -> touchManager.onKeyPressed(event.keyCode) + is RemoteKeyEvent.Release -> touchManager.onKeyReleased(event.keyCode) + } + event = remoteKeyEvents.poll() + } + touchManager.processReceivedEvent() + } override fun isKeyJustPressed(key: Key): Boolean = if (key == Key.ANY_KEY) { diff --git a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundHandler.kt b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundHandler.kt index e3f180bb..7ec75a61 100644 --- a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundHandler.kt +++ b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundHandler.kt @@ -34,6 +34,8 @@ class JavaSoundHandler( soundManager.removeSoundHandler(this) } + override fun isPlaying(): Boolean = !stop + override fun nextChunk(samples: Int): FloatData { val chunk = chunkGenerator.generateChunk(samples) if (chunk.size == 0) { diff --git a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundManager.kt b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundManager.kt index ce6286c5..6b421389 100644 --- a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundManager.kt +++ b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/JavaSoundManager.kt @@ -3,7 +3,8 @@ package com.github.minigdx.tiny.sound import com.github.minigdx.tiny.input.InputHandler import com.github.minigdx.tiny.input.internal.ObjectPool import com.github.minigdx.tiny.lua.Note -import com.github.minigdx.tiny.sound.SoundManager.Companion.MASTER_VOLUME +import com.github.minigdx.tiny.sound.SoundManager.Companion.DEFAULT_MASTER_VOLUME +import com.github.minigdx.tiny.sound.SoundManager.Companion.softClip import com.github.minigdx.tiny.util.MutableFixedSizeList import java.nio.ByteBuffer import java.nio.ByteOrder @@ -47,6 +48,7 @@ class StreamingAudioThread( private val noteEventPool: NoteEventPool, ) : Thread("tiny-streaming-audio-thread") { init { + isDaemon = true priority = MAX_PRIORITY } @@ -55,6 +57,13 @@ class StreamingAudioThread( @Volatile private var running = true + fun shutdown() { + running = false + if (::line.isInitialized) { + line.close() + } + } + private val instrumentPlayers = MutableFixedSizeList(MAX_INSTRUMENTS) private val byteBuffer = ByteArray(BUFFER_SIZE * 2) @@ -94,7 +103,7 @@ class StreamingAudioThread( instrumentPlayers.forEach { instrumentPlayer -> floatData[sample] += instrumentPlayer.generate() } - floatData[sample] = (floatData[sample] * MASTER_VOLUME).coerceIn(-1f, 1f) + floatData[sample] = softClip(floatData[sample] * DEFAULT_MASTER_VOLUME) val sampleValue = (floatData[sample] * 32767f).toInt().coerceIn(-32768, 32767) @@ -190,8 +199,13 @@ class JavaSoundManager : SoundManager() { } override fun destroy() { - soundPort.alive = false - mixer.add(JavaSoundHandler(FloatArray(0), mixer, this)) // unlock the sound port - mixer.alive = false + streamingAudioThread.shutdown() + streamingAudioThread.join(1000) + + mixer.shutdown() + mixer.join(1000) + + soundPort.shutdown() + soundPort.join(1000) } } diff --git a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/MixerGateway.kt b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/MixerGateway.kt index 0739c8cf..acaa13c0 100644 --- a/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/MixerGateway.kt +++ b/tiny-engine/src/jvmMain/kotlin/com/github/minigdx/tiny/sound/MixerGateway.kt @@ -1,13 +1,12 @@ package com.github.minigdx.tiny.sound +import com.github.minigdx.tiny.sound.SoundManager.Companion.softClip import java.util.concurrent.BlockingQueue import java.util.concurrent.ConcurrentLinkedQueue import javax.sound.sampled.AudioFormat import javax.sound.sampled.AudioSystem import javax.sound.sampled.DataLine import javax.sound.sampled.SourceDataLine -import kotlin.math.max -import kotlin.math.min import kotlin.math.roundToInt /** @@ -18,6 +17,10 @@ import kotlin.math.roundToInt * @param queue Output queue for processed audio chunks */ class MixerGateway(var alive: Boolean = true, val queue: BlockingQueue) : Thread("mixer-gateway") { + init { + isDaemon = true + } + /** Buffer for mixing audio samples from all active sounds */ val mixBuffer = FloatArray(CHUNK_SIZE) @@ -29,6 +32,12 @@ class MixerGateway(var alive: Boolean = true, val queue: BlockingQueue - val chunk = sound.nextChunk(CHUNK_SIZE) - var mixIndex = 0 - // Mix current sound chunk into the mix buffer - (0 until chunk.size).forEach { i -> - mixBuffer[mixIndex] = max(-1f, min(1f, (mixBuffer[mixIndex] + chunk[i]))) - mixIndex++ + try { + while (alive) { + // Clear the mix buffer for the next chunk + mixBuffer.fill(0f) + + // Skip processing if no sounds are playing + if (sounds.isEmpty()) continue + + // Process each active sound + sounds + .filter { !it.stop } + .forEach { sound -> + val chunk = sound.nextChunk(CHUNK_SIZE) + var mixIndex = 0 + // Mix current sound chunk into the mix buffer + (0 until chunk.size).forEach { i -> + mixBuffer[mixIndex] = softClip(mixBuffer[mixIndex] + chunk[i]) + mixIndex++ + } } - } - - // Remove finished or stopped sounds - sounds.removeIf { it.stop } - // Convert mixed audio to bytes and queue for output - queue.put(playSoundFromFloats(mixBuffer)) - // Sleep for half the chunk duration to maintain smooth playback - sleep(((CHUNK_DURATION.toFloat() * 0.5f) * 1000f).toLong()) + // Remove finished or stopped sounds + sounds.removeIf { it.stop } + + // Convert mixed audio to bytes and queue for output + queue.put(playSoundFromFloats(mixBuffer)) + // Sleep for half the chunk duration to maintain smooth playback + sleep(((CHUNK_DURATION.toFloat() * 0.5f) * 1000f).toLong()) + } + } catch (_: InterruptedException) { + // Shutdown requested + } finally { + line.close() } } @@ -88,12 +103,10 @@ class MixerGateway(var alive: Boolean = true, val queue: BlockingQueue) : Thread("sound-port") { + init { + isDaemon = true + } + + private lateinit var line: SourceDataLine + + fun shutdown() { + alive = false + interrupt() + if (::line.isInitialized) { + line.close() + } + } + override fun run() { val format = AudioFormat(SAMPLE_RATE.toFloat(), BITS_PER_SAMPLE, CHANNELS, IS_SIGNED, IS_BIG_ENDIAN) val info = DataLine.Info(SourceDataLine::class.java, format) - val line = (AudioSystem.getLine(info) as SourceDataLine).apply { + line = (AudioSystem.getLine(info) as SourceDataLine).apply { open(format) start() } - while (alive) { - val buffer = queue.take() - line.write(buffer, 0, buffer.size) + try { + while (alive) { + val buffer = queue.take() + line.write(buffer, 0, buffer.size) + } + } catch (_: InterruptedException) { + // Shutdown requested + } finally { + if (::line.isInitialized) { + line.close() + } } } } diff --git a/tiny-engine/src/jvmMain/kotlin/com/squareup/gifencoder/FastGifEncoder.kt b/tiny-engine/src/jvmMain/kotlin/com/squareup/gifencoder/FastGifEncoder.kt index dde38580..4e76bfe1 100644 --- a/tiny-engine/src/jvmMain/kotlin/com/squareup/gifencoder/FastGifEncoder.kt +++ b/tiny-engine/src/jvmMain/kotlin/com/squareup/gifencoder/FastGifEncoder.kt @@ -47,6 +47,64 @@ class FastGifEncoder( return this } + /** + * Add a frame from pre-indexed palette data, skipping color quantization. + * + * @param indexedData a ByteArray where each byte is a palette index + * @param width the number of pixels per row + * @param options options to be applied to this image + * @return this instance for chaining + */ + @Synchronized + @Throws(IOException::class) + fun addIndexedImage( + indexedData: ByteArray, + width: Int, + options: ImageOptions, + ): FastGifEncoder { + val height = indexedData.size / width + require( + !(options.left + width > screenWidth || options.top + height > screenHeight), + ) { "Image does not fit in screen." } + + var paddedColorCount = 2 + while (paddedColorCount < rgbPalette.size) { + paddedColorCount *= 2 + } + GraphicsControlExtensionBlock.write( + outputStream, + options.disposalMethod, + false, + false, + options.delayCentiseconds, + 0, + ) + ImageDescriptorBlock.write( + outputStream, options.left, options.top, width, + height, true, false, false, getColorTableSizeField(paddedColorCount), + ) + // Write color table directly from rgbPalette to preserve duplicate RGB entries + for (i in 0 until rgbPalette.size) { + val rgb = rgbPalette.getRGAasInt(i) + outputStream.write(rgb shr 16 and 0xFF) + outputStream.write(rgb shr 8 and 0xFF) + outputStream.write(rgb and 0xFF) + } + for (i in rgbPalette.size until paddedColorCount) { + outputStream.write(0) + outputStream.write(0) + outputStream.write(0) + } + + // Convert ByteArray palette indices to IntArray for LZW encoder + val colorIndices = IntArray(indexedData.size) { indexedData[it].toInt() and 0xFF } + + val lzwEncoder = FastLzwEncoder(paddedColorCount) + val lzwData = lzwEncoder.encode(colorIndices) + ImageDataBlock.write(outputStream, lzwEncoder.minimumCodeSize, lzwData) + return this + } + /** * Writes the trailer. This should be called exactly once per GIF file. * @@ -117,59 +175,85 @@ class FastGifEncoder( } /** + * LZW encoder using an integer-array trie instead of HashMap for zero-allocation encoding. + * * For background, see Appendix F of the * [GIF spec](http://www.w3.org/Graphics/GIF/spec-gif89a.txt). */ -internal class FastLzwEncoder(colorTableSize: Int) { +internal class FastLzwEncoder(private val colorTableSize: Int) { val minimumCodeSize: Int + private val outputBits = BitSet() private var position = 0 - private var codeTable: MutableMap = defaultCodeTable() private var codeSize = 0 - private var indexBuffer: String = "" - /** - * @param colorTableSize Size of the (padded) color table; must be a power of 2 - */ + // Trie: each node has colorTableSize child slots. -1 means no child. + // Node 0..colorTableSize-1 are single-color root nodes. + // clearCode and endOfInfoCode are special codes with no trie node. + private var trieChildren: Array = emptyArray() + private var nodeCodes: IntArray = IntArray(0) + private var nextNodeIndex = 0 + private var nextCode = 0 + + private val clearCode: Int + private val endOfInfoCode: Int + + // Current trie node (-1 means empty buffer) + private var currentNode = -1 + init { require(GifMath.isPowerOfTwo(colorTableSize)) { "Color table size must be a power of 2" } minimumCodeSize = computeMinimumCodeSize(colorTableSize) + clearCode = 1 shl minimumCodeSize + endOfInfoCode = clearCode + 1 resetCodeTableAndCodeSize() } fun encode(indices: IntArray): ByteArray { - writeCode(codeTable[CLEAR_CODE]!!) + writeCode(clearCode) for (index in indices) { processIndex(index) - // writeCode(codeTable[indexBuffer]!!) - // writeCode(codeTable[index.toChar().toString()]!!) } - writeCode(codeTable[indexBuffer]!!) - writeCode(codeTable[END_OF_INFO]!!) + // Flush remaining buffer + if (currentNode >= 0) { + writeCode(nodeCodes[currentNode]) + } + writeCode(endOfInfoCode) return toBytes() } private fun processIndex(index: Int) { - val indexAsStr = index.toChar().toString() - val extendedIndexBuffer = indexBuffer + indexAsStr - indexBuffer = - if (codeTable.containsKey(extendedIndexBuffer)) { - extendedIndexBuffer + if (currentNode < 0) { + // Empty buffer: start with the single-color root node + currentNode = index + return + } + val childNode = trieChildren[currentNode][index] + if (childNode >= 0) { + // Extend: the sequence is already in the trie + currentNode = childNode + } else { + // Output code for current sequence + writeCode(nodeCodes[currentNode]) + if (nextCode == MAX_CODE_TABLE_SIZE) { + // Table full: emit clear code, reset + writeCode(clearCode) + resetCodeTableAndCodeSize() } else { - writeCode(codeTable[indexBuffer]!!) - if (codeTable.size == MAX_CODE_TABLE_SIZE) { - writeCode(codeTable[CLEAR_CODE]!!) - resetCodeTableAndCodeSize() - } else { - addCodeToTable(extendedIndexBuffer) + // Add new trie node for currentNode + index + val newNode = nextNodeIndex++ + trieChildren[currentNode][index] = newNode + nodeCodes[newNode] = nextCode + if (nextCode == 1 shl codeSize) { + ++codeSize } - indexAsStr + nextCode++ } + // Reset buffer to single-color root node + currentNode = index + } } - /** - * Write the given code to the output stream. - */ private fun writeCode(code: Int) { for (shift in 0 until codeSize) { val bit = code ushr shift and 1 != 0 @@ -177,9 +261,6 @@ internal class FastLzwEncoder(colorTableSize: Int) { } } - /** - * Convert our stream of bits into a byte array, as described in the spec. - */ private fun toBytes(): ByteArray { val bitCount = position val result = ByteArray((bitCount + 7) / 8) @@ -191,63 +272,35 @@ internal class FastLzwEncoder(colorTableSize: Int) { return result } - private fun addCodeToTable(indices: String) { - val newCode = codeTable.size - codeTable[indices] = newCode - if (newCode == 1 shl codeSize) { - // The next code won't fit in {@code codeSize} bits, so we need to increment. - ++codeSize - } - } - private fun resetCodeTableAndCodeSize() { - codeTable = defaultCodeTable() - - // We add an extra bit because of the special "clear" and "end of info" codes. - codeSize = minimumCodeSize + 1 - } + // Max 4096 entries per GIF spec + trieChildren = Array(MAX_CODE_TABLE_SIZE) { IntArray(colorTableSize) { -1 } } + nodeCodes = IntArray(MAX_CODE_TABLE_SIZE) - private fun defaultCodeTable(): MutableMap { - val codeTable: MutableMap = HashMap(126 * 4 * 30 * 60) - - // The spec indicates that CLEAR_CODE must have a value of 2**minimumCodeSize. Thus we reserve - // the first 2**minimumCodeSize codes for colors, even if our color table is smaller. val colorsInCodeTable = 1 shl minimumCodeSize + // Initialize root nodes (single-color entries) for (i in 0 until colorsInCodeTable) { - codeTable[i.toChar().toString()] = i + nodeCodes[i] = i } - codeTable[CLEAR_CODE] = codeTable.size - codeTable[(END_OF_INFO)] = codeTable.size - return codeTable + nextNodeIndex = colorsInCodeTable + // clearCode = colorsInCodeTable, endOfInfoCode = colorsInCodeTable + 1 + nextCode = endOfInfoCode + 1 + + // Code size starts one bit larger to accommodate clear and end-of-info codes + codeSize = minimumCodeSize + 1 + + currentNode = -1 } companion object { - // Dummy values to represent special, GIF-specific instructions. - private val CLEAR_CODE = (-1).toChar().toString() - private val END_OF_INFO = (-2).toChar().toString() - - /** - * The specification stipulates that code size may not exceed 12 bits. - */ private const val MAX_CODE_TABLE_SIZE = 1 shl 12 - /** - * This computes what the spec refers to as "code size". The actual starting code size will be one - * bit larger than this, because of the special "clear" and "end of info" codes. - */ private fun computeMinimumCodeSize(colorTableSize: Int): Int { - var size = 2 // LZW has a minimum code size of 2. + var size = 2 while (colorTableSize > 1 shl size) { ++size } return size } - - private fun append( - list: List, - value: T, - ): List { - return list + value - } } } diff --git a/tiny-sample/default-sound.sfx b/tiny-sample/default-sound.sfx deleted file mode 100644 index 48997785..00000000 --- a/tiny-sample/default-sound.sfx +++ /dev/null @@ -1 +0,0 @@ -{"instruments":[{"index":0,"name":"clarinet","wave":"NOISE","attack":0.01,"decay":0.0375,"sustain":0.76875,"release":0.03125,"harmonics":[1.1,0.3,0.031,0.039,0.345,0.29,0.0119],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":1,"name":"violon","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.65,0.7,0.55,0.45,0.35,0.3],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":2,"name":"obos","wave":"SAW_TOOTH","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":3,"name":"drum","wave":"NOISE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":4,"name":"custom1","wave":"TRIANGLE","attack":0.0062500015,"decay":0.1,"sustain":0.9,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":5,"name":"custom2","wave":"SAW_TOOTH","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":6,"name":"custom3","wave":"TRIANGLE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},{"index":7,"name":"custom4","wave":"SQUARE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}]},null,null,null,null,null,null,null,null],"musicalBars":[{"index":0,"instrumentIndex":4,"tempo":420,"beats":[{"note":"E4","beat":0.0,"duration":0.5,"volume":1.0},{"note":"G4","beat":1.0,"duration":0.5,"volume":1.0}]},{"instrumentIndex":0,"beats":[{"note":"Cs3","beat":0.0,"duration":0.5,"volume":1.0}]},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0},{"index":4,"instrumentIndex":0},{"index":5,"instrumentIndex":0},{"index":6,"instrumentIndex":0},{"index":7,"instrumentIndex":0},{"index":8,"instrumentIndex":0},{"index":9,"instrumentIndex":0},{"index":10,"instrumentIndex":0},{"index":11,"instrumentIndex":0},{"index":12,"instrumentIndex":0},{"index":13,"instrumentIndex":0},{"index":14,"instrumentIndex":0},{"index":15,"instrumentIndex":0},{"index":16,"instrumentIndex":0},{"index":17,"instrumentIndex":0},{"index":18,"instrumentIndex":0},{"index":19,"instrumentIndex":0},{"index":20,"instrumentIndex":0},{"index":21,"instrumentIndex":0},{"index":22,"instrumentIndex":0},{"index":23,"instrumentIndex":0},{"index":24,"instrumentIndex":0},{"index":25,"instrumentIndex":0},{"index":26,"instrumentIndex":0},{"index":27,"instrumentIndex":0},{"index":28,"instrumentIndex":0},{"index":29,"instrumentIndex":0},{"index":30,"instrumentIndex":0},{"index":31,"instrumentIndex":0}],"sequences":[{"index":0,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":1,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":2,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":3,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":4,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":5,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":6,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]},{"index":7,"tracks":[{"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":1,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":2,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]},{"index":3,"instrumentIndex":0,"beats":[{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0},{"note":null,"beat":0.0,"duration":1.0,"volume":1.0},{"note":null,"beat":1.0,"duration":1.0,"volume":1.0},{"note":null,"beat":2.0,"duration":1.0,"volume":1.0},{"note":null,"beat":3.0,"duration":1.0,"volume":1.0},{"note":null,"beat":4.0,"duration":1.0,"volume":1.0},{"note":null,"beat":5.0,"duration":1.0,"volume":1.0},{"note":null,"beat":6.0,"duration":1.0,"volume":1.0},{"note":null,"beat":7.0,"duration":1.0,"volume":1.0},{"note":null,"beat":8.0,"duration":1.0,"volume":1.0},{"note":null,"beat":9.0,"duration":1.0,"volume":1.0},{"note":null,"beat":10.0,"duration":1.0,"volume":1.0},{"note":null,"beat":11.0,"duration":1.0,"volume":1.0},{"note":null,"beat":12.0,"duration":1.0,"volume":1.0},{"note":null,"beat":13.0,"duration":1.0,"volume":1.0},{"note":null,"beat":14.0,"duration":1.0,"volume":1.0},{"note":null,"beat":15.0,"duration":1.0,"volume":1.0},{"note":null,"beat":16.0,"duration":1.0,"volume":1.0},{"note":null,"beat":17.0,"duration":1.0,"volume":1.0},{"note":null,"beat":18.0,"duration":1.0,"volume":1.0},{"note":null,"beat":19.0,"duration":1.0,"volume":1.0},{"note":null,"beat":20.0,"duration":1.0,"volume":1.0},{"note":null,"beat":21.0,"duration":1.0,"volume":1.0},{"note":null,"beat":22.0,"duration":1.0,"volume":1.0},{"note":null,"beat":23.0,"duration":1.0,"volume":1.0},{"note":null,"beat":24.0,"duration":1.0,"volume":1.0},{"note":null,"beat":25.0,"duration":1.0,"volume":1.0},{"note":null,"beat":26.0,"duration":1.0,"volume":1.0},{"note":null,"beat":27.0,"duration":1.0,"volume":1.0},{"note":null,"beat":28.0,"duration":1.0,"volume":1.0},{"note":null,"beat":29.0,"duration":1.0,"volume":1.0},{"note":null,"beat":30.0,"duration":1.0,"volume":1.0},{"note":null,"beat":31.0,"duration":1.0,"volume":1.0},{"note":null,"beat":32.0,"duration":1.0,"volume":1.0}]}]}]} \ No newline at end of file diff --git a/tiny-sample/_tiny.json b/tiny-samples/breakout/_tiny.json similarity index 100% rename from tiny-sample/_tiny.json rename to tiny-samples/breakout/_tiny.json diff --git a/tiny-sample/breakout.aseprite b/tiny-samples/breakout/breakout.aseprite similarity index 100% rename from tiny-sample/breakout.aseprite rename to tiny-samples/breakout/breakout.aseprite diff --git a/tiny-samples/breakout/default-sound.sfx b/tiny-samples/breakout/default-sound.sfx new file mode 100644 index 00000000..cf5cb688 --- /dev/null +++ b/tiny-samples/breakout/default-sound.sfx @@ -0,0 +1 @@ +{"instruments":[{"index":0,"name":"clarinet","wave":"NOISE","attack":0.01,"decay":0.0375,"sustain":0.76875,"release":0.03125,"harmonics":[1.1,0.3,0.031,0.039,0.345,0.29,0.0119],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":1,"name":"violon","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.65,0.7,0.55,0.45,0.35,0.3],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":2,"name":"obos","wave":"SAW_TOOTH","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":3,"name":"drum","wave":"NOISE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":4,"name":"custom1","wave":"TRIANGLE","attack":0.0062500015,"decay":0.1,"sustain":0.9,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":5,"name":"custom2","wave":"SAW_TOOTH","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":6,"name":"custom3","wave":"TRIANGLE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":7,"name":"custom4","wave":"SQUARE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},null,null,null,null,null,null,null,null],"musicalBars":[{"index":0,"instrumentIndex":4,"tempo":420,"beats":[{"note":"E4","beat":0.0,"duration":0.5,"volume":1.0},{"note":"G4","beat":1.0,"duration":0.5,"volume":1.0}]},{"instrumentIndex":0,"beats":[{"note":"Cs3","beat":0.0,"duration":0.5,"volume":1.0}]},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0},{"index":4,"instrumentIndex":0},{"index":5,"instrumentIndex":0},{"index":6,"instrumentIndex":0},{"index":7,"instrumentIndex":0},{"index":8,"instrumentIndex":0},{"index":9,"instrumentIndex":0},{"index":10,"instrumentIndex":0},{"index":11,"instrumentIndex":0},{"index":12,"instrumentIndex":0},{"index":13,"instrumentIndex":0},{"index":14,"instrumentIndex":0},{"index":15,"instrumentIndex":0},{"index":16,"instrumentIndex":0},{"index":17,"instrumentIndex":0},{"index":18,"instrumentIndex":0},{"index":19,"instrumentIndex":0},{"index":20,"instrumentIndex":0},{"index":21,"instrumentIndex":0},{"index":22,"instrumentIndex":0},{"index":23,"instrumentIndex":0},{"index":24,"instrumentIndex":0},{"index":25,"instrumentIndex":0},{"index":26,"instrumentIndex":0},{"index":27,"instrumentIndex":0},{"index":28,"instrumentIndex":0},{"index":29,"instrumentIndex":0},{"index":30,"instrumentIndex":0},{"index":31,"instrumentIndex":0}],"sequences":[{"index":0,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":1,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":2,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":3,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":4,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":5,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":6,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":7,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]}]} \ No newline at end of file diff --git a/tiny-sample/game.aseprite b/tiny-samples/breakout/game.aseprite similarity index 100% rename from tiny-sample/game.aseprite rename to tiny-samples/breakout/game.aseprite diff --git a/tiny-sample/game.png b/tiny-samples/breakout/game.png similarity index 100% rename from tiny-sample/game.png rename to tiny-samples/breakout/game.png diff --git a/tiny-sample/pong.lua b/tiny-samples/breakout/pong.lua similarity index 100% rename from tiny-sample/pong.lua rename to tiny-samples/breakout/pong.lua diff --git a/tiny-samples/home/_tiny.json b/tiny-samples/home/_tiny.json new file mode 100644 index 00000000..ee048703 --- /dev/null +++ b/tiny-samples/home/_tiny.json @@ -0,0 +1,30 @@ +{ + "version": "V1", + "name": "Home Waves", + "id": "home-waves", + "resolution": { + "width": 523, + "height": 247 + }, + "sprites": { + "width": 16, + "height": 16 + }, + "zoom": 2, + "colors": [ + "#87CEEB", + "#9DD5EF", + "#B0D4E8", + "#C8E3F2", + "#D4E9F7", + "#E6F2FB", + "#F0F7FC", + "#FFFFFF" + ], + "scripts": [ + "clouds.lua" + ], + "sound": "default-sound.sfx", + "hideMouseCursor": false, + "bootScript": "boot.lua" +} \ No newline at end of file diff --git a/tiny-samples/home/boot.lua b/tiny-samples/home/boot.lua new file mode 100644 index 00000000..fcc2bd1a --- /dev/null +++ b/tiny-samples/home/boot.lua @@ -0,0 +1,19 @@ +local ready = false + +function _init() +end + +function _update() + if ready then + gfx.cls("#FFFFFF") + tiny.exit(0) + end +end + +function _draw() + gfx.cls("#FFFFFF") +end + +function _resources() + ready = true +end diff --git a/tiny-samples/home/clouds.lua b/tiny-samples/home/clouds.lua new file mode 100644 index 00000000..e4a462df --- /dev/null +++ b/tiny-samples/home/clouds.lua @@ -0,0 +1,116 @@ +-- 3 layered waves with bottom-to-top transition and mouse interaction + +local waves = {} +local width, height +local transition_start = 0 +local transition_duration = 1.5 -- seconds for all waves to fully appear + +function _init(w, h) + width = w + height = h + transition_start = tiny.t + + -- Define 3 waves (back to front) + -- Colors: 0=darkest blue ... 7=white + waves = { + { + base_y = h * 0.45, + amplitude = 40, + speed = 0.05, + color = 5, + layer = 1, + delay = 0.0, + }, + { + base_y = h * 0.58, + amplitude = 50, + speed = 0.07, + color = 3, + layer = 2, + delay = 0.3, + }, + { + base_y = h * 0.72, + amplitude = 30, + speed = 0.1, + color = 1, + layer = 3, + delay = 0.6, + }, + } +end + +-- Ease out cubic +local function ease_out(t) + local inv = 1 - t + return 1 - inv * inv * inv +end + +local mouse_radius = 120 +local mouse_strength = 20 +local step = 8 * 4 +local circle_r = 48 + +function _update() +end + +-- Compute wave Y at a given x position +local function wave_y_at(w, x, mx, my, offset_y, now) + local wy = w.base_y + + math.perlin(x / width, 0.5 * w.layer / 3, now * w.speed) * w.amplitude + + -- Mouse interaction: push wave up near cursor + local dx = x - mx + local dy = (wy + offset_y) - my + local dist = math.sqrt(dx * dx + dy * dy) + if dist < mouse_radius and dist > 0.1 then + local factor = (mouse_radius - dist) / mouse_radius + wy = wy - factor * factor * mouse_strength + end + + return wy + offset_y +end + +function _draw() + gfx.cls(7) + + local touch = ctrl.touch() + local mx = touch.x + local my = touch.y + local now = tiny.t + + for i = 1, #waves do + local w = waves[i] + + -- Per-wave transition progress (staggered) + local elapsed = now - transition_start - w.delay + local progress = math.max(0, math.min(elapsed / transition_duration, 1)) + local ease = ease_out(progress) + + -- Offset: wave starts below screen, slides up + local offset_y = (1 - ease) * height + + -- First pass: sample wave crest points, find peak (min y on screen) + local points = {} + local min_y = height + local x = -circle_r + while x <= width + circle_r do + local fy = wave_y_at(w, x, mx, my, offset_y, now) + points[#points + 1] = { x = x, y = fy } + if fy < min_y then min_y = fy end + x = x + step + end + + -- Body: one big rectangle from just below the peak circles to bottom + local body_top = min_y + circle_r + if body_top < height then + shape.rectf(0, body_top, width + 1, height - body_top + 1, w.color) + end + + -- Tip: consecutive circles along the wave crest + for j = 1, #points do + local p = points[j] + shape.circlef(p.x, p.y, circle_r, w.color) + end + end +end diff --git a/tiny-samples/home/default-sound.sfx b/tiny-samples/home/default-sound.sfx new file mode 100644 index 00000000..cf5cb688 --- /dev/null +++ b/tiny-samples/home/default-sound.sfx @@ -0,0 +1 @@ +{"instruments":[{"index":0,"name":"clarinet","wave":"NOISE","attack":0.01,"decay":0.0375,"sustain":0.76875,"release":0.03125,"harmonics":[1.1,0.3,0.031,0.039,0.345,0.29,0.0119],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":1,"name":"violon","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.65,0.7,0.55,0.45,0.35,0.3],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":2,"name":"obos","wave":"SAW_TOOTH","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":3,"name":"drum","wave":"NOISE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":4,"name":"custom1","wave":"TRIANGLE","attack":0.0062500015,"decay":0.1,"sustain":0.9,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":5,"name":"custom2","wave":"SAW_TOOTH","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":6,"name":"custom3","wave":"TRIANGLE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},{"index":7,"name":"custom4","wave":"SQUARE","attack":0.1,"decay":0.1,"sustain":0.9,"release":0.05,"harmonics":[1.0,0.05,0.01],"modulations":[{"type":"com.github.minigdx.tiny.sound.Sweep","sweep":880.0,"acceleration":1.0},{"type":"com.github.minigdx.tiny.sound.Vibrato","vibratoFrequency":0.0,"depth":0.0}],"tremolo":{}},null,null,null,null,null,null,null,null],"musicalBars":[{"index":0,"instrumentIndex":4,"tempo":420,"beats":[{"note":"E4","beat":0.0,"duration":0.5,"volume":1.0},{"note":"G4","beat":1.0,"duration":0.5,"volume":1.0}]},{"instrumentIndex":0,"beats":[{"note":"Cs3","beat":0.0,"duration":0.5,"volume":1.0}]},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0},{"index":4,"instrumentIndex":0},{"index":5,"instrumentIndex":0},{"index":6,"instrumentIndex":0},{"index":7,"instrumentIndex":0},{"index":8,"instrumentIndex":0},{"index":9,"instrumentIndex":0},{"index":10,"instrumentIndex":0},{"index":11,"instrumentIndex":0},{"index":12,"instrumentIndex":0},{"index":13,"instrumentIndex":0},{"index":14,"instrumentIndex":0},{"index":15,"instrumentIndex":0},{"index":16,"instrumentIndex":0},{"index":17,"instrumentIndex":0},{"index":18,"instrumentIndex":0},{"index":19,"instrumentIndex":0},{"index":20,"instrumentIndex":0},{"index":21,"instrumentIndex":0},{"index":22,"instrumentIndex":0},{"index":23,"instrumentIndex":0},{"index":24,"instrumentIndex":0},{"index":25,"instrumentIndex":0},{"index":26,"instrumentIndex":0},{"index":27,"instrumentIndex":0},{"index":28,"instrumentIndex":0},{"index":29,"instrumentIndex":0},{"index":30,"instrumentIndex":0},{"index":31,"instrumentIndex":0}],"sequences":[{"index":0,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":1,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":2,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":3,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":4,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":5,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":6,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]},{"index":7,"tracks":[{"instrumentIndex":0},{"index":1,"instrumentIndex":0},{"index":2,"instrumentIndex":0},{"index":3,"instrumentIndex":0}]}]} \ No newline at end of file diff --git a/tiny-samples/home/palette.png b/tiny-samples/home/palette.png new file mode 100644 index 00000000..707dfcb8 Binary files /dev/null and b/tiny-samples/home/palette.png differ diff --git a/tiny-web-editor/src/jsMain/kotlin/EditorStream.kt b/tiny-web-editor/src/jsMain/kotlin/EditorStream.kt index ac0157f6..033c3deb 100644 --- a/tiny-web-editor/src/jsMain/kotlin/EditorStream.kt +++ b/tiny-web-editor/src/jsMain/kotlin/EditorStream.kt @@ -1,11 +1,13 @@ import com.github.minigdx.tiny.file.SourceStream import kotlinx.browser.document -import kotlinx.browser.window import org.w3c.dom.HTMLAnchorElement import org.w3c.dom.HTMLDivElement import org.w3c.dom.get +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.js.Date +@OptIn(ExperimentalEncodingApi::class) class EditorStream(field: String) : SourceStream { private val exist: Boolean private var updated: Boolean = false @@ -23,7 +25,7 @@ class EditorStream(field: String) : SourceStream { this.textarea = textarea textarea.addEventListener("input", { updated = true - timeout = Date.now() + 1500 // add 1.5 second + timeout = Date.now() + 1000 // add 1 second }, null) } share = document.getElementById(field.replace("#editor", "share")) as? HTMLAnchorElement @@ -40,9 +42,9 @@ class EditorStream(field: String) : SourceStream { } override suspend fun read(): ByteArray { - val value = textarea?.innerText ?: "" + val value = textarea?.textContent ?: "" - share?.href = "playground.html?game=" + window.btoa(value) + share?.href = "editor.html?game=" + Base64.encode(value.encodeToByteArray()) return value.encodeToByteArray() } diff --git a/tiny-web-editor/src/jsMain/kotlin/Main.kt b/tiny-web-editor/src/jsMain/kotlin/Main.kt index c0736df4..86976d2e 100644 --- a/tiny-web-editor/src/jsMain/kotlin/Main.kt +++ b/tiny-web-editor/src/jsMain/kotlin/Main.kt @@ -23,11 +23,26 @@ import org.w3c.dom.Element import org.w3c.dom.HTMLAnchorElement import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLDivElement +import org.w3c.dom.HTMLSelectElement import org.w3c.dom.Node +import org.w3c.dom.events.Event import org.w3c.dom.url.URLSearchParams import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi +data class ExampleEntry(val name: String, val code: String) + +fun parseExamples(json: String): List { + val parsed: dynamic = kotlin.js.JSON.parse(json) + val length = parsed.length as Int + val result = mutableListOf() + for (i in 0 until length) { + val item = parsed[i] + result.add(ExampleEntry(item.name as String, item.code as String)) + } + return result +} + @OptIn(ExperimentalEncodingApi::class) fun main() { val rootPath = getRootPath() @@ -37,64 +52,186 @@ fun main() { val url = URLSearchParams(window.location.search) val savedCode = url.get("game") val decodedCode = if (savedCode?.isNotBlank() == true) { - Base64.decode(savedCode.encodeToByteArray()).decodeToString() + // URLSearchParams decodes '+' as space; restore it for valid base64 + Base64.decode(savedCode.replace(' ', '+').encodeToByteArray()).decodeToString() } else { null } elts.forEachIndexed { index, game -> - val code = game.textContent ?: "" - val spritePath = game.getAttribute("sprite") - val levelPath = game.getAttribute("level") + val mode = game.getAttribute("mode") + if (mode == "editor") { + setupEditorMode(index, game, decodedCode, rootPath) + } else { + setupDocMode(index, game, decodedCode, savedCode, rootPath) + } + } + + // Render Lucide icons in dynamically created elements + js("if (typeof lucide !== 'undefined') { lucide.createIcons(); }") +} +@OptIn(ExperimentalEncodingApi::class) +private fun setupDocMode( + index: Int, + game: Element, + decodedCode: String?, + savedCode: String?, + rootPath: String, +) { + val code = game.textContent ?: "" + val spritePath = game.getAttribute("sprite") + val levelPath = game.getAttribute("level") + + // Check if the fn-card structure already exists (e.g. API page from fn-card.peb). + // If not, create the toolbar + static code block + "Try it" button dynamically. + val hasFnCard = game.parentElement?.classList?.contains("fn-card__example") == true + var codeBlock: Element? = null + + if (!hasFnCard) { val toolbar = document.createElement("div") { setAttribute("class", "tiny-toolbar") } - game.before(toolbar) + game.after(toolbar) - val link = document.createElement("a") { - setAttribute("id", "link-editor-$index") - setAttribute("class", "tiny-play tiny-button") - setAttribute("href", "#link-editor-$index") - } as HTMLAnchorElement - toolbar.appendChild(link) + toolbar.appendChild( + document.createElement("span") { + setAttribute("class", "fn-card__filename") + textContent = "example.lua" + }, + ) - val playLink = document.createElement("div").apply { - setAttribute("class", "tiny-container") + val tryButton = document.createElement("a") { + setAttribute("class", "fn-card__try-btn tiny-button") + innerHTML = """ Try it""" } - toolbar.after(playLink) + toolbar.appendChild(tryButton) - val codeToUse = decodedCode ?: "-- Update the code to update the game!\n$code" - - var clicked = false - link.textContent = "\uD83D\uDC7E ▶ Run and tweak an example" - link.onclick = { _ -> - if (!clicked) { - createGame(playLink, index, codeToUse, spritePath, levelPath, rootPath) - clicked = true - } - true + codeBlock = document.createElement("div") { + setAttribute("class", "fn-card__code") + val pre = document.createElement("pre") + pre.innerHTML = highlightStatic(code) + appendChild(pre) } + toolbar.after(codeBlock) - val playground = ( - document.createElement("a") { - setAttribute("class", "tiny-button tiny-button-right") - id = "share-$index" - textContent = "↗\uFE0F Playground" - } as HTMLAnchorElement - ).apply { - val b64 = Base64.encode(code.encodeToByteArray()) - href = "playground.html?game=$b64" - target = "_blank" - } + tryButton.addEventListener("click", { + game.dispatchEvent(Event("play")) + }) + + js("if (typeof lucide !== 'undefined') { lucide.createIcons(); }") + } + + val container = document.createElement("div").apply { + setAttribute("class", "tiny-container") + asDynamic().style.display = "none" + } + (codeBlock ?: game).after(container) + + val codeToUse = decodedCode ?: "-- Update the code to update the game!\n$code" - toolbar.appendChild(playground) + var started = false - // There is a user code. Let's unfold the game. - if (savedCode != null) { - createGame(playLink, index, codeToUse, spritePath, levelPath, rootPath) + fun startGame() { + if (!started) { + codeBlock?.asDynamic()?.style?.display = "none" + container.asDynamic().style.display = "" + createGame(container, index, codeToUse, spritePath, levelPath, rootPath) + started = true } } + + game.addEventListener("play", { startGame() }) + + // There is a user code. Let's unfold the game. + if (savedCode != null) { + startGame() + } +} + +@OptIn(ExperimentalEncodingApi::class) +private fun setupEditorMode( + index: Int, + game: Element, + decodedCode: String?, + rootPath: String, +) { + val code = game.textContent ?: "" + val spritePath = game.getAttribute("sprite") + val levelPath = game.getAttribute("level") + val examplesJson = game.getAttribute("examples") ?: "[]" + val examples = parseExamples(examplesJson) + + val codeToUse = decodedCode ?: "-- Update the code to update the game!\n$code" + + // Widen the editor page layout + document.body?.classList?.add("tiny-editor-page") + + // Create editor toolbar (example name + dropdown only) + val toolbar = document.createElement("div") { + setAttribute("class", "tiny-editor-toolbar") + } + game.before(toolbar) + + // Example name label + val exampleName = document.createElement("span") { + setAttribute("class", "tiny-example-name") + textContent = if (decodedCode != null) "Shared Code" else examples.firstOrNull()?.name ?: "Empty Project" + } + toolbar.appendChild(exampleName) + + // Examples dropdown + val select = document.createElement("select") { + setAttribute("class", "tiny-examples-select") + } as HTMLSelectElement + examples.forEachIndexed { i, example -> + select.appendChild( + document.createElement("option") { + setAttribute("value", i.toString()) + textContent = example.name + }, + ) + } + toolbar.appendChild(select) + + // Container for game + editor + val playLink = document.createElement("div").apply { + setAttribute("class", "tiny-container") + } + toolbar.after(playLink) + + // Share link below the editor + val share = ( + document.createElement("a") { + setAttribute("class", "tiny-share-link") + id = "share-$index" + innerHTML = """ Share this code""" + } as HTMLAnchorElement + ).apply { + val b64 = Base64.encode(codeToUse.encodeToByteArray()) + href = "editor.html?game=$b64" + target = "_blank" + } + playLink.after(share) + + // Auto-start the game + createGame(playLink, index, codeToUse, spritePath, levelPath, rootPath) + + // Examples dropdown change handler + select.addEventListener("change", { + val selectedIndex = select.selectedIndex + if (selectedIndex >= 0 && selectedIndex < examples.size) { + val example = examples[selectedIndex] + val decoded = Base64.decode(example.code.encodeToByteArray()).decodeToString() + val editor = document.getElementById("editor-$index") as? HTMLDivElement + if (editor != null) { + editor.innerHTML = highlight(decoded) + editor.dispatchEvent(Event("input")) + } + exampleName.textContent = example.name + share.href = "editor.html?game=${example.code}" + } + }, null) } /** @@ -107,6 +244,7 @@ fun main() { */ fun getCaretPosition(el: Element): Int { val selection = window.asDynamic().getSelection() ?: return -1 + if (selection.rangeCount == 0) return -1 val range = selection.getRangeAt(0) val prefix = range.cloneRange() prefix.selectNodeContents(el) @@ -171,6 +309,21 @@ fun setCaret( return position } +/** + * Highlight Lua code for static display (no line wrapping in divs). + * Same highlighting rules as [highlight] but suited for a
 block.
+ */
+fun highlightStatic(content: String): String {
+    return content
+        .replace(Regex("(\".*?\")"), "$1")
+        .replace(Regex("--(.*)"), """--$1""")
+        .replace(
+            Regex("\\b(if|else|elif|end|while|for|in|of|continue|break|return|function|local|do)\\b"),
+            """$1""",
+        )
+        .replace(Regex("\\b(\\d+)"), "$1")
+}
+
 /**
  * Highlight the code with HTML tags.
  *
@@ -216,6 +369,20 @@ private fun createGame(
     levelPath: String?,
     rootPath: String,
 ) {
+    val statusBar = (document.createElement("div") as HTMLDivElement).apply {
+        setAttribute("class", "tiny-status-bar")
+    }
+    val statusBarProgress = (document.createElement("div") as HTMLDivElement).apply {
+        setAttribute("class", "tiny-status-bar-progress tiny-status-loaded")
+    }
+    statusBar.appendChild(statusBarProgress)
+    container.before(statusBar)
+
+    statusBarProgress.addEventListener("transitionend", {
+        statusBarProgress.classList.remove("tiny-status-editing")
+        statusBarProgress.classList.add("tiny-status-loaded")
+    }, null)
+
     val textarea = (document.createElement("div") as HTMLDivElement).apply {
         setAttribute("id", "editor-$index")
         setAttribute("spellcheck", "false")
@@ -228,6 +395,15 @@ private fun createGame(
             val pos = getCaretPosition(this)
             this.innerHTML = highlight(extractText(this))
             setCaret(pos, this)
+
+            // Reset status bar: orange, animate from 0% to 100% over 1.5s
+            statusBarProgress.classList.remove("tiny-status-loaded")
+            statusBarProgress.classList.add("tiny-status-editing")
+            statusBarProgress.style.transition = "none"
+            statusBarProgress.style.width = "0%"
+            statusBarProgress.getBoundingClientRect() // Force reflow
+            statusBarProgress.style.transition = "width 1s linear"
+            statusBarProgress.style.width = "100%"
         }
     }
     textarea.innerHTML = highlight(textarea.innerText)
diff --git a/tiny-web-editor/src/jsMain/resources/index.html b/tiny-web-editor/src/jsMain/resources/index.html
index 6e107a0c..17e0ba73 100644
--- a/tiny-web-editor/src/jsMain/resources/index.html
+++ b/tiny-web-editor/src/jsMain/resources/index.html
@@ -32,6 +32,10 @@
             right: calc(100% + 16px); /* add some space between this and the code line next to it */
         }
 
+        .tiny-textarea:focus {
+            outline: none;
+        }
+
         .tiny-textarea {
             padding-left: 48px;
             counter-reset: line; /* reset the "line" counter */
@@ -52,6 +56,24 @@
         .code_string {
             color: #128427
         }
+
+        .tiny-status-bar {
+            width: 100%;
+            height: 1px;
+        }
+
+        .tiny-status-bar-progress {
+            height: 100%;
+            width: 100%;
+        }
+
+        .tiny-status-editing {
+            background-color: #e89830;
+        }
+
+        .tiny-status-loaded {
+            background-color: #4caf50;
+        }