diff --git a/.github/workflows/jamscribe_release.yml b/.github/workflows/jamscribe_release.yml new file mode 100644 index 0000000..333e335 --- /dev/null +++ b/.github/workflows/jamscribe_release.yml @@ -0,0 +1,112 @@ +name: JamScribe release + +on: + push: + branches: + - "*" + workflow_dispatch: + inputs: + ref: + description: 'Ref to use in application repo' + required: false + default: 'main' + # repository_dispatch: + # types: [ build-remote ] + +env: + TARGET_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.ref != '' && github.event.inputs.ref || github.event_name == 'repository_dispatch' && github.event.client_payload.sha != '' && github.event.client_payload.sha || 'main' }} + +jobs: + release-jamscribe: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install zipcmp + run: sudo apt-get update && sudo apt-get install -y zipcmp + + - name: Checkout songdrive-releases repo into `.github/composite-actions` + uses: actions/checkout@v3 + with: + repository: jamtools/songdrive-releases + path: .github/composite-actions + ref: dev + + - name: Run build composite action + uses: ./.github/composite-actions/.github/actions/build_app + with: + entrypoint: src/index.tsx + + - name: Create dist.zip + run: | + cd dist + zip -r ../dist.zip . + + - name: Find latest matching release by tag prefix + id: find_release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PREFIX="vjamscribe-" + latest=$(gh release list --repo "${{ github.repository }}" --limit 100 --json tagName,publishedAt \ + | jq -r --arg prefix "$PREFIX" ' + map(select(.tagName | startswith($prefix))) + | sort_by(.publishedAt) + | last + | if . == null then "none" else .tagName[1:] end + ') + echo "latest_tag=$latest" >> $GITHUB_OUTPUT + + - name: Download artifact from latest release + if: steps.find_release.outputs.latest_tag != 'none' + uses: blauqs/actions-download-asset@master + with: + repo: ${{ github.repository }} + version: ${{ steps.find_release.outputs.latest_tag }} + file: dist.zip + out: previous_release.zip + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compare ZIP files using zipcmp + id: compare_zips + run: | + if [[ "${{ steps.find_release.outputs.latest_tag }}" == "none" ]]; then + echo "match=false" >> $GITHUB_OUTPUT + else + if zipcmp dist.zip previous_release.zip; then + echo "match=true" >> $GITHUB_OUTPUT + else + echo "match=false" >> $GITHUB_OUTPUT + fi + fi + + - name: Set version number + id: version + run: | + if [[ "${{ steps.find_release.outputs.latest_tag }}" != "none" ]]; then + # Extract number from the tag and increment + current=$(echo "${{ steps.find_release.outputs.latest_tag }}" | grep -o '[0-9]\+') + echo "new_release_number=$((current + 1))" >> $GITHUB_OUTPUT + else + echo "new_release_number=1" >> $GITHUB_OUTPUT + fi + + - name: Create new release + if: steps.compare_zips.outputs.match == 'false' + uses: ncipollo/release-action@v1 + with: + tag: vjamscribe-${{ steps.version.outputs.new_release_number }} + name: JamScribe v${{ steps.version.outputs.new_release_number }} + body: Automated release + artifacts: dist.zip + token: ${{ secrets.GITHUB_TOKEN }} + makeLatest: true + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: jamscribe-dist + path: dist.zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..21c6c7a --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +node-linker=hoisted +strict-peer-dependencies=false diff --git a/package.json b/package.json new file mode 100644 index 0000000..c290144 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "jamscribe", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "dev": "sb dev src/index.tsx", + "build": "sb build src/index.tsx" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@jamtools/core": "0.15.0-rc20", + "@springboardjs/data-storage": "0.15.0-rc20", + "@springboardjs/platforms-browser": "0.15.0-rc20", + "@springboardjs/platforms-node": "0.15.0-rc20", + "hono": "4.7.6", + "react": "^19.1.0", + "springboard": "0.15.0-rc20", + "springboard-cli": "0.15.0-rc20", + "springboard-server": "0.15.0-rc20" + }, + "devDependencies": { + "@types/react": "^19.1.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..e4f4b53 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1315 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@jamtools/core': + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(@tonejs/midi@2.0.28)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)) + '@springboardjs/data-storage': + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(kysely@0.28.1) + '@springboardjs/platforms-browser': + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)) + '@springboardjs/platforms-node': + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(isomorphic-ws@4.0.1(ws@8.18.1))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))(ws@8.18.1) + hono: + specifier: 4.7.6 + version: 4.7.6 + react: + specifier: ^19.1.0 + version: 19.1.0 + springboard: + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2) + springboard-cli: + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(@springboardjs/platforms-browser@0.15.0-rc20(react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)))(@springboardjs/platforms-node@0.15.0-rc20(isomorphic-ws@4.0.1(ws@8.18.1))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))(ws@8.18.1))(springboard-server@0.15.0-rc20(@springboardjs/data-storage@0.15.0-rc20(kysely@0.28.1))(@trpc/server@10.45.2)(hono@4.7.6)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)) + springboard-server: + specifier: 0.15.0-rc20 + version: 0.15.0-rc20(@springboardjs/data-storage@0.15.0-rc20(kysely@0.28.1))(@trpc/server@10.45.2)(hono@4.7.6)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)) + devDependencies: + '@types/react': + specifier: ^19.1.2 + version: 19.1.2 + +packages: + + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/node-server@1.14.1': + resolution: {integrity: sha512-vmbuM+HPinjWzPe7FFPWMMQMsbKE9gDPhaH0FFdqbGpkT5lp++tcWDTxwBl5EgS5y6JVgIaCdjeHRfQ4XRBRjQ==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@hono/node-ws@1.1.1': + resolution: {integrity: sha512-iFJrAw5GuBTstehBzLY2FyW5rRlXmO3Uwpijpm4Liv75owNP/UjZe3KExsLuEK4w+u+xhvHqOoQUyEKWUvyghw==} + engines: {node: '>=18.14.1'} + peerDependencies: + '@hono/node-server': ^1.11.1 + hono: ^4.6.0 + + '@hono/trpc-server@0.3.4': + resolution: {integrity: sha512-xFOPjUPnII70FgicDzOJy1ufIoBTu8eF578zGiDOrYOrYN8CJe140s9buzuPkX+SwJRYK8LjEBHywqZtxdm8aA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@trpc/server': ^10.10.0 || >11.0.0-rc + hono: '>=4.*' + + '@jamtools/core@0.15.0-rc20': + resolution: {integrity: sha512-ZfpSYOnCQnwS4AUUljF9dMFmcaKArGwNbpGYitkfZ5tUIJdMMNhVxC47YSpA0MFftl/Kk2cZGOabFHPYw/GKIQ==} + peerDependencies: + '@tonejs/midi': ^2.0.0 + springboard: 0.15.0-rc20 + + '@julusian/midi@3.6.1': + resolution: {integrity: sha512-sC6tTMAMZsHOQILAv/R0On5tKKhzBQUjdyYWzh9l0UQeNry12CFIyRWK1Mep5xCHWCTUB0w4gxngpciA5PgN/Q==} + engines: {node: '>=14.15'} + + '@remix-run/router@1.23.0': + resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} + engines: {node: '>=14.0.0'} + + '@springboardjs/data-storage@0.15.0-rc20': + resolution: {integrity: sha512-ff7kd/NgieXQj/15644NTVvWz904oNXIpJ7BSrjlBnmsRGDRHrt9YrCB3v03Y1NXCn/3GsM4qiMO13MEDCcq1Q==} + peerDependencies: + kysely: '>= 0.24.0' + + '@springboardjs/platforms-browser@0.15.0-rc20': + resolution: {integrity: sha512-6EcpEeEcgTYCbvXmiUZTvm/gL7hq23js9bA1tlWi9RmHnH5kIQSUACSuJwF87Yu4o+amOSMrD4yXZBg73l2e9g==} + peerDependencies: + react-router-dom: ^6 + springboard: 0.15.0-rc20 + + '@springboardjs/platforms-node@0.15.0-rc20': + resolution: {integrity: sha512-gEeC8HIWHPTH48nMVXaLJDXi1cWPc9o5ZDEA+v3dsob2/5YPQ4+JLXoIDPWKO7hqKDlbeSiiUC3LwJjHdq514A==} + peerDependencies: + isomorphic-ws: ^4.0.1 + springboard: 0.15.0-rc20 + ws: ^8.18.0 + + '@tonejs/midi@2.0.28': + resolution: {integrity: sha512-RII6YpInPsOZ5t3Si/20QKpNqB1lZ2OCFJSOzJxz38YdY/3zqDr3uaml4JuCWkdixuPqP1/TBnXzhQ39csyoVg==} + + '@trpc/client@10.45.2': + resolution: {integrity: sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==} + peerDependencies: + '@trpc/server': 10.45.2 + + '@trpc/server@10.45.2': + resolution: {integrity: sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg==} + + '@types/react@19.1.2': + resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==} + + '@types/webmidi@2.1.0': + resolution: {integrity: sha512-k898MjEUSHB+6rSeCPQk/kLgie0wgWZ2t78GlWj86HbTQ+XmtbBafYg5LNjn8bVHfItEhPGZPf579Xfc6keV6w==} + + adsr@1.0.1: + resolution: {integrity: sha512-thr9LK4jxApOzBA33IWOA83bXJFbyfbeozpHXyrMQOIhUni198uRxXqDhobW0S/51iokqty2Yz2WbLZbE6tntQ==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + array-flatten@3.0.0: + resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} + + audio-loader@0.5.0: + resolution: {integrity: sha512-mEoYRjZhqkBSen/X9i2PNosqvafEsur8bI5MNoPr0wsJu9Nzlul3Yv1elYeMPsXxTxYhXLY8AZlScBvaK4mydg==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@11.9.1: + resolution: {integrity: sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + concurrently@9.1.2: + resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} + engines: {node: '>=18'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + dexie@4.0.11: + resolution: {integrity: sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==} + + djipevents@2.0.7: + resolution: {integrity: sha512-KNFYaU85imxOCKOUsIR70Iz9E19r96/X7LSH+u0tSoZdpWcBdzoqtTsU+wuLhc6GMpSFob+KInkZAbfKi01Bjg==} + + easymidi@3.1.0: + resolution: {integrity: sha512-bxEwfPysM1L+SO/qwHaYu9dvTxw2QHFjGV9EMzqGQJbhEP2MupKpg6eJMkj+uoXN0Ep1JhVPLbNLPmt3UkZRTw==} + engines: {node: '>=14.15'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hono@4.7.6: + resolution: {integrity: sha512-564rVzELU+9BRqqx5k8sT2NFwGD3I3Vifdb6P7CmM6FiarOSY+fDC+6B+k9wcCb86ReoayteZP2ki0cRLN1jbw==} + engines: {node: '>=16.9.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isomorphic-ws@4.0.1: + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' + + jazz-midi@1.7.9: + resolution: {integrity: sha512-c8c4BBgwxdsIr1iVm53nadCrtH7BUlnX3V95ciK/gbvXN/ndE5+POskBalXgqlc/r9p2XUbdLTrgrC6fou5p9w==} + engines: {node: '>=10.0.0'} + + json-rpc-2.0@1.7.0: + resolution: {integrity: sha512-asnLgC1qD5ytP+fvBP8uL0rvj+l8P6iYICbzZ8dVxCpESffVjzA7KkYkbKCIbavs7cllwH1ZUaNtJwphdeRqpg==} + + jzz@1.9.3: + resolution: {integrity: sha512-DXQ7xXuJzC4YxNbQZy9pfHC/DOXM6IHgceTXnkhk1/+lU/qDk8W9xUaNpgCH6t1QDQ9roz7FSVtFc0/JhYQFxg==} + + kysely@0.28.1: + resolution: {integrity: sha512-umkhsHB0y2JvI83DUtuYYTfvr063xTHTcr/k1z0E2Bg39zGiBcdDvlbP79YMcItn55pKQtD120sZmKa9jhCVtw==} + engines: {node: '>=18.0.0'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + midi-file@1.2.4: + resolution: {integrity: sha512-B5SnBC6i2bwJIXTY9MElIydJwAmnKx+r5eJ1jknTLetzLflEl0GWveuBB6ACrQpecSRkOB6fhTx1PwXk2BVxnA==} + + midimessage@1.0.5: + resolution: {integrity: sha512-MPJ2tDupFOfZB5/PLp8fri1IS4fd9hPj0Bio//FBhWRQ+TsJA7/49CF1aJyraDxa0Jq8zMHAwrwXl2GINvLvgw==} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.74.0: + resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} + engines: {node: '>=10'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + note-parser@1.1.0: + resolution: {integrity: sha512-YTqWQBsRp40EFrEznnkGtmx68gcgOQ8CdoBspqGBA3G1/4mJwIYbDe/vuNpX3oGX2DhP7b1dBgTmj7p3Zr0P1Q==} + + note-parser@2.0.1: + resolution: {integrity: sha512-w9o6Fv46y3NsFxeezTZSmftBtUM/ypme6iZWVrTJvvsD5RN+w0XNDePWtfreNrZFL3jSjBFhadPoXb+pJO4UdA==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.8.0: + resolution: {integrity: sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.8.0: + resolution: {integrity: sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.14.1: + resolution: {integrity: sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + pkg-prebuilds@1.0.0: + resolution: {integrity: sha512-D9wlkXZCmjxj2kBHTw3fGSyjoahr33breGBoJcoezpi7ouYS59DJVOHMZ+dgqacSrZiJo4qtkXxLQTE+BqXJmQ==} + engines: {node: '>= 14.15.0'} + hasBin: true + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react-router-dom@6.30.0: + resolution: {integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.0: + resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + reconnecting-websocket@4.4.0: + resolution: {integrity: sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + sample-player@0.5.5: + resolution: {integrity: sha512-VQ9pXPJ1m/eTH8QK6OQ8Dn/HSVToNyY9w9vnv+y/yjkJeRm87tJ/gBEm66jItfSLhKe6VG1DfX8+oT+Mg7QUpg==} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + soundfont-player@0.12.0: + resolution: {integrity: sha512-8BJIsAt7h1PK3thSZDgF6zecgGhYkK74JnZO8WRZi3h34qG6H/DYlnv7cpRvL7Q9C8N6qld4Qwj7nJsX1gYjEA==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + springboard-cli@0.15.0-rc20: + resolution: {integrity: sha512-yfO+YVR8RuMfSOWfawnRVq4ep+j+DpGf53Ul+cJx8DlgN0GqcAdMAMJalALiFiTExN2SAYHCUK/F+WnYNnhtJQ==} + hasBin: true + peerDependencies: + '@springboardjs/platforms-browser': 0.15.0-rc20 + '@springboardjs/platforms-node': 0.15.0-rc20 + springboard: 0.15.0-rc20 + springboard-server: 0.15.0-rc20 + + springboard-server@0.15.0-rc20: + resolution: {integrity: sha512-sEbT8bXwY5fF9/DmWBU6l+zrbwOSNPa+Mr+cdjHfbkgc1Tij/Zxl7O9f6o00HIQNA+kBrQrAuxy5G/LzHv4rGg==} + peerDependencies: + '@springboardjs/data-storage': 0.15.0-rc20 + hono: ^4.6.7 + springboard: 0.15.0-rc20 + + springboard@0.15.0-rc20: + resolution: {integrity: sha512-Hy1wu2Mk8b+3hyRPppy/hiwRF9HHQ7qeOjW2l6MS7AKrD7Dt2HygkgaH5sXZySE8N9cJQodj1WkBPpvxMuI/Cw==} + peerDependencies: + '@trpc/client': ^10.45.2 + json-rpc-2.0: ^1.7.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + rxjs: ^7.0.0 + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + webmidi@3.1.12: + resolution: {integrity: sha512-X1lACggXm2BxuAPdx5wleh8S2kygduHbtR2ti5sGhivLkX6Muv/sLAYmPuIaNLOUddyxr71+3tsq8m5dKXoT4A==} + engines: {node: '>=8.5'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + zod@3.24.3: + resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + +snapshots: + + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@hono/node-server@1.14.1(hono@4.7.6)': + dependencies: + hono: 4.7.6 + + '@hono/node-ws@1.1.1(@hono/node-server@1.14.1(hono@4.7.6))(hono@4.7.6)': + dependencies: + '@hono/node-server': 1.14.1(hono@4.7.6) + hono: 4.7.6 + ws: 8.18.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@hono/trpc-server@0.3.4(@trpc/server@10.45.2)(hono@4.7.6)': + dependencies: + '@trpc/server': 10.45.2 + hono: 4.7.6 + + '@jamtools/core@0.15.0-rc20(@tonejs/midi@2.0.28)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))': + dependencies: + '@tonejs/midi': 2.0.28 + easymidi: 3.1.0 + midi-file: 1.2.4 + soundfont-player: 0.12.0 + springboard: 0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2) + webmidi: 3.1.12 + + '@julusian/midi@3.6.1': + dependencies: + node-addon-api: 6.1.0 + pkg-prebuilds: 1.0.0 + + '@remix-run/router@1.23.0': {} + + '@springboardjs/data-storage@0.15.0-rc20(kysely@0.28.1)': + dependencies: + '@trpc/client': 10.45.2(@trpc/server@10.45.2) + '@trpc/server': 10.45.2 + better-sqlite3: 11.9.1 + kysely: 0.28.1 + pg: 8.14.1 + zod: 3.24.3 + transitivePeerDependencies: + - pg-native + + '@springboardjs/platforms-browser@0.15.0-rc20(react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))': + dependencies: + react-router-dom: 6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + springboard: 0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2) + + '@springboardjs/platforms-node@0.15.0-rc20(isomorphic-ws@4.0.1(ws@8.18.1))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))(ws@8.18.1)': + dependencies: + isomorphic-ws: 4.0.1(ws@8.18.1) + springboard: 0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2) + ws: 8.18.1 + + '@tonejs/midi@2.0.28': + dependencies: + array-flatten: 3.0.0 + midi-file: 1.2.4 + + '@trpc/client@10.45.2(@trpc/server@10.45.2)': + dependencies: + '@trpc/server': 10.45.2 + + '@trpc/server@10.45.2': {} + + '@types/react@19.1.2': + dependencies: + csstype: 3.1.3 + + '@types/webmidi@2.1.0': + optional: true + + adsr@1.0.1: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + array-flatten@3.0.0: {} + + audio-loader@0.5.0: {} + + base64-js@1.5.1: {} + + better-sqlite3@11.9.1: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chownr@1.1.4: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@12.1.0: {} + + concurrently@9.1.2: + dependencies: + chalk: 4.1.2 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + csstype@3.1.3: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + detect-libc@2.0.3: {} + + dexie@4.0.11: {} + + djipevents@2.0.7: + dependencies: + '@babel/runtime': 7.27.0 + + easymidi@3.1.0: + dependencies: + '@julusian/midi': 3.6.1 + + emoji-regex@8.0.0: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + escalade@3.2.0: {} + + expand-template@2.0.3: {} + + file-uri-to-path@1.0.0: {} + + fs-constants@1.0.0: {} + + get-caller-file@2.0.5: {} + + github-from-package@0.0.0: {} + + has-flag@4.0.0: {} + + hono@4.7.6: {} + + ieee754@1.2.1: {} + + immer@10.1.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-fullwidth-code-point@3.0.0: {} + + isomorphic-ws@4.0.1(ws@8.18.1): + dependencies: + ws: 8.18.1 + + jazz-midi@1.7.9: + optional: true + + json-rpc-2.0@1.7.0: {} + + jzz@1.9.3: + dependencies: + '@types/webmidi': 2.1.0 + jazz-midi: 1.7.9 + optional: true + + kysely@0.28.1: {} + + lodash@4.17.21: {} + + midi-file@1.2.4: {} + + midimessage@1.0.5: {} + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + napi-build-utils@2.0.0: {} + + node-abi@3.74.0: + dependencies: + semver: 7.7.1 + + node-addon-api@6.1.0: {} + + note-parser@1.1.0: {} + + note-parser@2.0.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pg-cloudflare@1.1.1: + optional: true + + pg-connection-string@2.7.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.8.0(pg@8.14.1): + dependencies: + pg: 8.14.1 + + pg-protocol@1.8.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.14.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.8.0(pg@8.14.1) + pg-protocol: 1.8.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + pkg-prebuilds@1.0.0: + dependencies: + yargs: 17.7.2 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.74.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.2 + tunnel-agent: 0.6.0 + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@remix-run/router': 1.23.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 6.30.0(react@19.1.0) + + react-router@6.30.0(react@19.1.0): + dependencies: + '@remix-run/router': 1.23.0 + react: 19.1.0 + + react@19.1.0: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + reconnecting-websocket@4.4.0: {} + + regenerator-runtime@0.14.1: {} + + require-directory@2.1.1: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + sample-player@0.5.5: + dependencies: + adsr: 1.0.1 + midimessage: 1.0.5 + note-parser: 1.1.0 + + scheduler@0.26.0: {} + + semver@7.7.1: {} + + shell-quote@1.8.2: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + soundfont-player@0.12.0: + dependencies: + audio-loader: 0.5.0 + note-parser: 2.0.1 + sample-player: 0.5.5 + + split2@4.2.0: {} + + springboard-cli@0.15.0-rc20(@springboardjs/platforms-browser@0.15.0-rc20(react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)))(@springboardjs/platforms-node@0.15.0-rc20(isomorphic-ws@4.0.1(ws@8.18.1))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))(ws@8.18.1))(springboard-server@0.15.0-rc20(@springboardjs/data-storage@0.15.0-rc20(kysely@0.28.1))(@trpc/server@10.45.2)(hono@4.7.6)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)): + dependencies: + '@springboardjs/platforms-browser': 0.15.0-rc20(react-router-dom@6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)) + '@springboardjs/platforms-node': 0.15.0-rc20(isomorphic-ws@4.0.1(ws@8.18.1))(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2))(ws@8.18.1) + commander: 12.1.0 + concurrently: 9.1.2 + esbuild: 0.23.1 + springboard: 0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2) + springboard-server: 0.15.0-rc20(@springboardjs/data-storage@0.15.0-rc20(kysely@0.28.1))(@trpc/server@10.45.2)(hono@4.7.6)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)) + typescript: 5.8.3 + + springboard-server@0.15.0-rc20(@springboardjs/data-storage@0.15.0-rc20(kysely@0.28.1))(@trpc/server@10.45.2)(hono@4.7.6)(springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2)): + dependencies: + '@hono/node-server': 1.14.1(hono@4.7.6) + '@hono/node-ws': 1.1.1(@hono/node-server@1.14.1(hono@4.7.6))(hono@4.7.6) + '@hono/trpc-server': 0.3.4(@trpc/server@10.45.2)(hono@4.7.6) + '@springboardjs/data-storage': 0.15.0-rc20(kysely@0.28.1) + hono: 4.7.6 + springboard: 0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2) + transitivePeerDependencies: + - '@trpc/server' + - bufferutil + - utf-8-validate + + springboard@0.15.0-rc20(@trpc/client@10.45.2(@trpc/server@10.45.2))(json-rpc-2.0@1.7.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rxjs@7.8.2): + dependencies: + '@trpc/client': 10.45.2(@trpc/server@10.45.2) + dexie: 4.0.11 + immer: 10.1.1 + json-rpc-2.0: 1.7.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + reconnecting-websocket: 4.4.0 + rxjs: 7.8.2 + ws: 8.18.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@2.0.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + tar-fs@2.1.2: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tree-kill@1.2.2: {} + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + typescript@5.8.3: {} + + util-deprecate@1.0.2: {} + + webmidi@3.1.12: + dependencies: + djipevents: 2.0.7 + optionalDependencies: + jzz: 1.9.3 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@8.18.1: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + zod@3.24.3: {} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..287962d --- /dev/null +++ b/src/index.tsx @@ -0,0 +1 @@ +import './jamscribe_module'; diff --git a/src/jamscribe_module.tsx b/src/jamscribe_module.tsx new file mode 100644 index 0000000..23e3979 --- /dev/null +++ b/src/jamscribe_module.tsx @@ -0,0 +1,172 @@ +// TODO: useState isn't working for some reason +import React from 'react'; + +import springboard from 'springboard'; + +import '@jamtools/core/modules/io/io_module'; +import 'springboard/modules/files/files_module'; + +import type {FileSaver, RecordingConfig} from './services/recorder'; + +let fileSaver: FileSaver | undefined; + +// @platform "node" +import fs from 'node:fs'; +fileSaver = { + writeFile: async (fileName, buffer) => { + if (!fs.existsSync('./midi_files')) { + fs.mkdirSync('midi_files') + } + + const content = buffer.toString(); + await fs.promises.writeFile(fileName, content); + }, +}; +// @platform end + +import {MidiRecorderImpl} from './services/recorder'; + +type DraftedFile = { + name: string; + buffer: Buffer; +} + +const initialRecordingConfig: RecordingConfig = { + inactivityTimeLimitSeconds: 60, +}; + +springboard.registerModule('JamScribe', {}, async (moduleAPI) => { + // @platform "node" + await moduleAPI.getModule('io').ensureListening(); + // @platform end + + const recordingConfig = await moduleAPI.statesAPI.createPersistentState('recordingConfig', initialRecordingConfig); + const draftRecordingConfig = await moduleAPI.statesAPI.createSharedState('draftRecordingConfig', recordingConfig.getState()); + + const logMessages = await moduleAPI.statesAPI.createSharedState('logMessages', []); + const draftedFiles = await moduleAPI.statesAPI.createSharedState('draftedFiles', []); + + const changeDraftInactivityTimeLimit = moduleAPI.createAction('changeDraftInactivityTimeLimit', {}, async ({limit}: {limit: number}) => { + draftRecordingConfig.setState(c => ({...c, inactivityTimeLimitSeconds: limit})); + }); + + const submitInactivityTimeLimit = moduleAPI.createAction('submitInactivityTimeLimit', {}, async () => { + recordingConfig.setState(c => ({...c, inactivityTimeLimitSeconds: draftRecordingConfig.getState().inactivityTimeLimitSeconds})); + }); + + moduleAPI.registerRoute('/', {}, () => ( +
changeDraftInactivityTimeLimit({limit})} + submitInactivityTimeLimitChange={() => submitInactivityTimeLimit({})} + /> + )); + + // bail out if this is a presentation-only client + if (!moduleAPI.deps.core.isMaestro()) { + return; + } + + // default implementation of file saver + if (!fileSaver) { + fileSaver = { + writeFile: (fileName, buffer) => { + const filesModule = moduleAPI.deps.module.moduleRegistry.getModule('Files'); + const file = new File([ + new Blob([buffer.toString()]) + ], fileName); + + filesModule.uploadFile(file); + }, + }; + } + + const log = (msg: string) => { + console.log(msg); + logMessages.setState(logs => { + return [...logs, msg] + }); + } + + const ioModule = moduleAPI.deps.module.moduleRegistry.getModule('io'); + + ioModule.midiDeviceStatusSubject.subscribe(device => { + const msg = `Device '${device.name}' ${device.status}`; + log(msg); + }); + + const recorder = new MidiRecorderImpl(ioModule.midiInputSubject, {log}, fileSaver, recordingConfig); + recorder.initialize(); +}); + +type MainProps = { + logs: string[]; + availableFiles: DraftedFile[]; + + recordingConfig: RecordingConfig; + + draftInactivityTimeLimit: number; + onDraftInactivityTimeLimitChange: (newLimit: number) => void; + submitInactivityTimeLimitChange: () => void; +} + +const Main = ({ + logs, + availableFiles, + recordingConfig, + draftInactivityTimeLimit, + onDraftInactivityTimeLimitChange, + submitInactivityTimeLimitChange +}: MainProps) => { + return ( + <> +
+ + Config + +
+                    {JSON.stringify(recordingConfig, null, 2)}
+                
+ onDraftInactivityTimeLimitChange(parseInt(e.target.value))} + /> + +
+
+ Files +
    + {availableFiles.map(file => ( +
  • { + + }} + > + {file.name} +
  • + ))} +
