From 0a385130000a58d2559b064edf42136e507f1586 Mon Sep 17 00:00:00 2001 From: Teon L Brooks Date: Thu, 15 May 2025 09:47:49 -0400 Subject: [PATCH 1/9] wip - audio in memory --- examples/.DS_Store | Bin 0 -> 6148 bytes examples/index.html | 55 ++- package-lock.json | 967 +++++++++++++++++++++++++++++++++++++++++++- package.json | 8 +- src/index.ts | 74 +++- 5 files changed, 1062 insertions(+), 42 deletions(-) create mode 100644 examples/.DS_Store diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + jsPsychPluginVoiceResponse Example + + + + + + - - jsPsychPluginVoiceResponse Example - - - - - + + - - \ No newline at end of file + jsPsych.run([init_mic, trial]); + + diff --git a/package-lock.json b/package-lock.json index 989fd9e..5c113c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,9 @@ "@citation-js/plugin-bibtex": "^0.7.14", "@citation-js/plugin-cff": "^0.6.1", "@citation-js/plugin-software-formats": "^0.6.1", - "jspsych": "^8.0.0" + "@huggingface/transformers": "^3.5.1", + "jspsych": "^8.0.0", + "wavefile": "^11.0.0" }, "devDependencies": { "@jspsych/config": "^3.2.2", @@ -991,6 +993,16 @@ "node": ">=14.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", @@ -1422,6 +1434,404 @@ "node": ">=10.13.0" } }, + "node_modules/@huggingface/jinja": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.4.1.tgz", + "integrity": "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@huggingface/transformers": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.5.1.tgz", + "integrity": "sha512-qWsPoJMBPYcrGuzRMVL//3dwcLXED9r+j+Hzw+hZpBfrMPdyq57ehNs7lew0D34Paj0DO0E0kZQjOZcIVN17hQ==", + "license": "Apache-2.0", + "dependencies": { + "@huggingface/jinja": "^0.4.1", + "onnxruntime-node": "1.21.0", + "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4", + "sharp": "^0.34.1" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz", + "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz", + "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz", + "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz", + "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz", + "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz", + "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz", + "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz", + "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz", + "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz", + "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz", + "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@inquirer/checkbox": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", @@ -1759,6 +2169,18 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2233,6 +2655,70 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/plugin-commonjs": { "version": "26.0.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", @@ -2808,7 +3294,6 @@ "version": "22.15.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3452,6 +3937,13 @@ "node": ">= 6" } }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3687,6 +4179,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -3842,11 +4343,23 @@ "dev": true, "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3859,9 +4372,18 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -4080,6 +4602,49 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties/node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4100,6 +4665,15 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4110,6 +4684,12 @@ "node": ">=8" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -4247,7 +4827,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4257,7 +4836,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4299,6 +4877,12 @@ "node": ">= 0.4" } }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -4820,6 +5404,12 @@ "node": ">= 10.13.0" } }, + "node_modules/flatbuffers": { + "version": "25.2.10", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.2.10.tgz", + "integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==", + "license": "Apache-2.0" + }, "node_modules/flow-parser": { "version": "0.270.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.270.0.tgz", @@ -5124,6 +5714,35 @@ "node": ">= 10.13.0" } }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -5179,6 +5798,22 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glogg": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", @@ -5196,7 +5831,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5212,6 +5846,12 @@ "dev": true, "license": "ISC" }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, "node_modules/gulp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", @@ -5404,6 +6044,18 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -6887,6 +7539,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -7013,6 +7671,12 @@ "node": ">=8" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7082,6 +7746,30 @@ "node": ">=0.10.0" } }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7186,12 +7874,23 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -7465,6 +8164,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/onnxruntime-common": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz", + "integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==", + "license": "MIT" + }, + "node_modules/onnxruntime-node": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.21.0.tgz", + "integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==", + "hasInstallScript": true, + "license": "MIT", + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "global-agent": "^3.0.0", + "onnxruntime-common": "1.21.0", + "tar": "^7.0.1" + } + }, + "node_modules/onnxruntime-web": { + "version": "1.22.0-dev.20250409-89f8206ba4", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.22.0-dev.20250409-89f8206ba4.tgz", + "integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, + "node_modules/onnxruntime-web/node_modules/onnxruntime-common": { + "version": "1.22.0-dev.20250409-89f8206ba4", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.22.0-dev.20250409-89f8206ba4.tgz", + "integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==", + "license": "MIT" + }, "node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -7761,6 +8503,12 @@ "node": ">=8" } }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -7810,6 +8558,30 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.1.tgz", + "integrity": "sha512-3qx3IRjR9WPQKagdwrKjO3Gu8RgQR2qqw+1KnigWhoVjFqegIj1K3bP11sGqhxrO46/XL7lekuG4jmjL+4cLsw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -8117,6 +8889,29 @@ "rimraf": "bin.js" } }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/roarr/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/rollup": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", @@ -8310,6 +9105,12 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "license": "MIT" + }, "node_modules/semver-greatest-satisfied-range": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", @@ -8323,6 +9124,33 @@ "node": ">= 10.13.0" } }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -8336,6 +9164,58 @@ "node": ">=8" } }, + "node_modules/sharp": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", + "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.7.1" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.1", + "@img/sharp-darwin-x64": "0.34.1", + "@img/sharp-libvips-darwin-arm64": "1.1.0", + "@img/sharp-libvips-darwin-x64": "1.1.0", + "@img/sharp-libvips-linux-arm": "1.1.0", + "@img/sharp-libvips-linux-arm64": "1.1.0", + "@img/sharp-libvips-linux-ppc64": "1.1.0", + "@img/sharp-libvips-linux-s390x": "1.1.0", + "@img/sharp-libvips-linux-x64": "1.1.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", + "@img/sharp-libvips-linuxmusl-x64": "1.1.0", + "@img/sharp-linux-arm": "0.34.1", + "@img/sharp-linux-arm64": "0.34.1", + "@img/sharp-linux-s390x": "0.34.1", + "@img/sharp-linux-x64": "0.34.1", + "@img/sharp-linuxmusl-arm64": "0.34.1", + "@img/sharp-linuxmusl-x64": "0.34.1", + "@img/sharp-wasm32": "0.34.1", + "@img/sharp-win32-ia32": "0.34.1", + "@img/sharp-win32-x64": "0.34.1" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8372,6 +9252,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -8705,6 +9600,47 @@ "node": ">=14" } }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -8921,7 +9857,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/type-detect": { @@ -9001,7 +9937,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -9323,6 +10258,18 @@ "makeerror": "1.0.12" } }, + "node_modules/wavefile": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", + "integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==", + "license": "MIT", + "bin": { + "wavefile": "bin/wavefile.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 4fdabc9..6a176b9 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,13 @@ }, "homepage": "/plugin-voice-response/README.md", "dependencies": { - "jspsych": "^8.0.0", "@citation-js/core": "^0.7.14", - "@citation-js/plugin-software-formats": "^0.6.1", "@citation-js/plugin-bibtex": "^0.7.14", - "@citation-js/plugin-cff": "^0.6.1" + "@citation-js/plugin-cff": "^0.6.1", + "@citation-js/plugin-software-formats": "^0.6.1", + "@huggingface/transformers": "^3.5.1", + "jspsych": "^8.0.0", + "wavefile": "^11.0.0" }, "devDependencies": { "@jspsych/config": "^3.2.2", diff --git a/src/index.ts b/src/index.ts index 69de7bb..ffdff2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych"; +import { pipeline } from "@huggingface/transformers"; +import { WaveFile } from 'wavefile'; import { version } from "../package.json"; @@ -50,7 +52,7 @@ const info = { /** If `true`, then an [Object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) will be generated and stored for the recorded audio. Only set this to `true` if you plan to reuse the recorded audio later in the experiment, as it is a potentially memory-intensive feature. */ save_audio_url: { type: ParameterType.BOOL, - default: false, + default: true, }, /** Whether or not to download the audio response automatically after recording ends. If true, the 'response' value will be the name of the downloaded file rather than a base64 representation of the data. Default is false. */ local_download: { @@ -112,6 +114,8 @@ class VoiceResponsePlugin implements JsPsychPlugin { private stop_event_handler; private data_available_handler; private recorded_data_chunks = []; + private arrayBuffer; + private data; constructor(private jsPsych: JsPsych) {} @@ -191,6 +195,15 @@ class VoiceResponsePlugin implements JsPsychPlugin { this.load_resolver(); }); reader.readAsDataURL(data); + // loading the data + const fileReader = new FileReader(); + // let arrayBuffer; + this.data = data; + fileReader.onloadend = () => { + this.arrayBuffer = fileReader.result; + } + fileReader.readAsArrayBuffer(data); + }; this.start_event_handler = (e) => { @@ -287,7 +300,66 @@ class VoiceResponsePlugin implements JsPsychPlugin { // move on to the next trial this.jsPsych.finishTrial(trial_data); + transcribe(trial_data.audio_url, this.arrayBuffer, this.data); + + + } +} + +async function transcribe(audio_url, arrayBuffer, data) { + + // huggingface + const transcriber = await pipeline("automatic-speech-recognition", "Xenova/whisper-tiny.en"); + // const output = await transcriber("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav"); + + + async function dataURLtoArrayBuffer(audio_url) { + const response = await fetch(audio_url).then(x => x.arrayBuffer()); + const arrayBuffer = new Uint8Array(response); + + return arrayBuffer; + } + + // dataURL way + const jfk = "http://localhost:8000/examples/audio-response-1747257891581.wav" + const dataArrayBuffer = await dataURLtoArrayBuffer(audio_url); + + let wav = new WaveFile(dataArrayBuffer); + wav.toBitDepth('32f'); + wav.toSampleRate(16000); + let audioData = wav.getSamples(); + + if (Array.isArray(audioData)) { + if (audioData.length > 1) { + const SCALING_FACTOR = Math.sqrt(2); + + // Merge channels (into first channel to save memory) + for (let i = 0; i < audioData[0].length; ++i) { + audioData[0][i] = SCALING_FACTOR * (audioData[0][i] + audioData[1][i]) / 2; + } + } + + // Select first channel + audioData = audioData[0]; } + + // const count = dataArrayBuffer.byteLength / 4; + // const audioArray = new Float32Array(dataArrayBuffer, 0, count); + + // data way + // const dataAudioArray = new Float32Array(data, 0, count); + + // const end = audioArray.length - (audioArray.length % 4); + // const trimmedAudioArray = new Float32Array(audioArray.slice(0, end)); + + // console.log(dataArrayBuffer) + // console.log(audioArray) + // console.log(data) + const outputDataArrayBuffer = await transcriber(audioData, { return_timestamps: 'word' }); + // const outputDataAudioArray = await transcriber(dataAudioArray); + + console.log(outputDataArrayBuffer) + // console.log(outputDataAudioArray) } export default VoiceResponsePlugin; From 5f11fcd3d8cea88cb2d64a7eb7b0c307c2bc132c Mon Sep 17 00:00:00 2001 From: Teon L Brooks Date: Thu, 15 May 2025 18:09:26 -0400 Subject: [PATCH 2/9] wip: plugin config still raises error --- rollup.config.mjs | 26 ++++- src/index.ts | 283 ++++++++++++++++++++++++++-------------------- 2 files changed, 183 insertions(+), 126 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 29962e7..d8e2201 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,3 +1,27 @@ import { makeRollupConfig } from "@jspsych/config/rollup"; +import esbuild from "rollup-plugin-esbuild"; -export default makeRollupConfig("jsPsychPluginVoiceResponse"); +const config = makeRollupConfig("jsPsychPluginVoiceResponse"); + +const newConfig = config.map((config) => { + if (config.output.file?.endsWith(".browser.min.js")) { + const modifiedConfig = { + ...config, + plugins: [ + ...config.plugins, + esbuild({ + // loaders: "json", + minify: true, + target: "es2020", + }), + ], + }; + + return modifiedConfig; + } else { + return config; + } +}); + +console.log(newConfig); +export default newConfig; diff --git a/src/index.ts b/src/index.ts index ffdff2f..ca52ce9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych"; import { pipeline } from "@huggingface/transformers"; -import { WaveFile } from 'wavefile'; +import { WaveFile } from "wavefile"; import { version } from "../package.json"; @@ -8,87 +8,87 @@ const info = { name: "plugin-voice-response", version: version, parameters: { - /** The HTML content to be displayed. */ - stimulus: { - type: ParameterType.HTML_STRING, - default: undefined, + /** The HTML content to be displayed. */ + stimulus: { + type: ParameterType.HTML_STRING, + default: undefined, + }, + /** How long to display the stimulus in milliseconds. The visibility CSS property of the stimulus will be set to `hidden` after this time has elapsed. If this is null, then the stimulus will remain visible until the trial ends. */ + stimulus_duration: { + type: ParameterType.INT, + default: null, + }, + /** The maximum length of the recording, in milliseconds. The default value is intentionally set low because of the potential to accidentally record very large data files if left too high. You can set this to `null` to allow the participant to control the length of the recording via the done button, but be careful with this option as it can lead to crashing the browser if the participant waits too long to stop the recording. */ + recording_duration: { + type: ParameterType.INT, + default: 2000, + }, + /** Whether to show a button on the screen that the participant can click to finish the recording. */ + show_done_button: { + type: ParameterType.BOOL, + default: true, + }, + /** The label for the done button. */ + done_button_label: { + type: ParameterType.STRING, + default: "Continue", + }, + /** The label for the record again button enabled when `allow_playback: true`. + */ + record_again_button_label: { + type: ParameterType.STRING, + default: "Record again", + }, + /** The label for the accept button enabled when `allow_playback: true`. */ + accept_button_label: { + type: ParameterType.STRING, + default: "Continue", + }, + /** Whether to allow the participant to listen to their recording and decide whether to rerecord or not. If `true`, then the participant will be shown an interface to play their recorded audio and click one of two buttons to either accept the recording or rerecord. If rerecord is selected, then stimulus will be shown again, as if the trial is starting again from the beginning. */ + allow_playback: { + type: ParameterType.BOOL, + default: false, + }, + /** If `true`, then an [Object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) will be generated and stored for the recorded audio. Only set this to `true` if you plan to reuse the recorded audio later in the experiment, as it is a potentially memory-intensive feature. */ + save_audio_url: { + type: ParameterType.BOOL, + default: true, + }, + /** Whether or not to download the audio response automatically after recording ends. If true, the 'response' value will be the name of the downloaded file rather than a base64 representation of the data. Default is false. */ + local_download: { + type: ParameterType.BOOL, + default: false, + }, + /** If local_download is true, this sets the base file name of the downloaded file, which will be followed by a timestamp. The default is 'audio-response'. */ + download_file_name: { + type: ParameterType.STRING, + default: "audio-response", + }, }, - /** How long to display the stimulus in milliseconds. The visibility CSS property of the stimulus will be set to `hidden` after this time has elapsed. If this is null, then the stimulus will remain visible until the trial ends. */ - stimulus_duration: { - type: ParameterType.INT, - default: null, + data: { + /** The time, since the onset of the stimulus, for the participant to click the done button. If the button is not clicked (or not enabled), then `rt` will be `null`. */ + rt: { + type: ParameterType.INT, + }, + /** The base64-encoded audio data (if local_download is false) or name of the downloaded file (if local_download is true). */ + response: { + type: ParameterType.STRING, + }, + /** The HTML content that was displayed on the screen. */ + stimulus: { + type: ParameterType.HTML_STRING, + }, + /** This is an estimate of when the stimulus appeared relative to the start of the audio recording. The plugin is configured so that the recording should start prior to the display of the stimulus. We have not yet been able to verify the accuracy of this estimate with external measurement devices. */ + estimated_stimulus_onset: { + type: ParameterType.INT, + }, + /** A URL to a copy of the audio data. */ + audio_url: { + type: ParameterType.STRING, + }, }, - /** The maximum length of the recording, in milliseconds. The default value is intentionally set low because of the potential to accidentally record very large data files if left too high. You can set this to `null` to allow the participant to control the length of the recording via the done button, but be careful with this option as it can lead to crashing the browser if the participant waits too long to stop the recording. */ - recording_duration: { - type: ParameterType.INT, - default: 2000, - }, - /** Whether to show a button on the screen that the participant can click to finish the recording. */ - show_done_button: { - type: ParameterType.BOOL, - default: true, - }, - /** The label for the done button. */ - done_button_label: { - type: ParameterType.STRING, - default: "Continue", - }, - /** The label for the record again button enabled when `allow_playback: true`. - */ - record_again_button_label: { - type: ParameterType.STRING, - default: "Record again", - }, - /** The label for the accept button enabled when `allow_playback: true`. */ - accept_button_label: { - type: ParameterType.STRING, - default: "Continue", - }, - /** Whether to allow the participant to listen to their recording and decide whether to rerecord or not. If `true`, then the participant will be shown an interface to play their recorded audio and click one of two buttons to either accept the recording or rerecord. If rerecord is selected, then stimulus will be shown again, as if the trial is starting again from the beginning. */ - allow_playback: { - type: ParameterType.BOOL, - default: false, - }, - /** If `true`, then an [Object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) will be generated and stored for the recorded audio. Only set this to `true` if you plan to reuse the recorded audio later in the experiment, as it is a potentially memory-intensive feature. */ - save_audio_url: { - type: ParameterType.BOOL, - default: true, - }, - /** Whether or not to download the audio response automatically after recording ends. If true, the 'response' value will be the name of the downloaded file rather than a base64 representation of the data. Default is false. */ - local_download: { - type: ParameterType.BOOL, - default: false, - }, - /** If local_download is true, this sets the base file name of the downloaded file, which will be followed by a timestamp. The default is 'audio-response'. */ - download_file_name: { - type: ParameterType.STRING, - default: 'audio-response', - }, -}, -data: { - /** The time, since the onset of the stimulus, for the participant to click the done button. If the button is not clicked (or not enabled), then `rt` will be `null`. */ - rt: { - type: ParameterType.INT, - }, - /** The base64-encoded audio data (if local_download is false) or name of the downloaded file (if local_download is true). */ - response: { - type: ParameterType.STRING, - }, - /** The HTML content that was displayed on the screen. */ - stimulus: { - type: ParameterType.HTML_STRING, - }, - /** This is an estimate of when the stimulus appeared relative to the start of the audio recording. The plugin is configured so that the recording should start prior to the display of the stimulus. We have not yet been able to verify the accuracy of this estimate with external measurement devices. */ - estimated_stimulus_onset: { - type: ParameterType.INT, - }, - /** A URL to a copy of the audio data. */ - audio_url: { - type: ParameterType.STRING, - }, -}, -// prettier-ignore -citations: '__CITATIONS__', + // prettier-ignore + citations: '__CITATIONS__', }; type Info = typeof info; @@ -146,7 +146,9 @@ class VoiceResponsePlugin implements JsPsychPlugin { } private hideStimulus(display_element: HTMLElement) { - const el: HTMLElement = display_element.querySelector("#jspsych-html-audio-response-stimulus"); + const el: HTMLElement = display_element.querySelector( + "#jspsych-html-audio-response-stimulus" + ); if (el) { el.style.visibility = "hidden"; } @@ -177,7 +179,9 @@ class VoiceResponsePlugin implements JsPsychPlugin { }; this.stop_event_handler = () => { - const data = new Blob(this.recorded_data_chunks, { type: this.recorded_data_chunks[0].type }); + const data = new Blob(this.recorded_data_chunks, { + type: this.recorded_data_chunks[0].type, + }); this.audio_url = URL.createObjectURL(data); if (trial.local_download) { const link = document.createElement("a"); @@ -201,9 +205,8 @@ class VoiceResponsePlugin implements JsPsychPlugin { this.data = data; fileReader.onloadend = () => { this.arrayBuffer = fileReader.result; - } + }; fileReader.readAsArrayBuffer(data); - }; this.start_event_handler = (e) => { @@ -239,7 +242,10 @@ class VoiceResponsePlugin implements JsPsychPlugin { } }; - this.recorder.addEventListener("dataavailable", this.data_available_handler); + this.recorder.addEventListener( + "dataavailable", + this.data_available_handler + ); this.recorder.addEventListener("stop", this.stop_event_handler); @@ -264,11 +270,13 @@ class VoiceResponsePlugin implements JsPsychPlugin { `; - display_element.querySelector("#record-again").addEventListener("click", () => { - // release object url to save memory - URL.revokeObjectURL(this.audio_url); - this.startRecording(); - }); + display_element + .querySelector("#record-again") + .addEventListener("click", () => { + // release object url to save memory + URL.revokeObjectURL(this.audio_url); + this.startRecording(); + }); display_element.querySelector("#continue").addEventListener("click", () => { this.endTrial(display_element, trial); }); @@ -280,7 +288,10 @@ class VoiceResponsePlugin implements JsPsychPlugin { private endTrial(display_element, trial) { // clear recordering event handler - this.recorder.removeEventListener("dataavailable", this.data_available_handler); + this.recorder.removeEventListener( + "dataavailable", + this.data_available_handler + ); this.recorder.removeEventListener("start", this.start_event_handler); this.recorder.removeEventListener("stop", this.stop_event_handler); @@ -289,7 +300,9 @@ class VoiceResponsePlugin implements JsPsychPlugin { rt: this.rt, stimulus: trial.stimulus, response: this.response, - estimated_stimulus_onset: Math.round(this.stimulus_start_time - this.recorder_start_time), + estimated_stimulus_onset: Math.round( + this.stimulus_start_time - this.recorder_start_time + ), }; if (trial.save_audio_url) { @@ -301,65 +314,85 @@ class VoiceResponsePlugin implements JsPsychPlugin { // move on to the next trial this.jsPsych.finishTrial(trial_data); transcribe(trial_data.audio_url, this.arrayBuffer, this.data); - - } } async function transcribe(audio_url, arrayBuffer, data) { - // huggingface - const transcriber = await pipeline("automatic-speech-recognition", "Xenova/whisper-tiny.en"); + const transcriber = await pipeline( + "automatic-speech-recognition", + "Xenova/whisper-tiny.en" + ); // const output = await transcriber("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav"); - async function dataURLtoArrayBuffer(audio_url) { - const response = await fetch(audio_url).then(x => x.arrayBuffer()); + const response = await fetch(audio_url).then((x) => x.arrayBuffer()); const arrayBuffer = new Uint8Array(response); - + return arrayBuffer; } - + // dataURL way - const jfk = "http://localhost:8000/examples/audio-response-1747257891581.wav" + // const wav = "https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav" + const wav = "http://localhost:8000/examples/audio-response-1747257891581.wav"; const dataArrayBuffer = await dataURLtoArrayBuffer(audio_url); - - let wav = new WaveFile(dataArrayBuffer); - wav.toBitDepth('32f'); - wav.toSampleRate(16000); - let audioData = wav.getSamples(); - - if (Array.isArray(audioData)) { - if (audioData.length > 1) { - const SCALING_FACTOR = Math.sqrt(2); - - // Merge channels (into first channel to save memory) - for (let i = 0; i < audioData[0].length; ++i) { - audioData[0][i] = SCALING_FACTOR * (audioData[0][i] + audioData[1][i]) / 2; - } - } - // Select first channel - audioData = audioData[0]; - } + // AudioContext + + // const setAudioFromRecording = async (data: Blob) => { + + // const fileReader = new FileReader(); + // fileReader.onloadend = async () => { + // const audioCTX = new AudioContext({ + // sampleRate: 16000, //determined by whisper-tiny, figure this out later + // }); + // const arrayBuffer = fileReader.result as ArrayBuffer; + // const decoded = await audioCTX.decodeAudioData(arrayBuffer); + // setAudioData({ + // buffer: decoded, + // url: blobUrl, + // source: AudioSource.RECORDING, + // mimeType: data.type, + // }); + // }; + // fileReader.readAsArrayBuffer(data); + // }; + + // let wav = new WaveFile(dataArrayBuffer); + // wav.toBitDepth('32f'); + // wav.toSampleRate(16000); + // let audioData = wav.getSamples(); + + // if (Array.isArray(audioData)) { + // if (audioData.length > 1) { + // const SCALING_FACTOR = Math.sqrt(2); + + // // Merge channels (into first channel to save memory) + // for (let i = 0; i < audioData[0].length; ++i) { + // audioData[0][i] = SCALING_FACTOR * (audioData[0][i] + audioData[1][i]) / 2; + // } + // } + + // // Select first channel + // audioData = audioData[0]; + // } // const count = dataArrayBuffer.byteLength / 4; // const audioArray = new Float32Array(dataArrayBuffer, 0, count); // data way // const dataAudioArray = new Float32Array(data, 0, count); - + // const end = audioArray.length - (audioArray.length % 4); // const trimmedAudioArray = new Float32Array(audioArray.slice(0, end)); // console.log(dataArrayBuffer) // console.log(audioArray) // console.log(data) - const outputDataArrayBuffer = await transcriber(audioData, { return_timestamps: 'word' }); - // const outputDataAudioArray = await transcriber(dataAudioArray); - - console.log(outputDataArrayBuffer) - // console.log(outputDataAudioArray) + const output = await transcriber(wav, { return_timestamps: "word" }); + // const output = await transcriber(dataAudioArray); + + console.log(output); } export default VoiceResponsePlugin; From d475ff0075634579a8ef7e694480dbf52c263407 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Thu, 15 May 2025 21:04:55 -0400 Subject: [PATCH 3/9] attempt to overwrite target 2015 with 2020 in esbuild rollup IIFE config --- rollup.config.mjs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index d8e2201..640ff12 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,27 +1,23 @@ import { makeRollupConfig } from "@jspsych/config/rollup"; import esbuild from "rollup-plugin-esbuild"; -const config = makeRollupConfig("jsPsychPluginVoiceResponse"); - -const newConfig = config.map((config) => { - if (config.output.file?.endsWith(".browser.min.js")) { - const modifiedConfig = { - ...config, - plugins: [ - ...config.plugins, - esbuild({ - // loaders: "json", +const newConfig = makeRollupConfig("jsPsychPluginVoiceResponse").map((config) => { + // Find the IIFE config with target: "es2015" + if (Array.isArray(config.plugins) && config.output?.file?.endsWith(".browser.min.js")) { + const updatedPlugins = config.plugins.map((plugin) => { + // Find esbuild and replace it with target: "es2020" + if (plugin.name === "esbuild") { + return esbuild({ + loaders: { ".json": "json" }, minify: true, - target: "es2020", - }), - ], - }; - - return modifiedConfig; - } else { - return config; + target: "es2020" + }); + } + return plugin; + }); + return { ...config, plugins: updatedPlugins }; } + return config; }); -console.log(newConfig); export default newConfig; From 0f98dd1d0f03e182b0bece82eab4d56bd0f656a4 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 16 Jun 2026 20:04:26 -0400 Subject: [PATCH 4/9] remove the data URL and file path method, and replace with audioBlob.arrayBuffer, convert audio for whisper, wrap in try/catch --- src/index.ts | 137 ++++++++++++++++++++------------------------------- 1 file changed, 54 insertions(+), 83 deletions(-) diff --git a/src/index.ts b/src/index.ts index ca52ce9..c714845 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych"; import { pipeline } from "@huggingface/transformers"; -import { WaveFile } from "wavefile"; import { version } from "../package.json"; @@ -86,6 +85,14 @@ const info = { audio_url: { type: ParameterType.STRING, }, + /** The full transcript text returned by Whisper.*/ + transcript: { + type: ParameterType.STRING, + }, + /** Word-level timestamps from Whisper. */ + transcript_chunks: { + type: ParameterType.COMPLEX, + }, }, // prettier-ignore citations: '__CITATIONS__', @@ -93,6 +100,17 @@ const info = { type Info = typeof info; +// Loaded once on first use; subsequent trials reuse the cached model. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let _transcriber: any = null; + +async function getTranscriber() { + if (!_transcriber) { + _transcriber = await pipeline("automatic-speech-recognition", "Xenova/whisper-tiny.en"); + } + return _transcriber; +} + /** * **plugin-voice-response** * @@ -114,8 +132,7 @@ class VoiceResponsePlugin implements JsPsychPlugin { private stop_event_handler; private data_available_handler; private recorded_data_chunks = []; - private arrayBuffer; - private data; + private data: Blob; constructor(private jsPsych: JsPsych) {} @@ -199,14 +216,7 @@ class VoiceResponsePlugin implements JsPsychPlugin { this.load_resolver(); }); reader.readAsDataURL(data); - // loading the data - const fileReader = new FileReader(); - // let arrayBuffer; this.data = data; - fileReader.onloadend = () => { - this.arrayBuffer = fileReader.result; - }; - fileReader.readAsArrayBuffer(data); }; this.start_event_handler = (e) => { @@ -317,82 +327,43 @@ class VoiceResponsePlugin implements JsPsychPlugin { } } -async function transcribe(audio_url, arrayBuffer, data) { - // huggingface - const transcriber = await pipeline( - "automatic-speech-recognition", - "Xenova/whisper-tiny.en" - ); - // const output = await transcriber("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav"); +async function transcribe(audioBlob: Blob, savedResult: Record): Promise { + console.log("[transcribe] starting — loading model if needed"); + try { + const transcriber = await getTranscriber(); + console.log("[transcribe] model ready — decoding audio"); + + // Decode and resample to 16kHz (Whisper's required sample rate) using AudioContext. + // decodeAudioData resamples to match the context's sampleRate. + const arrayBuffer = await audioBlob.arrayBuffer(); + const audioContext = new AudioContext({ sampleRate: 16000 }); + const decoded = await audioContext.decodeAudioData(arrayBuffer); + audioContext.close(); + + // Downmix stereo to mono + let audioData: Float32Array; + if (decoded.numberOfChannels > 1) { + const ch0 = decoded.getChannelData(0); + const ch1 = decoded.getChannelData(1); + audioData = new Float32Array(ch0.length); + const SCALING_FACTOR = Math.sqrt(2); + for (let i = 0; i < ch0.length; i++) { + audioData[i] = (SCALING_FACTOR * (ch0[i] + ch1[i])) / 2; + } + } else { + audioData = decoded.getChannelData(0); + } - async function dataURLtoArrayBuffer(audio_url) { - const response = await fetch(audio_url).then((x) => x.arrayBuffer()); - const arrayBuffer = new Uint8Array(response); + console.log("[transcribe] running inference"); + const output = await transcriber(audioData, { return_timestamps: "word" }); + console.log("[transcribe] result:", output); - return arrayBuffer; + // Write result back into the stored trial data object (direct reference — no copy was made). + savedResult.transcript = (output as any).text; + savedResult.transcript_chunks = (output as any).chunks; + } catch (err) { + console.error("[transcribe] failed:", err); } - - // dataURL way - // const wav = "https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav" - const wav = "http://localhost:8000/examples/audio-response-1747257891581.wav"; - const dataArrayBuffer = await dataURLtoArrayBuffer(audio_url); - - // AudioContext - - // const setAudioFromRecording = async (data: Blob) => { - - // const fileReader = new FileReader(); - // fileReader.onloadend = async () => { - // const audioCTX = new AudioContext({ - // sampleRate: 16000, //determined by whisper-tiny, figure this out later - // }); - // const arrayBuffer = fileReader.result as ArrayBuffer; - // const decoded = await audioCTX.decodeAudioData(arrayBuffer); - // setAudioData({ - // buffer: decoded, - // url: blobUrl, - // source: AudioSource.RECORDING, - // mimeType: data.type, - // }); - // }; - // fileReader.readAsArrayBuffer(data); - // }; - - // let wav = new WaveFile(dataArrayBuffer); - // wav.toBitDepth('32f'); - // wav.toSampleRate(16000); - // let audioData = wav.getSamples(); - - // if (Array.isArray(audioData)) { - // if (audioData.length > 1) { - // const SCALING_FACTOR = Math.sqrt(2); - - // // Merge channels (into first channel to save memory) - // for (let i = 0; i < audioData[0].length; ++i) { - // audioData[0][i] = SCALING_FACTOR * (audioData[0][i] + audioData[1][i]) / 2; - // } - // } - - // // Select first channel - // audioData = audioData[0]; - // } - - // const count = dataArrayBuffer.byteLength / 4; - // const audioArray = new Float32Array(dataArrayBuffer, 0, count); - - // data way - // const dataAudioArray = new Float32Array(data, 0, count); - - // const end = audioArray.length - (audioArray.length % 4); - // const trimmedAudioArray = new Float32Array(audioArray.slice(0, end)); - - // console.log(dataArrayBuffer) - // console.log(audioArray) - // console.log(data) - const output = await transcriber(wav, { return_timestamps: "word" }); - // const output = await transcriber(dataAudioArray); - - console.log(output); } export default VoiceResponsePlugin; From 9c49c2863211c1410d61882e4b4cba42d12e4d24 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 16 Jun 2026 20:05:30 -0400 Subject: [PATCH 5/9] call transcribe before finishTrial and await it (cannot modify trial data after ending trial) --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index c714845..dfd3fa4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -321,9 +321,10 @@ class VoiceResponsePlugin implements JsPsychPlugin { URL.revokeObjectURL(this.audio_url); } + await transcribe(this.data, trial_data); + // move on to the next trial this.jsPsych.finishTrial(trial_data); - transcribe(trial_data.audio_url, this.arrayBuffer, this.data); } } From 89da6f3bc45da3645e434b2168330ebf420be90c Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 16 Jun 2026 20:12:57 -0400 Subject: [PATCH 6/9] mark endTrial as async --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index dfd3fa4..65201d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -295,7 +295,7 @@ class VoiceResponsePlugin implements JsPsychPlugin { // audio.src = } - private endTrial(display_element, trial) { + private async endTrial(display_element, trial) { // clear recordering event handler this.recorder.removeEventListener( From 1b446a87255929b48f7be0fa2236dbc675330fd3 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 16 Jun 2026 20:14:26 -0400 Subject: [PATCH 7/9] add a start trial before recording starts --- examples/index.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/index.html b/examples/index.html index 704aefa..b133040 100644 --- a/examples/index.html +++ b/examples/index.html @@ -3,8 +3,10 @@ jsPsychPluginVoiceResponse Example - + + + @@ -21,13 +23,20 @@ type: jsPsychInitializeMicrophone, }; + const start = { + type: jsPsychHtmlButtonResponse, + stimulus: "Recording will begin when you press start.", + choices: ["Start"] + }; + const trial = { type: jsPsychPluginVoiceResponse, stimulus: "Say something!", allow_playback: true, + save_audio_url: false, // local_download: true, }; - jsPsych.run([init_mic, trial]); + jsPsych.run([init_mic, start, trial]); From 9b293ac2a9b64da4f5dbc90618bd355696064557 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 16 Jun 2026 20:15:11 -0400 Subject: [PATCH 8/9] suppress huggingface warnings --- rollup.config.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rollup.config.mjs b/rollup.config.mjs index 640ff12..033bfd7 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,6 +1,14 @@ import { makeRollupConfig } from "@jspsych/config/rollup"; import esbuild from "rollup-plugin-esbuild"; +// Suppress known harmless warnings from @huggingface/transformers and onnxruntime-common. +// These are upstream library issues that can't be fixed here. +function onwarn(warning, warn) { + if (warning.code === "INVALID_ANNOTATION" && warning.id?.includes("@huggingface/transformers")) return; + if (warning.code === "CIRCULAR_DEPENDENCY" && warning.message?.includes("onnxruntime-common")) return; + warn(warning); +} + const newConfig = makeRollupConfig("jsPsychPluginVoiceResponse").map((config) => { // Find the IIFE config with target: "es2015" if (Array.isArray(config.plugins) && config.output?.file?.endsWith(".browser.min.js")) { From 03b2992d98b941d62c9bbd0b2285d39bcfc98685 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 16 Jun 2026 20:18:27 -0400 Subject: [PATCH 9/9] apply onwarn to both browser output files; clean up function flow (always return updatedConfig rather than early returns) --- rollup.config.mjs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 033bfd7..62e9451 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -10,10 +10,15 @@ function onwarn(warning, warn) { } const newConfig = makeRollupConfig("jsPsychPluginVoiceResponse").map((config) => { - // Find the IIFE config with target: "es2015" - if (Array.isArray(config.plugins) && config.output?.file?.endsWith(".browser.min.js")) { - const updatedPlugins = config.plugins.map((plugin) => { - // Find esbuild and replace it with target: "es2020" + const isBrowserBuild = + config.output?.file?.endsWith(".browser.js") || + config.output?.file?.endsWith(".browser.min.js"); + + let updatedConfig = isBrowserBuild ? { ...config, onwarn } : config; + + // Replace esbuild target in minified build: es2015 can't handle modern transformers syntax + if (Array.isArray(updatedConfig.plugins) && updatedConfig.output?.file?.endsWith(".browser.min.js")) { + const updatedPlugins = updatedConfig.plugins.map((plugin) => { if (plugin.name === "esbuild") { return esbuild({ loaders: { ".json": "json" }, @@ -23,9 +28,10 @@ const newConfig = makeRollupConfig("jsPsychPluginVoiceResponse").map((config) => } return plugin; }); - return { ...config, plugins: updatedPlugins }; + updatedConfig = { ...updatedConfig, plugins: updatedPlugins }; } - return config; + + return updatedConfig; }); export default newConfig;