+
+
+
    + {logs.map((msg, i) => ( +
  • + {msg} +
  • + ))} +
+
+ + ); +} diff --git a/src/services/recorder.ts b/src/services/recorder.ts new file mode 100644 index 0000000..f44c07a --- /dev/null +++ b/src/services/recorder.ts @@ -0,0 +1,198 @@ +import {writeMidi, MidiData} from 'midi-file'; +import {Buffer} from 'buffer'; +import {Subject} from 'rxjs'; + +import {MidiEventFull} from '@jamtools/core/modules/macro_module/macro_module_types'; +import {StateSupervisor} from 'springboard/services/states/shared_state_service'; + +const sendPushNotification = (data: {title: string, data: {url: string}}) => { + +}; + +// const FIVE_SECONDS = 1000 * 5; +const TICKS_PER_BEAT = 480; // Standard MIDI timing resolution +const BPM = 120; // Default BPM + +type LoggedMidiEvent = { + event: MidiEventFull; + time: number; +}; + +type Logger = { + log: (msg: string) => void; +}; + +export type FileSaver = { + writeFile: (fileName: string, buffer: Buffer) => void; +} + +export type RecordingConfig = { + inactivityTimeLimitSeconds: number; +} + +export class MidiRecorderImpl { + private deviceActivity: {[deviceName: string]: boolean} = {}; + private deviceTimeouts: {[deviceName: string]: NodeJS.Timeout | undefined} = {}; + private recordedEvents: {[deviceName: string]: LoggedMidiEvent[]} = {}; + // private INACTIVITY_LIMIT = FIVE_SECONDS; + + constructor(private onInputEvent: Subject, private logger: Logger, private fileSaver: FileSaver, private recordingConfigState: StateSupervisor) { } + + public initialize = () => { + this.onInputEvent.subscribe(this.handleMidiEvent); + }; + + private handleMidiEvent = (midiEventFull: MidiEventFull) => { + const deviceName = midiEventFull.deviceInfo.name; + const event = midiEventFull.event; + const time = performance.now(); + + this.deviceActivity[deviceName] = true; + + // Store the event in memory + if (!this.recordedEvents[deviceName]?.length) { + this.logger.log(`Started recording ${deviceName}. Will stop after ${this.getInactivityLimit() / 1000} seconds`); + this.recordedEvents[deviceName] = []; + this.notifyUserOfStartRecording(); + } + this.recordedEvents[deviceName].push({event: midiEventFull, time}); + + this.resetDeviceInactivityTimerForDevice(deviceName); + }; + + // Stop recording and save all recorded MIDI events to a file + private stopRecordingForAllDevices = () => { + this.logger.log('Stopping all recordings due to inactivity...'); + Object.keys(this.recordedEvents).forEach((deviceName) => { + this.saveRecordedMidiToFile(deviceName); + + // Clear events after saving + this.recordedEvents[deviceName] = []; + }); + }; + + private getInactivityLimit = () => { + return this.recordingConfigState.getState().inactivityTimeLimitSeconds * 1000; + } + + private resetDeviceInactivityTimerForDevice = (deviceName: string) => { + if (this.deviceTimeouts[deviceName]) { + clearTimeout(this.deviceTimeouts[deviceName]); + } + + this.deviceTimeouts[deviceName] = setTimeout(() => { + this.logger.log(`Device ${deviceName} is now inactive.`); + this.deviceActivity[deviceName] = false; + + const allInactive = Object.values(this.deviceActivity).every(isActive => !isActive); + if (allInactive) { + this.stopRecordingForAllDevices(); + } + }, this.getInactivityLimit()); + }; + + private generateFilename = (deviceName: string): string => { + const timestamp = new Date().toISOString(); + const filename = `./midi_files/${deviceName}_${timestamp}_recording.mid`; + return filename; + }; + + private saveRecordedMidiToFile = (deviceName: string) => { + const midiEvents = this.recordedEvents[deviceName]; + if (!midiEvents || midiEvents.length === 0) { + this.logger.log(`No events recorded for device: ${deviceName}`); + return; + } + + // Convert the stored events to a MIDI file structure + const midiData: MidiData = { + header: { + format: 1, + numTracks: 1, + ticksPerBeat: TICKS_PER_BEAT, + }, + tracks: [[]], + }; + + let previousTime = midiEvents[0].time; // Set the initial time + midiEvents.forEach(({event, time}) => { + const deltaTime = this.calculateDeltaTime(previousTime, time); + previousTime = time; + + const midiTrackEvent = this.convertMidiEventToMidiFileFormat(event, deltaTime); + + if (midiTrackEvent) { + midiData.tracks[0].push(midiTrackEvent); + } + }); + + const midiFilePath = this.generateFilename(deviceName); + + // Write the MIDI file to disk + try { + const outputBuffer = Buffer.from(writeMidi(midiData)); + this.fileSaver.writeFile(midiFilePath, outputBuffer); + this.logger.log(`MIDI file saved for device: ${deviceName} at ${midiFilePath}`); + this.notifyUserOfNewRecordedSession(); + } catch (error) { + this.logger.log(`Error while saving MIDI file for ${deviceName}: ${(error as Error).message}`); + } + }; + + private notifyUserOfStartRecording = () => { + sendPushNotification({ + title: 'Started recording', + data: { + url: 'http://jamscribe.local:1337', + } + }); + }; + + private notifyUserOfNewRecordedSession = () => { + sendPushNotification({ + title: 'Stopped recording', + data: { + url: 'http://jamscribe.local:1337', + } + }); + }; + + // Convert the event to a format that `midi-file` expects + private convertMidiEventToMidiFileFormat = (event: MidiEventFull, deltaTime: number): MidiData['tracks'][0][0] | null => { + if (event.event.type === 'noteon') { + return { + deltaTime, + type: 'noteOn', + noteNumber: event.event.number, + velocity: event.event.velocity || 64, + channel: event.event.channel, + }; + } + if (event.event.type === 'noteoff') { + return { + deltaTime, + type: 'noteOff', + noteNumber: event.event.number, + velocity: 0, + channel: event.event.channel, + }; + } + if (event.event.type === 'cc') { + return { + deltaTime, + type: 'controller', + controllerType: event.event.number, + value: event.event.value!, + channel: event.event.channel, + }; + } + + return null; + }; + + private calculateDeltaTime = (previousTime: number, currentTime: number): number => { + const msPerBeat = (60 / BPM) * 1000; + const msDifference = currentTime - previousTime; + return Math.round((msDifference / msPerBeat) * TICKS_PER_BEAT); + }; +}