├── .gitignore ├── config.env ├── shell.ps1 ├── 255s.raw ├── workbook2.xlsx ├── dpsk-graphs.xlsx ├── docs ├── .detecting-phase-shifts.un~ ├── detecting-phase-shifts~ └── detecting-phase-shifts.txt ├── src ├── lots-of-255s.ts ├── modules.ts ├── types-and-constants.ts ├── defunct.ts ├── types │ ├── wav.d.ts │ └── mic.d.ts ├── yargs.ts ├── core.ts ├── cmds │ ├── record-backup.ts │ ├── raw-to-slice.ts │ ├── raw-to-bits.ts │ ├── replay-backup.ts │ ├── parse.ts │ └── parse-dpsk-legacy.ts ├── cli.ts └── generator-utils.ts ├── bin ├── sox └── record-backup ├── source-backup ├── README.md ├── MKP2R_01.WAV └── PO33-empty-backup-16bit-44100.wav ├── vendor └── sox-14.4.2 │ ├── sox.exe │ ├── sox.pdf │ ├── soxi.pdf │ ├── wget.ini │ ├── ChangeLog.txt │ ├── README.txt │ ├── libogg-0.dll │ ├── libssp-0.dll │ ├── soxformat.pdf │ ├── wget.exe │ ├── zlib1.dll │ ├── LICENSE.GPL.txt │ ├── README.win32.txt │ ├── batch-example.bat │ ├── libflac-8.dll │ ├── libgomp-1.dll │ ├── libid3tag-0.dll │ ├── libpng16-16.dll │ ├── libsox-3.dll │ ├── libvorbis-0.dll │ ├── libwavpack-1.dll │ ├── libgcc_s_sjlj-1.dll │ ├── libvorbisenc-2.dll │ ├── libvorbisfile-3.dll │ └── libwinpthread-1.dll ├── .gitattributes ├── data ├── __legacy__ │ ├── left.raw │ ├── right.parsed │ ├── right.raw │ └── right.parsed.gz ├── empty-baseline │ ├── left.bits │ ├── left.phases │ ├── right.bits │ ├── backup.wav │ ├── left.96000.bits │ ├── left.96000.s8 │ ├── right.96000.s8 │ ├── right.phases │ ├── subtracted.wav │ ├── slice.200000.202000.tsv │ ├── slice.76000.116000.tsv │ ├── left.reconstituted.44100.s16 │ └── right.reconstituted.44100.s16 ├── baseline-w-bpm-61 │ ├── backup.wav │ ├── left.bits │ ├── right.bits │ ├── left.96000.phases │ ├── left.96000.s8 │ ├── right.96000.phases │ └── right.96000.s8 ├── baseline-w-bpm-back-to-60 │ ├── left.bits │ ├── backup.wav │ ├── left.96000.s8 │ ├── right.96000.s8 │ ├── right.bits │ ├── left.96000.phases │ └── right.96000.phases └── sample-1-40-silence │ └── backup.wav ├── shell ├── package.json ├── .vscode └── shell.code-snippets ├── wav-file.js ├── README.md ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | ~$* 2 | node_modules 3 | 4 | -------------------------------------------------------------------------------- /config.env: -------------------------------------------------------------------------------- 1 | audioDevice=-d 2 | duration=10 3 | sampleRate=96000 -------------------------------------------------------------------------------- /shell.ps1: -------------------------------------------------------------------------------- 1 | $env:PATH = "$( $env:PATH );$PSScriptRoot/vendor/sox-14.4.2" -------------------------------------------------------------------------------- /255s.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cspotcode/PO-33-KO-backup-re/HEAD/255s.raw -------------------------------------------------------------------------------- /workbook2.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cspotcode/PO-33-KO-backup-re/HEAD/workbook2.xlsx -------------------------------------------------------------------------------- /dpsk-graphs.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cspotcode/PO-33-KO-backup-re/HEAD/dpsk-graphs.xlsx -------------------------------------------------------------------------------- /docs/.detecting-phase-shifts.un~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cspotcode/PO-33-KO-backup-re/HEAD/docs/.detecting-phase-shifts.un~ -------------------------------------------------------------------------------- /src/lots-of-255s.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const buffer = Buffer.alloc(44100 * 50, 255); 4 | fs.writeFileSync('255s.raw', buffer); -------------------------------------------------------------------------------- /bin/sox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | __dirname="$(dirname "$( readlink -m "${BASH_SOURCE[0]}" )" )" 4 | 5 | "$__dirname/../vendor/sox-14.4.2/sox.exe" "$@" 6 | -------------------------------------------------------------------------------- /source-backup/README.md: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bf7e67a0c57073f3eb2aa70b2046ac78ad4a3fb9d8f86a1fab5ecd74af1b321b 3 | size 47 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/sox.exe: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e0e3cdc4bcdfbb5b91ac8f53b024964d092f89ba90130ba74b223a1df11b5439 3 | size 213624 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/sox.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:002c30bced27f5276115a5aa59b0a077e7942894cd2562e8d879941d950c246f 3 | size 326565 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/soxi.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2d21aa8d73878af558164b0bd34643c66354af47c2fc9011f12bf1238598550a 3 | size 8836 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/wget.ini: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:58120b725e0240049821b48007d002bb2b448df961a0a0f1f45707ebeefb2aa9 3 | size 4336 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | data/** filter=lfs diff=lfs merge=lfs -text 2 | source-backup/** filter=lfs diff=lfs merge=lfs -text 3 | vendor/** filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /data/__legacy__/left.raw: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:31f0fd5023c184185fee2273dc19b51ab07a5964003bb0d53daa54132d6f0062 3 | size 16879848 4 | -------------------------------------------------------------------------------- /data/__legacy__/right.parsed: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:38cf649d3ef26ee646e06ec122b7b806bc25763874e0ebeb2e1a764739f20b8a 3 | size 1325695 4 | -------------------------------------------------------------------------------- /data/__legacy__/right.raw: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9ff66bbd7920f730184e87c8c7b62e73c88e03e04cf74019ba5920d8966c5c5d 3 | size 16879848 4 | -------------------------------------------------------------------------------- /data/empty-baseline/left.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:494609cfc81c327cb18bef8f250599562ca89389e73d6ae8146fbcaa015b4d46 3 | size 182348 4 | -------------------------------------------------------------------------------- /data/empty-baseline/left.phases: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:30da4c850bb8c38311c4c9088b3d6086ad5898ac9a5049938eb5ad972a5e74bb 3 | size 45586 4 | -------------------------------------------------------------------------------- /data/empty-baseline/right.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:494609cfc81c327cb18bef8f250599562ca89389e73d6ae8146fbcaa015b4d46 3 | size 182348 4 | -------------------------------------------------------------------------------- /source-backup/MKP2R_01.WAV: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:59412ddf606da592cca786c68b1d4abb4e880ff9ba5f20903840459e515994da 3 | size 33759740 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/ChangeLog.txt: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4a6d45ad06b1c0159b9245edd494e06d2f28ec78f840bd26352be6441d2ed46d 3 | size 83796 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/README.txt: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:abd41a4d33716d0880652802fbfa2f38c2da29b05919a160f62c2154341d7d85 3 | size 8073 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libogg-0.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3262325b43b6f249831cc15428372a6daa34cf2459e66ed047dbbd9f00f49378 3 | size 27423 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libssp-0.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:113da78ba0514b3947e0988376002c4d5f783d62d5058f99259771648f2e9138 3 | size 73555 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/soxformat.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:eefdc74794e0e47c6350545f1a0467d55df53b89e4ad8886d4e6f4573ae1956a 3 | size 64479 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/wget.exe: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a48ad33695a44de887bba8f2f3174fd8fb01a46a19e3ec9078b0118647ccf599 3 | size 401408 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/zlib1.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:189336a0cf1131d6a681e81b43385ade7252f7211df50774772d8c8ec40d5f8f 3 | size 85026 4 | -------------------------------------------------------------------------------- /data/__legacy__/right.parsed.gz: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b5e5170cd21b96c6ec7737c4178d27f636acd31363abf10c903d49d058ac74ef 3 | size 563318 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/backup.wav: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:aee14ab0ef5415783dd0d62cbd95f7d83149e55d51ef82769c22c818f4dc9de0 3 | size 1920044 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/left.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:77526366fb47972d7d21f09e344f6c18c77ed2de1cd4d3f0282baecd0f5071d5 3 | size 182349 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/right.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:77526366fb47972d7d21f09e344f6c18c77ed2de1cd4d3f0282baecd0f5071d5 3 | size 182349 4 | -------------------------------------------------------------------------------- /data/empty-baseline/backup.wav: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f1f9274ed6971af63255a691c8915778dc1aeddf0855baa6ab8a98bc05faad2d 3 | size 2214040 4 | -------------------------------------------------------------------------------- /data/empty-baseline/left.96000.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:494609cfc81c327cb18bef8f250599562ca89389e73d6ae8146fbcaa015b4d46 3 | size 182348 4 | -------------------------------------------------------------------------------- /data/empty-baseline/left.96000.s8: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:679adf8d269371ed482b9d53c6e93caa42b4a22a12132c51abb62a6e985043ce 3 | size 553499 4 | -------------------------------------------------------------------------------- /data/empty-baseline/right.96000.s8: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:941b323b716115840f395bedcc308b0b31380f091b6ed6d45defd0c7809c270c 3 | size 553499 4 | -------------------------------------------------------------------------------- /data/empty-baseline/right.phases: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0be668c16fda089e71179e024c613a84c6e48429d44177d717f99f99a517a12a 3 | size 45586 4 | -------------------------------------------------------------------------------- /data/empty-baseline/subtracted.wav: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:728c24d9c6a62f6d077dbc28f61337d4c66a3761bd7e2719733081b987742e3d 3 | size 960044 4 | -------------------------------------------------------------------------------- /src/modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import Path from 'path'; 3 | import stream from 'stream'; 4 | import assert from 'assert'; 5 | export {fs, Path, stream, assert}; -------------------------------------------------------------------------------- /vendor/sox-14.4.2/LICENSE.GPL.txt: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9f2e250993c6206fac643824e05e5a0d7d3e0895d9e09a5ce4b12bc2610afc11 3 | size 18326 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/README.win32.txt: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:62d3273c2901648c74844e18436525bdf527a2357441e91a865f05b5b9f2a76c 3 | size 6010 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/batch-example.bat: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:596fc49c554985087b1bd2a3ada98b2465a436a5ee8e78b019f71cf702ba38a6 3 | size 534 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libflac-8.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2c154aeaad085e01361b967adfec84b63db76a82a45681b3e6cdfacb6088e369 3 | size 284258 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libgomp-1.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9dbbb0cb3f31ded7fbf51141a39578c0bc5cc85b83e9b6908657a6083948b9ba 3 | size 424566 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libid3tag-0.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4ec8e74cdd48a388a41f7f3ab2cea810a1661f47e79b6338192f000341f20af0 3 | size 73559 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libpng16-16.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:22d46adb7927690eeeb1a27f9544108f7a1c66bf1daec90f3c41aa36e176920d 3 | size 211237 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libsox-3.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:240a7e47a4274908786220f1b92372ed1b5f2a1c29874292fad5e64f120d84b4 3 | size 2349388 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libvorbis-0.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:03ae1625a47c1255d70e7d63ee42ef2483c7e2adc9a33d21f45799f323a530b4 3 | size 171087 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libwavpack-1.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9123e73f801629a8791c80d9dc5041ee23f7f1d9ed8510f2f758269159aa26b8 3 | size 157861 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/left.96000.phases: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:06d8113013c1865ccc405f7646e59fc5121aaf033b672d30658d85bcb9bbe405 3 | size 45586 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/left.96000.s8: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8cb33cba1d6f0ac68e1e221e4ebce06a6ac0b3da39ab64bc0240e61061ba40cd 3 | size 960000 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/right.96000.phases: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:340419c2b06e654ee10c007816a266efddad5e8354511414d91e2a1faec2470b 3 | size 45586 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-61/right.96000.s8: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2e1457055eb878073fe9cb7d1e5e8af69fca3c8598373b0ea6d8bdebcdf2ba3d 3 | size 960000 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/left.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:494609cfc81c327cb18bef8f250599562ca89389e73d6ae8146fbcaa015b4d46 3 | size 182348 4 | -------------------------------------------------------------------------------- /data/sample-1-40-silence/backup.wav: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c249f9ac113f464cea58d2f8f9d6356456f00857ad61d8146091339d710fecfe 3 | size 99715076 4 | -------------------------------------------------------------------------------- /shell: -------------------------------------------------------------------------------- 1 | export PATH="$PATH:$( dirname "$( readlink -m "${BASH_SOURCE[0]}" )" )/bin" 2 | export PATH="$PATH:$( dirname "$( readlink -m "${BASH_SOURCE[0]}" )" )/node_modules/.bin" 3 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libgcc_s_sjlj-1.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2af381dbd5ea1a9a150307b1cba3a150321a3c3ff09fb6ead9bba1e880e03a64 3 | size 474449 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libvorbisenc-2.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3b809daf958b6dedeedbe05e91a0ac016b18c871b33dc98c728f8b3440874385 3 | size 567578 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libvorbisfile-3.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1a14d8b0749f66811dacb23b093d54d4ff88036d49fd017b0d98b21de7f9ef1d 3 | size 38203 4 | -------------------------------------------------------------------------------- /vendor/sox-14.4.2/libwinpthread-1.dll: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:036af4b9aed20a6bd8b1993f2e0a4789c2ba555c00c0b2d72a6d6a6b8c13ef68 3 | size 54131 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/backup.wav: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:89e2924b522c4aaab595e5a0d619dec1d371175a8f586323c95141ab287722a0 3 | size 1920044 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/left.96000.s8: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:801ed5683dbb4d5915ab59e9bb3737cdfe4adf22c537bd04fc23a4bd009f3a81 3 | size 960000 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/right.96000.s8: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cdb34af8e7cb5caae9fa80055eeb4ddeb8c4d6b6e23ddb11e68a47e0e48a0f54 3 | size 960000 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/right.bits: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:494609cfc81c327cb18bef8f250599562ca89389e73d6ae8146fbcaa015b4d46 3 | size 182348 4 | -------------------------------------------------------------------------------- /data/empty-baseline/slice.200000.202000.tsv: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:59d0988fb609b70069ff81ed7c270de6aa33689448f6d15c46c2dfb66cb8f286 3 | size 13702 4 | -------------------------------------------------------------------------------- /data/empty-baseline/slice.76000.116000.tsv: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0387093abf07063b977dcad9380ab496fcfd6786cd1c16afbfc0fda25605b9b6 3 | size 274243 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/left.96000.phases: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:30da4c850bb8c38311c4c9088b3d6086ad5898ac9a5049938eb5ad972a5e74bb 3 | size 45586 4 | -------------------------------------------------------------------------------- /data/baseline-w-bpm-back-to-60/right.96000.phases: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0be668c16fda089e71179e024c613a84c6e48429d44177d717f99f99a517a12a 3 | size 45586 4 | -------------------------------------------------------------------------------- /data/empty-baseline/left.reconstituted.44100.s16: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:50d9b1f47aa76e91342b7cf14c361581ac55b0ae58ec4ff36ebd120680dc8cb0 3 | size 509054 4 | -------------------------------------------------------------------------------- /data/empty-baseline/right.reconstituted.44100.s16: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cfd32c10e4ccb57b74a89fcf1fdc1772fc59dd11d55c963b55836f7a6b79ea7d 3 | size 509054 4 | -------------------------------------------------------------------------------- /source-backup/PO33-empty-backup-16bit-44100.wav: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:96da0bf76b0fb33dc4cfc696793c89615e4bd62fb020b3d80dd72abe4550adbe 3 | size 1016880 4 | -------------------------------------------------------------------------------- /src/types-and-constants.ts: -------------------------------------------------------------------------------- 1 | export type Bit = 0 | 1; 2 | 3 | export type Phase = typeof Phases[number]; 4 | 5 | export const Phases = ['A', 'B', 'C', 'D'] as const; 6 | 7 | export interface ZeroCrossing { 8 | side: 1 | -1; 9 | timestamp: number; 10 | delta: number; 11 | } 12 | 13 | export const carrierHz = 7800; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@cspotcode/speaker": "^0.4.2-node12support", 4 | "@types/fs-extra": "8", 5 | "@types/node": "^12.7.5", 6 | "@types/yargs": "^13.0.2", 7 | "fs-extra": "8.1.0", 8 | "mic": "2.1.2", 9 | "source-map-support": "^0.5.13", 10 | "ts-node": "^8.4.1", 11 | "ts-node-to": "0.0.3", 12 | "tslib": "^1.10.0", 13 | "typescript": "^3.6.3", 14 | "wav": "1.0.2", 15 | "wavefile": "^8.4.5", 16 | "yargs": "^14.0.0" 17 | }, 18 | "scripts": { 19 | "cli": "ts-node-to ./src/cli.ts" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/defunct.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "./modules"; 2 | 3 | /** Read a stream of bits, emitting as bytes */ 4 | function* bitsToBytes(bits: Generator<1 | 0>): Generator { 5 | let nextByte = 0; 6 | let bitIndex = 7; 7 | for(const bit of bits) { 8 | assert(bit === 0 || bit === 1); 9 | if(bit) { 10 | nextByte |= 1< 2 | declare module 'wav' { 3 | export class Reader {} 4 | export interface Reader extends NodeJS.ReadableStream {} 5 | export interface WriterOptions { 6 | channels?: number; 7 | sampleRate?: number; 8 | bitDepth?: number; 9 | } 10 | export class Writer { 11 | constructor(options: WriterOptions); 12 | } 13 | export type FileWriterOptions = WriterOptions & Exclude[1], string>; 14 | export interface Writer extends NodeJS.WritableStream {} 15 | export class FileWriter { 16 | constructor(path: string, options?: FileWriterOptions); 17 | } 18 | export interface FileWriter extends NodeJS.WritableStream {} 19 | } -------------------------------------------------------------------------------- /src/types/mic.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module "mic" { 3 | import { Transform } from "stream"; 4 | 5 | export = mic; 6 | function mic(options: mic.Options): mic.Mic; 7 | namespace mic { 8 | export interface Mic { 9 | start(): void; 10 | stop(): void; 11 | pause(): void; 12 | resume(): void; 13 | getAudioStream(): Transform; 14 | } 15 | 16 | export interface Options { 17 | endian?: 'big' | 'little'; 18 | bitwidth?: number | string; 19 | encoding?: 'signed-integer' | 'unsigned-integer'; 20 | rate?: number | string; 21 | channels?: number | string; 22 | device?: string; 23 | exitOnSilence?: number | string; 24 | debug?: boolean | string; 25 | fileType?: string; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/yargs.ts: -------------------------------------------------------------------------------- 1 | import __yargs, { CommandModule, Arguments, Options } from 'yargs'; 2 | const yargs = require('yargs') as typeof __yargs; 3 | 4 | export {yargs, Options}; 5 | 6 | export type Yargs = __yargs.Argv; 7 | 8 | export interface Argv extends Pick {} 9 | 10 | export interface Command extends Omit, 'handler' | 'builder'> { 11 | subCommands?: ReadonlyArray>; 12 | builder?: (yargs: Yargs) => void; 13 | handler?: (args: Args) => void | Promise; 14 | } 15 | export function Command(cmd: Command) { 16 | return { 17 | ...cmd, 18 | builder(yargs: Yargs) { 19 | cmd.builder && cmd.builder(yargs); 20 | if(!cmd.handler) yargs.demandCommand(); 21 | if(cmd.subCommands) { 22 | for(const subCommand of cmd.subCommands) { 23 | yargs.command(subCommand); 24 | } 25 | } 26 | return yargs; 27 | } 28 | } as CommandModule; 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/shell.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your PO-33-KO-backup-re workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | "shell boilerplate": { 19 | "prefix": "init", 20 | "body": [ 21 | "#!/usr/bin/env bash", 22 | "set -euo pipefail", 23 | "__dirname=\"\\$(dirname \"\\$( readlink -m \"\\${BASH_SOURCE[0]}\" )\" )\"", 24 | "$0" 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /docs/detecting-phase-shifts~: -------------------------------------------------------------------------------- 1 | Find the first peak's time and amplitude. 2 | This sets our clock and our general idea of large vs small amplitude. 3 | 4 | From there on out, we step forward in 1 cycle blocks, interpreting the block by detecting its phase. 5 | 6 | First half of the block / cycle stays in a single phase. 7 | 8 | Second half of the block / cycle is used for transition from one phase to another. 9 | 10 | Sample 3 points in the cycle: beginning, 1/4 cycle, and 1/2 cycle. 11 | 12 | Categorize each sample as: 13 | near zero, rising 14 | near zero, falling 15 | high 16 | low 17 | 18 | Assert that those 3 points consistently match a given phase. 19 | For example, `high` then `near zero, falling` then `low` is consistent. 20 | `high` then `near zero, falling` then `high` is *not* consistent. 21 | These inconsistencies should only occur during the *second* half of a cycle, 22 | when phase transitions occur. 23 | 24 | If any of the 3 samples are a zero-crossing, find the exact time of zero-crossing. 25 | Re-adjust our clock accordingly. This affects the window of the next block. 26 | Assert that window adjustment never exceeds a sane maximum; if it does, it might mean 27 | our assumptions are wrong. 28 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | import Path from 'path'; 2 | import fs from 'fs'; 3 | 4 | export function dataPath(name: string, filename?: string) { 5 | if(filename == null) { 6 | return Path.join(__dirname, '..', 'data', name); 7 | } else { 8 | return Path.join(__dirname, '..', 'data', name, filename); 9 | } 10 | } 11 | 12 | /** 13 | * emit an input FD one byte at a time 14 | * @deprecated 15 | */ 16 | export function* generateNumbers(input: number): Generator { 17 | const inputBuffer = Buffer.alloc(100); 18 | // const outputBuffer = new Buffer(100); 19 | while(true) { 20 | const length = fs.readSync(input, inputBuffer, 0, 100, null); 21 | if(length === 0) break; 22 | 23 | for(const number of new Int8Array(inputBuffer.slice(0, length))) { 24 | yield number; 25 | } 26 | } 27 | } 28 | 29 | export function minIndex(items: ReadonlyArray, getter: (v: T) => number) { 30 | let minIndex = 0; 31 | let minValue = Infinity; 32 | for(let i = 0, l = items.length; i < l; i++) { 33 | const item = items[i]; 34 | const v = getter(item); 35 | if(v < minValue) { 36 | minValue = v; 37 | minIndex = i; 38 | } 39 | } 40 | return minIndex; 41 | } -------------------------------------------------------------------------------- /src/cmds/record-backup.ts: -------------------------------------------------------------------------------- 1 | import { GlobalArgs } from "../cli"; 2 | import { Command } from "../yargs"; 3 | import { fs } from "../modules"; 4 | import { dataPath } from "../core"; 5 | /// 6 | /// 7 | import mic from 'mic'; 8 | import wav from 'wav'; 9 | 10 | interface Args extends GlobalArgs { 11 | 12 | } 13 | export const command = Command({ 14 | command: 'record', 15 | handler(args) { 16 | const {name, sampleRate} = args; 17 | fs.mkdirpSync(dataPath(name)); 18 | 19 | const bitDepth = 16; 20 | 21 | const micInstance = mic({ 22 | rate: `${ sampleRate }`, 23 | channels: '2', 24 | exitOnSilence: 20, 25 | debug: true, 26 | bitwidth: `${ bitDepth }`, 27 | }); 28 | 29 | const outputFile = new wav.FileWriter(dataPath(name, 'backup.wav'), { 30 | sampleRate, 31 | bitDepth, 32 | channels: 2 33 | }); 34 | 35 | micInstance.start(); 36 | 37 | const audio = micInstance.getAudioStream(); 38 | // audio.on('data', d => { 39 | // console.dir(d); 40 | // }); 41 | audio.on('silence', () => { 42 | micInstance.stop(); 43 | }); 44 | 45 | micInstance.getAudioStream().pipe(outputFile); 46 | 47 | console.log('Recording...'); 48 | } 49 | }); -------------------------------------------------------------------------------- /docs/detecting-phase-shifts.txt: -------------------------------------------------------------------------------- 1 | *NOTE: these are old notes. I tried this and it didn't work. Instead I used by old zero-crossing detector that spit out a bitstream, 2 | and I converted the bitstream into a stream of phases.* 3 | 4 | Find the first peak's time and amplitude. 5 | This sets our clock and our general idea of large vs small amplitude. 6 | 7 | From there on out, we step forward in 1 cycle blocks, interpreting the block by detecting its phase. 8 | 9 | First half of the block / cycle stays in a single phase. 10 | 11 | Second half of the block / cycle is used for transition from one phase to another. 12 | 13 | Sample 3 points in the cycle: beginning, 1/4 cycle, and 1/2 cycle. 14 | 15 | Categorize each sample as: 16 | near zero, rising 17 | near zero, falling 18 | high 19 | low 20 | 21 | Assert that those 3 points consistently match a given phase. 22 | For example, `high` then `near zero, falling` then `low` is consistent. 23 | `high` then `near zero, falling` then `high` is *not* consistent. 24 | These inconsistencies should only occur during the *second* half of a cycle, 25 | when phase transitions occur. 26 | 27 | If any of the 3 samples are a zero-crossing, find the exact time of zero-crossing. 28 | Re-adjust our clock accordingly. This affects the window of the next block. 29 | Assert that window adjustment never exceeds a sane maximum; if it does, it might mean 30 | our assumptions are wrong. 31 | -------------------------------------------------------------------------------- /src/cmds/raw-to-slice.ts: -------------------------------------------------------------------------------- 1 | import { Argv, Command } from "../yargs"; 2 | import { dataPath } from "../core"; 3 | import fs from 'fs'; 4 | import { GlobalArgs } from "../cli"; 5 | 6 | interface Args extends GlobalArgs { 7 | start: number; 8 | end: number; 9 | } 10 | export const command = Command({ 11 | command: 'dump-slice', 12 | describe: 'extract a slice of backup as CSV', 13 | builder(yargs) { 14 | return yargs.options({ 15 | start: { 16 | type: 'number', 17 | demand: true 18 | }, 19 | end: { 20 | type: 'number', 21 | demand: true 22 | } 23 | }); 24 | }, 25 | handler(argv) { 26 | const {name, sampleRate, start, end} = argv as any as {sampleRate: number, name: string, output: string, start: number, end: number}; 27 | const countBytes = end - start; 28 | function readSide(side: 'left' | 'right') { 29 | const sideRaw = fs.openSync(dataPath(name, `${side}.${sampleRate}.s8`), 'r'); 30 | const b = Buffer.alloc(countBytes); 31 | fs.readSync(sideRaw, b, 0, countBytes, start); 32 | fs.closeSync(sideRaw); 33 | return new Int8Array(b); 34 | } 35 | const left = readSide('left'); 36 | const right = readSide('right'); 37 | let acc = ''; 38 | for(let i = 0; i < countBytes; i++) { 39 | acc += `${left[i]}\t${right[i]}\n`; 40 | } 41 | fs.writeFileSync(dataPath(name, `slice.${start}.${end}.tsv`), acc); 42 | } 43 | }); -------------------------------------------------------------------------------- /bin/record-backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | __dirname="$(dirname "$( readlink -m "${BASH_SOURCE[0]}" )" )" 4 | __root="$(dirname "$__dirname" )" 5 | cd "$__root" 6 | 7 | . "$__root/config.env" 8 | 9 | backupName="$1" 10 | 11 | dir="$__root/data/$backupName" 12 | mkdir -p "$dir" 13 | 14 | if [[ -e "$dir/backup.wav" ]]; then 15 | echo "skipping recording because it already exists" 16 | else 17 | echo "Recording for $duration seconds, sample rate $sampleRate..." 18 | sox -t waveaudio -c 2 -r $sampleRate -b 8 "$audioDevice" "$( wslpath -w "$dir" )/backup.wav" trim 0 "$duration" 19 | #sox -t waveaudio -c 2 -r 48k -b 8 "$audioDevice" "$( wslpath -w "$dir" )/backup.wav" trim 0 "$duration" 20 | fi 21 | 22 | if [[ -e "data/$backupName/subtracted.wav" ]]; then 23 | echo "skipping recording because it already exists" 24 | else 25 | sox "data/$backupName/backup.wav" "data/$backupName/subtracted.wav" remix 1,2i 26 | fi 27 | 28 | for side in left right; do 29 | echo "Extracting $side channel..." 30 | case $side in 31 | left) channel=1 ;; 32 | right) channel=2 ;; 33 | esac 34 | file="$side.$sampleRate.s8" 35 | if [[ -e "$dir/$file" ]]; then 36 | echo "skipping $side because file already exists" 37 | else 38 | sox "$( wslpath -w "$dir" )/backup.wav" "$( wslpath -w "$dir" )/$file" remix $channel 39 | fi 40 | 41 | echo "Parsing $side channel..." 42 | outFile="$dir/$side.bits" 43 | if [[ -e "$outFile" ]]; then 44 | echo "Skipping parsing to $outFile because already exists" 45 | else 46 | "$__root/parse-raw.ts" --sampleRate $sampleRate --input "$dir/left.$sampleRate.s8" --output "$outFile" 47 | fi 48 | done 49 | 50 | echo "Done" -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ts-node-to 2 | import { yargs, Command } from './yargs'; 3 | import {command as parseCommand} from './cmds/parse'; 4 | import {command as parseDpskLegacyCommand} from './cmds/parse-dpsk-legacy'; 5 | import {command as rawToBitsCommand} from './cmds/raw-to-bits'; 6 | import {command as rawToSliceCommand} from './cmds/raw-to-slice'; 7 | import {command as replayCommand} from './cmds/replay-backup'; 8 | import {command as recordCommand} from './cmds/record-backup'; 9 | import { Options, Argv } from './yargs'; 10 | 11 | function O(o: Options) { 12 | return o; 13 | } 14 | export const globalOptions = { 15 | sampleRate: O({ 16 | describe: ` 17 | sampleRate for recording or playback. 18 | Don\'t use lower than 44100. 19 | 96000 is a nice sample rate if you want the waveform to look pretty. 20 | `, 21 | type: 'number', 22 | default: 44100 23 | }), 24 | name: O({ 25 | describe: `name of backup. Should be the name of a subdirectory of ./data`, 26 | type: 'string', 27 | demand: true 28 | }) 29 | }; 30 | export interface GlobalArgs extends Argv { 31 | sampleRate: number; 32 | name: string; 33 | } 34 | 35 | const rootCommand = Command({ 36 | command: '$0', 37 | describe: 'Parse and replay PO-33 backup audio', 38 | subCommands: [ 39 | parseCommand, 40 | parseDpskLegacyCommand, 41 | rawToBitsCommand, 42 | rawToSliceCommand, 43 | replayCommand, 44 | recordCommand 45 | ] 46 | }); 47 | 48 | yargs 49 | .options(globalOptions) 50 | .command(rootCommand) 51 | .completion() 52 | .strict() 53 | .parse(); 54 | -------------------------------------------------------------------------------- /wav-file.js: -------------------------------------------------------------------------------- 1 | require('source-map-support').install(); 2 | const {default: __WaveFile} = require('wavefile'); 3 | const WaveFile = /** @type {typeof __WaveFile} */(/** @type {any} */(require('wavefile'))); 4 | const fs = require('fs'); 5 | const util = require('util'); 6 | 7 | function main() { 8 | const [, , inputPath, outputPath] = process.argv; 9 | const wav = readWavFile(inputPath); 10 | let i = 0; 11 | const bitDepth = +wav.bitDepth; 12 | const {numChannels, chunkSize, sampleRate} = wav.fmt; 13 | console.dir({bitDepth, numChannels, chunkSize, sampleRate}); 14 | // while(true) { 15 | // console.log(wav.getSample(i++)); 16 | // } 17 | } 18 | 19 | function readWavFile(inputPath) { 20 | const wavData = fs.readFileSync(inputPath); 21 | // Load a wav file buffer as a WaveFile object 22 | let wav = new WaveFile(wavData); 23 | return wav; 24 | } 25 | 26 | function createWavFile(outputPath) { 27 | const wav = new WaveFile(); 28 | const H = 255; 29 | const L = 0; 30 | const sampleCount = 10; 31 | const channels = 2; 32 | const channelContents = [H, L]; 33 | 34 | const sampleArray = new Proxy([], { 35 | get(target, index) { 36 | if(index === 'length') { 37 | return sampleCount * channels; 38 | } else { 39 | const parsed = +/**@type {string} */(index); 40 | if(!isNaN(parsed)) { 41 | return channelContents[parsed % channels]; 42 | } else { 43 | throw new Error(`not implemented: getting ${ util.inspect(index) }`); 44 | } 45 | } 46 | } 47 | }); 48 | 49 | wav.fromScratch(2, 96000, '8', sampleArray); 50 | fs.writeFileSync(outputPath, wav.toBuffer()); 51 | } 52 | 53 | main(); -------------------------------------------------------------------------------- /src/cmds/raw-to-bits.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "../yargs"; 2 | import { fs } from "../modules"; 3 | import { dataPath, generateNumbers } from "../core"; 4 | import { bitSpitter } from "./parse"; 5 | import { GlobalArgs } from "../cli"; 6 | 7 | interface Args extends GlobalArgs { 8 | sampleRate: number; 9 | name: string; 10 | } 11 | export const command = Command({ 12 | command: 'raw-to-bits', 13 | describe: 'Parse raw to bits', 14 | builder(yargs) { 15 | return yargs.options({ 16 | sampleRate: { 17 | alias: 'r', 18 | type: 'number', 19 | demand: true 20 | }, 21 | name: { 22 | type: 'string', 23 | demand: true 24 | }, 25 | }); 26 | }, 27 | handler(argv) { 28 | main(argv as any); 29 | } 30 | }); 31 | 32 | /** 33 | * Parse a raw file exported from Audacity. 34 | * Assumes raw file contains signed 8bit PCM, 48000Hz 35 | */ 36 | function main(opts: {name: string, sampleRate: number}) { 37 | const {name, sampleRate} = opts; 38 | const side = 'left'; 39 | 40 | console.log(`Opening input and output files`); 41 | const output = fs.openSync(dataPath(name, `${ side }.${ sampleRate }.bits`), 'w'); 42 | const input = fs.openSync(dataPath(name, `${ side }.${ sampleRate }.s8`), 'r'); 43 | 44 | const bitStream = bitSpitter({input: generateNumbers(input), sampleRate}); 45 | console.log(`Iterating bitstream`); 46 | let lineLength = 0; 47 | for(const bit of bitStream) { 48 | fs.writeSync(output, `${bit}`); 49 | lineLength++; 50 | if(lineLength === 80) { 51 | fs.writeSync(output, '\n'); 52 | lineLength = 0; 53 | } 54 | } 55 | console.log(`Closing files`); 56 | fs.closeSync(input); 57 | fs.closeSync(output); 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interpreting PO-33 KO backups on PC 2 | 3 | Backups need to be recorded stereo 44KHz, meaning a very big file. 4 | What if we could make software to parse these backups and save them 5 | as plain files. (much smaller) Then play as audio when restoring 6 | to the device. 7 | 8 | https://www.reddit.com/r/pocketoperators/comments/d1hd9o/deconstructing_po33_backup_format/ 9 | 10 | --- 11 | 12 | Use a bash shell. Dot source `. ./shell` 13 | 14 | `yarn install` You'll need node and yarn. Use npm if you really want. 15 | 16 | Edit config.env to set the name of your recording device. (*) 17 | 18 | `record-backup ` (*) 19 | 20 | *\* This assumes you're running on Windows & WSL. If on another platform, modify 21 | `./bin/sox` or `./bin/record-backup` to use either Linux sox binaries or an 22 | alternative audio recorder.* 23 | 24 | --- 25 | 26 | Staying organized with backup files: 27 | 28 | ``` 29 | data//backup.wav 30 | data//left.raw 31 | data//right.raw 32 | data//left.txt 33 | data//right.txt 34 | ``` 35 | 36 | --- 37 | 38 | Backup is some sorta PSK. 39 | 40 | 7800Hz oscillator. 41 | 42 | Phases are either 0, 90deg, 180deg, or 270deg. 43 | First half of each cycle stays at a given phase. 44 | Second half is used for transition from one phase to the next. 45 | 46 | First cycle begins at the first high peak of input. 47 | 48 | My hacky code can parse this by: 49 | 50 | * a) converting the audio wave to a bitstream, where each bit is 1/4 cycle, above x axis is a 1, below is 0. 51 | * b) slicing the bitstream into 4-bit (one cycle) chunks. The first 2 bits indicate which of the 4 phases. Second 2 bits can be ignored because they are phase transition. 52 | 53 | 64 | 65 | --- 66 | 67 | TE says samples are "8-bit, µ-law companded, 23437.5 Hz samplerate" 68 | 69 | Magnus Lidstrom created PO-32 and PO-35 70 | Jonatan Blomster coded PO-33 71 | 72 | Link dump: 73 | 74 | * https://soniccharge.com/forum/topic/1060-po-35-secret-powers 75 | * https://www.youtube.com/watch?v=OW5DqR_c5Hw 76 | * https://www.reddit.com/r/pocketoperators/comments/7ybjk2/po33_ko_secret_synth/ 77 | * https://www.youtube.com/watch?v=NmJoW-tiBSM&t=400s 78 | * https://soniccharge.com/forum/topic/1067-what-are-the-pos-based-off 79 | * https://www.youtube.com/watch?v=GgIkX_EDFLA 80 | * https://www.youtube.com/watch?v=2E5V8f7LzZU 81 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src" 4 | ], 5 | "compilerOptions": { 6 | /* Basic Options */ 7 | // "incremental": true, /* Enable incremental compilation */ 8 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 9 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 10 | // "lib": [], /* Specify library files to be included in the compilation. */ 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | // "outDir": "./", /* Redirect output structure to the directory. */ 19 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/cmds/replay-backup.ts: -------------------------------------------------------------------------------- 1 | import Speaker, { Options } from '@cspotcode/speaker'; 2 | import fs from "fs"; 3 | import { numbersToArrayBuffers, arrayBuffersToNodeBuffers, buffersToFile, fileToBuffers, typedArraysToNumbers, buffersToUint8Arrays, generatorShift, generatorMap } from "../generator-utils"; 4 | import { Readable } from "stream"; 5 | import { dataPath } from "../core"; 6 | import { Phase, carrierHz as hz } from '../types-and-constants'; 7 | import { Command } from '../yargs'; 8 | import { GlobalArgs } from '../cli'; 9 | import { FileWriter } from 'wav'; 10 | 11 | const PI = Math.PI; 12 | 13 | interface Args extends GlobalArgs { 14 | toFile?: boolean; 15 | toSpeaker?: boolean; 16 | } 17 | export const command = Command({ 18 | command: 'replay', 19 | describe: 'Take a parsed backup and replay it by re-generating the audio.', 20 | builder(yargs) { 21 | yargs.options({ 22 | toFile: { 23 | describe: 'Emit reconstituted..wav', 24 | type: 'boolean', 25 | default: true 26 | }, 27 | toSpeaker: { 28 | describe: 'Play to the speakers.', 29 | type: 'boolean' 30 | } 31 | }); 32 | }, 33 | handler(args) { 34 | const {name, sampleRate, toSpeaker} = args; 35 | let {toFile} = args; 36 | if(toSpeaker) toFile = false; 37 | if(toFile) { 38 | console.error('toFile output is currently BROKEN!'); 39 | } 40 | 41 | const signed = !!toSpeaker; 42 | const channels = 2 as 1 | 2; 43 | const bitDepth = 16 as 8 | 16; 44 | const AB = signed ? ( 45 | bitDepth === 8 ? Int8Array : Int16Array 46 | ) : ( 47 | bitDepth === 8 ? Uint8Array : Uint16Array 48 | ); 49 | const amplitude = 1 << (bitDepth - 2); 50 | 51 | const audioGenerator = channels === 2 52 | ? createInterleavedStream() 53 | : createChannelStream('left'); 54 | 55 | const bias = 1<<(bitDepth - 1); 56 | 57 | const generator = arrayBuffersToNodeBuffers( 58 | numbersToArrayBuffers( 59 | signed ? audioGenerator : generatorMap( 60 | audioGenerator, 61 | n => n + bias // bias to convert signed to unsigned 62 | ), 63 | new AB(100) 64 | ) 65 | ); 66 | const stream = Readable.from( 67 | generator 68 | ); 69 | 70 | if(toSpeaker) { 71 | const speaker = new Speaker({ 72 | bitDepth, 73 | channels, 74 | sampleRate, 75 | signed: true 76 | } as Options); 77 | stream.pipe(speaker); 78 | } else if(toFile) { 79 | const outputFile = new FileWriter(dataPath(name, 'reconstituted.wav'), { 80 | sampleRate, 81 | bitDepth, 82 | channels, 83 | }); 84 | stream.pipe(outputFile); 85 | } 86 | // const output = fs.openSync(dataPath(name, `${side}.reconstituted.${sampleRate}.s16`), 'w'); 87 | // buffersToFile(generator, output); 88 | 89 | function* phasesToAudioSamples(opts: {input: Generator, amplitude: number}) { 90 | const {amplitude, input} = opts; 91 | const samplesPerCycle = sampleRate / hz; 92 | const sampleToRadians = 2 * PI / samplesPerCycle; 93 | 94 | let sampleIndex = 0; 95 | let radiansStart = 0; 96 | let thisPhase = input.next().value; 97 | while(true) { 98 | const nextPhase = input.next().value; 99 | if(!nextPhase) return; 100 | while(true) { 101 | const radians = sampleIndex * sampleToRadians; 102 | if(radians > radiansStart + 2 * PI) break; 103 | 104 | const mixValue = getEnvelopeValue(radians - radiansStart); 105 | const thisPhaseValue = getOscillatorValue(thisPhase, radians); 106 | const nextPhaseValue = getOscillatorValue(nextPhase, radians); 107 | const sample = mix(mixValue, thisPhaseValue, nextPhaseValue); 108 | yield Math.round(sample * amplitude); 109 | 110 | sampleIndex++; 111 | } 112 | radiansStart += 2 * PI; 113 | 114 | thisPhase = nextPhase; 115 | } 116 | 117 | function getEnvelopeValue(radians: number): number { 118 | const start = 1.2 * PI; 119 | const end = 1.8 * PI; 120 | if(radians < start) { 121 | return 0; 122 | } 123 | if(radians > end) { 124 | return 1; 125 | } 126 | return (radians - start) / (end - start); 127 | } 128 | 129 | function getOscillatorValue(phase: Phase, radians: number) { 130 | switch(phase) { 131 | case 'A': 132 | return Math.sin(radians); 133 | case 'B': 134 | return Math.cos(radians); 135 | case 'C': 136 | return -Math.sin(radians); 137 | case 'D': 138 | return -Math.cos(radians); 139 | default: throw 'nope'; 140 | } 141 | } 142 | 143 | function mix( 144 | /** from 0 (all valueA) to 1 (all valueB) or anywhere in-between */ 145 | mixAmount: number, 146 | valueA: number, 147 | valueB: number 148 | ) { 149 | return valueA * (1 - mixAmount) + valueB * mixAmount; 150 | } 151 | } 152 | 153 | function* createInterleavedStream() { 154 | const left = createChannelStream('left'); 155 | const right = createChannelStream('right'); 156 | while(true) { 157 | const l = left.next(); 158 | yield l.done ? 0 : l.value; 159 | const r = right.next(); 160 | yield r.done ? 0 : r.value; 161 | if(r.done && l.done) { 162 | break; 163 | } 164 | } 165 | } 166 | 167 | function createChannelStream(side: 'left' | 'right'): Generator { 168 | const inputPhases = fs.openSync(dataPath(name, `${side}.phases`), 'r'); 169 | function *phaseSpitter() { 170 | for(const item of typedArraysToNumbers(buffersToUint8Arrays(fileToBuffers(inputPhases)))) { 171 | const str = String.fromCharCode(item); 172 | if('ABCD'.includes(str)) { 173 | yield str as Phase; 174 | } 175 | } 176 | } 177 | const audioGenerator = phasesToAudioSamples({ 178 | amplitude, 179 | input: phaseSpitter() 180 | }); 181 | return audioGenerator; 182 | } 183 | } 184 | }); 185 | -------------------------------------------------------------------------------- /src/cmds/parse.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "../yargs"; 2 | import fs from 'fs'; 3 | import { dataPath, generateNumbers } from "../core"; 4 | import { Bit, ZeroCrossing } from "../types-and-constants"; 5 | import { noReturn, arraysEqual, concatGenerators, arrayToGenerator, pullXValuesFromGenerator } from "../generator-utils"; 6 | import { GlobalArgs } from "../cli"; 7 | 8 | interface Args extends GlobalArgs {} 9 | export const command = Command({ 10 | command: 'parse', 11 | describe: 'Parse a backup into a phase dump', 12 | handler(args) { 13 | parsePhasesViaBits(args as unknown as {name: string, sampleRate: number}); 14 | } 15 | }); 16 | 17 | /** 18 | * Parse a raw file exported from Audacity. 19 | * Assumes raw file contains signed 8bit PCM, 48000Hz 20 | */ 21 | function parsePhasesViaBits(opts: {name: string, sampleRate: number}) { 22 | const {name, sampleRate} = opts; 23 | doIt('left'); 24 | doIt('right'); 25 | function doIt(side: 'left' | 'right') { 26 | console.log(`Opening input and output files`); 27 | const output = fs.openSync(dataPath(name, `${ side }.phases`), 'w'); 28 | const input = fs.openSync(dataPath(name, `${ side }.${ sampleRate }.s8`), 'r'); 29 | 30 | const bitStream = bitSpitter({input: generateNumbers(input), sampleRate}); 31 | const phaseStream = bitsToPhases({bitStream}); 32 | 33 | let lineLength = 0; 34 | for(const phase of phaseStream) { 35 | fs.writeSync(output, `${phase}`); 36 | lineLength++; 37 | if(lineLength === 80) { 38 | fs.writeSync(output, '\n'); 39 | lineLength = 0; 40 | } 41 | } 42 | console.log(`Closing files`); 43 | fs.closeSync(input); 44 | fs.closeSync(output); 45 | } 46 | } 47 | 48 | function* bitsToPhases(opts: {bitStream: Generator}) { 49 | const {bitStream} = opts; 50 | const bits = new Array(); 51 | // detect the first 11 52 | for(const bit of noReturn(bitStream)) { 53 | bits.push(bit); 54 | if(arraysEqual(bits.slice(-2), [1, 1])) { 55 | break; 56 | } 57 | } 58 | 59 | const rest = noReturn(concatGenerators(arrayToGenerator([1]), bitStream)); 60 | while(true) { 61 | const bits = pullXValuesFromGenerator(rest, 2); 62 | pullXValuesFromGenerator(rest, 2); 63 | if(bits.length < 2) break; 64 | const b = ([ 65 | [[1, 1], 'A'], 66 | [[1, 0], 'B'], 67 | [[0, 0], 'C'], 68 | [[0, 1], 'D'] 69 | ] as const).find(([signature, phase]) => arraysEqual(bits, signature)); 70 | if(!b) console.dir({Error: true, bits, b}); 71 | yield b ? b[1] : '-'; 72 | } 73 | } 74 | 75 | /** 76 | * Take a stream of audio samples and spit out a stream of high/low bits based on zero-crossings of the audio 77 | */ 78 | export function bitSpitter(opts: {input: Generator, sampleRate: number}) { 79 | const {sampleRate, input} = opts; 80 | const poEncodingSampleRateHz = 7800 * 4; 81 | const samplesPerBit = sampleRate / poEncodingSampleRateHz; 82 | const maxUncertainty = 1/4; 83 | const bitStream = zeroCrossingsToBits({ 84 | zeroCrossings: generateZeroCrossings( 85 | input 86 | ), 87 | maxUncertainty, 88 | samplesPerBit 89 | }); 90 | return bitStream; 91 | } 92 | 93 | function* zeroCrossingsToBits(opts: { 94 | zeroCrossings: Generator, 95 | maxUncertainty: number, 96 | samplesPerBit: number 97 | }): Generator { 98 | const {maxUncertainty, samplesPerBit, zeroCrossings} = opts; 99 | let skip = 0; 100 | for(const {side, timestamp, delta} of zeroCrossings) { 101 | const numberOfBitsFractional = delta / samplesPerBit; 102 | const numberOfBits = Math.round(numberOfBitsFractional); 103 | // Uncertainty of 0.5 means it's smack in the middle, between two possible counts of bits. 104 | // (For example, the delta time is halfway between the time it takes for 2 bits and for 3 bits) 105 | // That is bad because it means we don't have any idea what number of bits was being encoded. 106 | // Or it means our assumptions about the bit-rate or encoding format are totally wrong. 107 | const uncertainty = (numberOfBits - numberOfBitsFractional) / samplesPerBit; 108 | if(Math.abs(uncertainty) > maxUncertainty) 109 | console.log('High uncertainty: ' + uncertainty); 110 | const bit = side === 1 ? 0 : 1; 111 | if(skip) { 112 | console.dir({side, crossingTimestamp: timestamp, crossingDelta: delta}); 113 | skip--; 114 | continue; 115 | } 116 | for(let i = 0; i < numberOfBits; i++) { 117 | yield bit; 118 | } 119 | } 120 | } 121 | 122 | function* generateZeroCrossings(generator: Generator): Generator { 123 | const threshold = 10; 124 | let i = -1; 125 | let negativeIndex = 0; 126 | let negativeValue = 0; 127 | let positiveIndex = 0; 128 | let positiveValue = 0; 129 | let lastSeenAboveAxis = true; 130 | let lastCrossing = 0; 131 | let f = 2; 132 | function computeCrossing({ 133 | beforeIndex, beforeValue, afterIndex, afterValue 134 | }: {beforeIndex: number, beforeValue: number, afterIndex: number, afterValue: number}) { 135 | // HACK if these are the first crossings 136 | if(beforeIndex === 0) return afterIndex - 0.5; 137 | const percentageOfTimeBeforeCrossing = beforeValue / (beforeValue - afterValue); 138 | const crossingTime = beforeIndex + percentageOfTimeBeforeCrossing * (afterIndex - beforeIndex); 139 | // if(Math.abs(beforeIndex - afterIndex) > 2) { 140 | // if(f) { 141 | // f--; 142 | // console.dir({ 143 | // beforeIndex, beforeValue, 144 | // afterIndex, afterValue, 145 | // percentageOfTimeBeforeCrossing, 146 | // crossingTime 147 | // }); 148 | // } 149 | return crossingTime; 150 | } 151 | for(const number of generator) { 152 | i++; 153 | // Skip values too close to 0 154 | if(Math.abs(number) < threshold) continue; 155 | if(number < 0) { 156 | // is negative number 157 | negativeIndex = i; 158 | negativeValue = number; 159 | // if is transition to negative 160 | if(lastSeenAboveAxis) { 161 | lastSeenAboveAxis = false; 162 | const crossingTime = computeCrossing({ 163 | beforeIndex: positiveIndex, 164 | beforeValue: positiveValue, 165 | afterIndex: negativeIndex, 166 | afterValue: negativeValue 167 | }); 168 | yield {side: -1, timestamp: crossingTime, delta: lastCrossing === 0 ? 0 : crossingTime - lastCrossing}; 169 | lastCrossing = crossingTime; 170 | } 171 | } else { 172 | // is positive number 173 | positiveIndex = i; 174 | positiveValue = number; 175 | // if is transition to positive 176 | if(!lastSeenAboveAxis) { 177 | lastSeenAboveAxis = true; 178 | const crossingTime = computeCrossing({ 179 | beforeIndex: negativeIndex, 180 | beforeValue: negativeValue, 181 | afterIndex: positiveIndex, 182 | afterValue: positiveValue 183 | }); 184 | yield {side: 1, timestamp: crossingTime, delta: lastCrossing === 0 ? 0 : crossingTime - lastCrossing}; 185 | lastCrossing = crossingTime; 186 | } 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /src/generator-utils.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from "stream"; 2 | import fs from "fs"; 3 | import assert from "assert"; 4 | 5 | /** 6 | * Concat 2 generators into one. 7 | * Return value of generator1 is swallowed. 8 | */ 9 | export function* concatGenerators( 10 | generator1: Generator, 11 | generator2: Generator 12 | ): Generator { 13 | let input: ReceivesFromYield = undefined as any; 14 | let first = true; 15 | while(true) { 16 | const nextResult = first ? generator1.next() : generator1.next(input); 17 | first = false; 18 | if(nextResult.done) { 19 | break; 20 | } else { 21 | input = yield nextResult.value; 22 | } 23 | } 24 | first = true; 25 | while(true) { 26 | const nextResult = first ? generator2.next() : generator2.next(input); 27 | first = false; 28 | if(nextResult.done) { 29 | return nextResult.value; 30 | } else { 31 | input = yield nextResult.value; 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * Wrap a generator so that the wrapped generator is protected from termination. 38 | * The returned generator may be terminated, but the wrapped generator will *not* be told 39 | * to terminate. Useful when you want to pipe data out of a generator in a for() loop, 40 | * break the loop, and later resume reading from the generator. 41 | */ 42 | export function* noReturn(generator: Generator): Generator { 43 | let v: V = undefined as any; 44 | let errorToThrow = undefined; 45 | let shouldThrow = false; 46 | while(true) { 47 | let nextResult = shouldThrow ? generator.throw(errorToThrow) : generator.next(v); 48 | // Errors thrown from the wrapped generator will not be caught and will be thrown by us. 49 | // This is what we want. 50 | 51 | if(nextResult.done) return nextResult.value; 52 | try { 53 | v = yield nextResult.value; 54 | } catch(e) { 55 | errorToThrow = e; 56 | shouldThrow = true; 57 | } 58 | } 59 | } 60 | 61 | export function dupeGenerator_TODO(generator: Generator) { 62 | // As soon as next() is called on *one* of the outputs, it's passed to the source. 63 | // If next() is called again 64 | } 65 | 66 | export function pullXValuesFromGenerator(generator: Generator, count: number): Array { 67 | const items: T[] = []; 68 | for(const item of noReturn(generator)) { 69 | items.push(item); 70 | if(items.length >= count) break; 71 | } 72 | return items; 73 | } 74 | 75 | export function generatorShift(generator: Generator): T | undefined { 76 | const n = generator.next(); 77 | if(n.done) return; 78 | return n.value; 79 | } 80 | 81 | export function arraysEqual(a: ReadonlyArray, b: ReadonlyArray) { 82 | return a.length === b.length && a.every((v, i) => v === b[i]); 83 | } 84 | 85 | export function* arrayToGenerator(array: ReadonlyArray) { 86 | for(const i of array) { 87 | yield i; 88 | } 89 | } 90 | 91 | export function* pipeGenerators( 92 | source: Generator, 93 | receiver: Generator 94 | ): Generator { 95 | for(const item of source) { 96 | const r = receiver.next(item); 97 | if(r.done) return r.value; 98 | } 99 | while(true) { 100 | const r = receiver.next(undefined); 101 | if(r.done) return r.value; 102 | yield r.value; 103 | } 104 | } 105 | 106 | type TypedArrayLike = { 107 | length: number; 108 | [index: number]: number; 109 | buffer: ArrayBufferLike; 110 | [Symbol.iterator](): Iterator; 111 | } 112 | 113 | /** Reuses the array buffer, so the receiver must copy it! */ 114 | export function* numbersToArrayBuffers(stream: Generator, typedArray: TypedArrayLike): Generator { 115 | // TODO can we reuse the buffer? When written to a stream, does the stream make a copy of the buffer? 116 | let indexToWrite = 0; 117 | const length = typedArray.length; 118 | for(const item of stream) { 119 | typedArray[indexToWrite++] = item; 120 | if(indexToWrite === length) { 121 | yield typedArray.buffer; 122 | indexToWrite = 0; 123 | } 124 | } 125 | yield typedArray.buffer.slice(0, indexToWrite); 126 | } 127 | 128 | export function* arrayBuffersToNodeBuffers(input: Generator) { 129 | for(const ab of input) { 130 | yield Buffer.from(ab); 131 | } 132 | } 133 | 134 | export function generatorToStream(input: Generator): Readable { 135 | return Readable.from(input); 136 | } 137 | export async function* streamToGenerator(input: Readable): AsyncGenerator { 138 | for await(const item of input) { 139 | yield item; 140 | } 141 | } 142 | 143 | /** Generator that emits a file synchronously, closing it upon termination. */ 144 | export function* fileToBuffers(input: number, bufferSize = 100): Generator { 145 | try { 146 | const buffer = Buffer.alloc(bufferSize); 147 | while(true) { 148 | const amtRead = fs.readSync(input, buffer, 0, 100, null); 149 | if(amtRead === 0) return; 150 | yield Buffer.from(buffer.slice(0, amtRead)); 151 | } 152 | } finally { 153 | fs.closeSync(input); 154 | } 155 | } 156 | /** Generator that emits a file synchronously, closing it upon termination. */ 157 | export function buffersToFile(input: Generator, output: number, bufferSize = 100) { 158 | try { 159 | for(const b of input) { 160 | fs.writeSync(output, b); 161 | } 162 | } finally { 163 | fs.closeSync(output); 164 | } 165 | } 166 | 167 | export function buffersToInt16Arrays(input: Generator, sampleSize = 100): Generator { 168 | return impl(input, sampleSize, Int16Array) as any; 169 | } 170 | export function buffersToInt8Arrays(input: Generator, sampleSize = 100): Generator { 171 | return impl(input, sampleSize, Int8Array) as any; 172 | } 173 | export function buffersToUint8Arrays(input: Generator, sampleSize = 100): Generator { 174 | return impl(input, sampleSize, Uint8Array) as any; 175 | } 176 | interface TypedArrayCtor { 177 | BYTES_PER_ELEMENT: number; 178 | new(len: number | ArrayBuffer): TypedArrayLike & { 179 | byteLength: number 180 | } 181 | } 182 | function* impl(input: Generator, sampleSize: number, Ctor: TypedArrayCtor) { 183 | const bytesPerSample = Ctor.BYTES_PER_ELEMENT; 184 | let next = new Ctor(sampleSize); 185 | const {byteLength} = next; 186 | let nextAsUint8 = new Uint8Array(next.buffer); 187 | let buffers: Buffer[] = []; 188 | let bufferedBytes = 0; 189 | 190 | for(const buffer of input) { 191 | buffers.push(Buffer.from(buffer)); 192 | bufferedBytes += buffer.length; 193 | while(bufferedBytes >= byteLength) { 194 | const concat = Buffer.concat(buffers); 195 | concat.copy(nextAsUint8, 0, 0, byteLength); 196 | yield next; 197 | next = new Ctor(sampleSize); 198 | nextAsUint8 = new Uint8Array(next.buffer); 199 | buffers = [concat.slice(byteLength)]; 200 | bufferedBytes = buffers[0].length; 201 | } 202 | } 203 | // Send the remaining buffer 204 | assert(bufferedBytes <= byteLength); 205 | const bytesToSend = bufferedBytes - (bufferedBytes % bytesPerSample); 206 | const concat = Buffer.concat(buffers); 207 | const ab = new ArrayBuffer(bytesToSend); 208 | concat.copy(new Uint8Array(ab)); 209 | yield new Ctor(ab); 210 | } 211 | 212 | export function* typedArraysToNumbers(input: Generator): Generator { 213 | for(const ta of input) { 214 | for(const n of ta) { 215 | yield n; 216 | } 217 | } 218 | } 219 | 220 | export function* generatorMap(generator: Generator, cb: (t: T) => V): Generator { 221 | for(const item of generator) { 222 | yield cb(item); 223 | } 224 | } -------------------------------------------------------------------------------- /src/cmds/parse-dpsk-legacy.ts: -------------------------------------------------------------------------------- 1 | import { Command, Argv } from "../yargs"; 2 | import assert from 'assert'; 3 | import fs from 'fs'; 4 | import { dataPath, generateNumbers, minIndex } from '../core'; 5 | import { concatGenerators, arrayToGenerator, noReturn, pullXValuesFromGenerator } from '../generator-utils'; 6 | import { Phase, Phases } from "../types-and-constants"; 7 | import { GlobalArgs } from "../cli"; 8 | 9 | interface Args extends GlobalArgs { 10 | name: string; 11 | sampleRate: number; 12 | } 13 | export const command = Command({ 14 | command: 'parse-dpsk-legacy', 15 | describe: 'DEPRECATED old, failed method of parsing dpsk', 16 | builder(yargs) { 17 | return yargs.options({ 18 | name: {type: 'string', demand: true}, 19 | sampleRate: {type: 'number', demand: true} 20 | }); 21 | }, 22 | handler(args) { 23 | parseDpskHandler(args as any as {name: string, sampleRate: string}); 24 | } 25 | }); 26 | 27 | const debug = { 28 | log(this: unknown, ...a: any[]) {}, 29 | dir(this: unknown, a: any) {} 30 | }; 31 | 32 | interface ParseDpskOptions { 33 | name: string; 34 | sampleRate: string; 35 | } 36 | export function parseDpskHandler(args: ParseDpskOptions) { 37 | const {name, sampleRate} = args; 38 | const side = 'left'; 39 | const input = fs.openSync(dataPath(name, `${ side }.${ sampleRate }.s8`), 'r'); 40 | const inputStream = generateNumbers(input); 41 | const {indexOfPeak, itemsRead} = detectFirstPeak(inputStream); 42 | debug.dir({indexOfPeak, itemsRead}); 43 | const amplitudeOfFirstPeak = itemsRead[indexOfPeak]; 44 | const highAmplitude = amplitudeOfFirstPeak * 0.7; 45 | const lowAmplitude = amplitudeOfFirstPeak * 0.5; 46 | const inputStream2 = concatGenerators(arrayToGenerator(itemsRead.slice(indexOfPeak)), inputStream); 47 | 48 | for(const phase of generatePhases({ 49 | generator: inputStream2, 50 | samplesPerPhase: 12, 51 | highAmplitude, 52 | lowAmplitude 53 | })) { 54 | console.log(phase); 55 | } 56 | } 57 | 58 | function detectFirstPeak(generator: Generator, threshold: number = 50): { 59 | indexOfPeak: number; 60 | itemsRead: Array; 61 | } { 62 | const items = new Array(); 63 | let max: number = 0; 64 | for(const item of noReturn(generator)) { 65 | items.push(item); 66 | if(item < threshold) continue; 67 | max = item; 68 | break; 69 | } 70 | for(const item of noReturn(generator)) { 71 | items.push(item); 72 | if(item < max) break; 73 | max = item; 74 | } 75 | return { 76 | indexOfPeak: items.length - 2, // the previous sample is the peak 77 | itemsRead: items 78 | }; 79 | } 80 | 81 | const categoryOrder: Array = ['ZeroRising', 'High', "ZeroDropping", 'Low', 'ZeroRising', "High"]; 82 | 83 | function* generatePhases(opts: { 84 | generator: Generator; 85 | samplesPerPhase: number; 86 | highAmplitude: number; 87 | lowAmplitude: number; 88 | }): Generator { 89 | const {generator, samplesPerPhase, lowAmplitude, highAmplitude} = opts; 90 | debug.dir({lowAmplitude, highAmplitude}); 91 | let offset = 0; 92 | let lastItems: Array = []; 93 | let shouldShiftClock = 0; 94 | while(true) { 95 | const items = pullXValuesFromGenerator(generator, Math.ceil(samplesPerPhase) + shouldShiftClock); 96 | if(shouldShiftClock > 0) { 97 | items.splice(0, shouldShiftClock); 98 | } else if(shouldShiftClock < 0) { 99 | items.splice(0, 0, ...lastItems.slice(shouldShiftClock)); 100 | } 101 | const startSample = getSample(items, 0 + offset); 102 | const quarterSample = getSample(items, samplesPerPhase / 4 + offset); 103 | const halfSample = getSample(items, samplesPerPhase / 2 + offset); 104 | debug.dir({ 105 | items, 106 | startSample, 107 | quarterSample, 108 | halfSample 109 | }); 110 | const startSampleType = categorizeSample({sample: startSample, highAmplitude, lowAmplitude, samplesPerPhase}); 111 | const quarterSampleType = categorizeSample({sample: quarterSample, highAmplitude, lowAmplitude, samplesPerPhase}); 112 | const halfSampleType = categorizeSample({sample: halfSample, highAmplitude, lowAmplitude, samplesPerPhase}); 113 | debug.dir({startSampleType, quarterSampleType, halfSampleType}); 114 | FindPhase: 115 | { 116 | for(let i = 0; i < 4; i++) { 117 | if( 118 | startSampleType === categoryOrder[i] && 119 | quarterSampleType === categoryOrder[i + 1] && 120 | halfSampleType === categoryOrder[i + 2] 121 | ) { 122 | yield Phases[i]; 123 | break FindPhase; 124 | } 125 | } 126 | console.dir({ 127 | startSample, startSampleType, quarterSample, quarterSampleType, halfSample, halfSampleType, 128 | items, 129 | }); 130 | throw 'Samples categories do not match a consistent phase.'; 131 | } 132 | 133 | // Re-adjust our clock based on the first zero-crossing 134 | for(let i = 0; i < items.length; i++) { 135 | if(Math.sign(items[i]) != Math.sign(items[i + 1])) { 136 | // i is the index of a zero crossing 137 | const zeroCrossing = i + Math.abs(items[i] / (items[i] - items[i + 1])); 138 | const a = [0, samplesPerPhase / 4, samplesPerPhase / 2]; 139 | const b = a.map(aVal => { 140 | return {phase: aVal, distance: zeroCrossing - aVal}; 141 | }); 142 | const bestMatch = b[minIndex(b, v => Math.abs(v.distance))]; 143 | 144 | // we know i should be at the phase indicated by bestMatch 145 | if(bestMatch.distance < -0.5) { 146 | // we must shift clock backwards 147 | shouldShiftClock = -1; 148 | } else if(bestMatch.distance > 0.5) { 149 | // we must shift clock forward 150 | shouldShiftClock = 1; 151 | } else { 152 | shouldShiftClock = 0; 153 | } 154 | lastItems = items; 155 | debug.dir({zeroCrossing, a, b, bestMatch, shouldShiftClock}); 156 | break; 157 | } 158 | } 159 | } 160 | } 161 | 162 | type SampleCategory = 'High' | 'Low' | 'ZeroRising' | 'ZeroDropping'; 163 | function categorizeSample(opts: {sample: Sample, highAmplitude: number, lowAmplitude: number, samplesPerPhase: number}): SampleCategory { 164 | const {sample: {value, slope}, highAmplitude, lowAmplitude, samplesPerPhase} = opts; 165 | if(value > highAmplitude) return 'High'; 166 | if(value < -highAmplitude) return 'Low'; 167 | if(Math.abs(value) < lowAmplitude) { 168 | if(slope > 0) return 'ZeroRising'; 169 | if(slope < 0) return 'ZeroDropping'; 170 | throw 'slope is zero; that shouldnt happen'; 171 | } 172 | 173 | if(Math.abs(slope) > highAmplitude / samplesPerPhase / 4) { 174 | // slope is extreme enough we can assume a zero crossing 175 | return slope > 0 ? 'ZeroRising' : 'ZeroDropping'; 176 | } 177 | throw 'value is neither high enough nor low enough to categorize; that shouldnt happen'; 178 | } 179 | 180 | interface Sample { 181 | value: number; 182 | slope: number; 183 | } 184 | 185 | function getSample(items: Array, index: number): Sample { 186 | assert(items.length >= 2); 187 | assert(index >= 0); 188 | let 189 | lowerIndex: number, 190 | upperIndex: number, 191 | slope: number, 192 | value: number; 193 | 194 | lowerIndex = Math.floor(index); 195 | upperIndex = lowerIndex + 1; 196 | if(upperIndex >= items.length) { 197 | // delta between last 2 values 198 | slope = items[items.length - 1] - items[items.length - 2]; 199 | value = items[items.length - 1] + (index - items.length - 1) * slope; 200 | } else { 201 | assert(lowerIndex >= 0); 202 | slope = items[upperIndex] - items[lowerIndex]; 203 | value = items[lowerIndex] + (index - lowerIndex) * slope; 204 | } 205 | return {value, slope}; 206 | } 207 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^12.7.5": 6 | version "12.7.5" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" 8 | integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== 9 | 10 | "@types/yargs-parser@*": 11 | version "13.1.0" 12 | resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228" 13 | integrity sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg== 14 | 15 | "@types/yargs@^13.0.2": 16 | version "13.0.2" 17 | resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.2.tgz#a64674fc0149574ecd90ba746e932b5a5f7b3653" 18 | integrity sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ== 19 | dependencies: 20 | "@types/yargs-parser" "*" 21 | 22 | alawmulaw@^5.0.2: 23 | version "5.0.2" 24 | resolved "https://registry.yarnpkg.com/alawmulaw/-/alawmulaw-5.0.2.tgz#885a914933e3f3bc0bdd67534af669a20f17d4aa" 25 | integrity sha512-W3bWBB7MwTNGALlAKbOxe+tMNW9DpqGsv1V1idGPzctnBH++eS+Dx3UuucHNe5nk38WvAuy0sgMAbS5idHCArw== 26 | 27 | ansi-regex@^4.1.0: 28 | version "4.1.0" 29 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 30 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 31 | 32 | ansi-styles@^3.2.0: 33 | version "3.2.1" 34 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 35 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 36 | dependencies: 37 | color-convert "^1.9.0" 38 | 39 | arg@^4.1.0: 40 | version "4.1.1" 41 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.1.tgz#485f8e7c390ce4c5f78257dbea80d4be11feda4c" 42 | integrity sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw== 43 | 44 | base64-arraybuffer-es6@^0.3.1: 45 | version "0.3.1" 46 | resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.3.1.tgz#fdf0e382f4e2f56caf881f48ee0ce01ae79afe48" 47 | integrity sha512-TrhBheudYaff9adiTAqjSScjvtmClQ4vF9l4cqkPNkVsA11m4/NRdH4LkZ/tAMmpzzwfI20BXnJ/PTtafECCNA== 48 | 49 | bitdepth@^7.0.2: 50 | version "7.0.2" 51 | resolved "https://registry.yarnpkg.com/bitdepth/-/bitdepth-7.0.2.tgz#d9290de0e4b44ce5fc0c29f813c8f49fbdfa2eeb" 52 | integrity sha512-Ed11TL4IIWyUEoQTfkbRBDCgDNurxYzFgmk30ZU6SgNCsysoEx7UMm+g7SDFHxA2lhLbWyjV8T1ab3z0BtYOAw== 53 | 54 | buffer-from@^1.0.0: 55 | version "1.1.1" 56 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 57 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 58 | 59 | byte-data@^16.0.3: 60 | version "16.0.3" 61 | resolved "https://registry.yarnpkg.com/byte-data/-/byte-data-16.0.3.tgz#8abb3e212c9b026790a28c2d6c22fdc0515fd49d" 62 | integrity sha512-IzV3mzv8OnnzPdb9CoESQr2ikPX/gkHUesRu+vff9XB7KwMxyflPDewtPFWXPvF+Xukl52ceor2IRLbnQZf3PQ== 63 | dependencies: 64 | endianness "^8.0.2" 65 | ieee754-buffer "^0.2.1" 66 | twos-complement-buffer "0.0.1" 67 | uint-buffer "^0.1.0" 68 | utf8-buffer "^0.2.0" 69 | 70 | camelcase@^5.0.0: 71 | version "5.3.1" 72 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 73 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 74 | 75 | cliui@^5.0.0: 76 | version "5.0.0" 77 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" 78 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== 79 | dependencies: 80 | string-width "^3.1.0" 81 | strip-ansi "^5.2.0" 82 | wrap-ansi "^5.1.0" 83 | 84 | color-convert@^1.9.0: 85 | version "1.9.3" 86 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 87 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 88 | dependencies: 89 | color-name "1.1.3" 90 | 91 | color-name@1.1.3: 92 | version "1.1.3" 93 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 94 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 95 | 96 | decamelize@^1.2.0: 97 | version "1.2.0" 98 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 99 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 100 | 101 | diff@^4.0.1: 102 | version "4.0.1" 103 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" 104 | integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== 105 | 106 | emoji-regex@^7.0.1: 107 | version "7.0.3" 108 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 109 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 110 | 111 | endianness@^8.0.2: 112 | version "8.0.2" 113 | resolved "https://registry.yarnpkg.com/endianness/-/endianness-8.0.2.tgz#e35d16bbe80b6ff94fbc199168dd234d9f78168e" 114 | integrity sha512-IU+77+jJ7lpw2qZ3NUuqBZFy3GuioNgXUdsL1L9tooDNTaw0TgOnwNuc+8Ns+haDaTifK97QLzmOANJtI/rGvw== 115 | 116 | find-up@^3.0.0: 117 | version "3.0.0" 118 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 119 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 120 | dependencies: 121 | locate-path "^3.0.0" 122 | 123 | get-caller-file@^2.0.1: 124 | version "2.0.5" 125 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 126 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 127 | 128 | ieee754-buffer@^0.2.1: 129 | version "0.2.1" 130 | resolved "https://registry.yarnpkg.com/ieee754-buffer/-/ieee754-buffer-0.2.1.tgz#7e0d7d381d228de949f06b71f1cfd971691271f6" 131 | integrity sha512-dDlJhYk8BAmH1HDncTjCt6xOm2+kT+MxGhRKB+mUoF8nocDzPAgZPEWTRI9QgkGvbDkbJgCqyxweGlIV0yhbUQ== 132 | 133 | imaadpcm@^4.1.2: 134 | version "4.1.2" 135 | resolved "https://registry.yarnpkg.com/imaadpcm/-/imaadpcm-4.1.2.tgz#8757742610004bc2aba1181919f899abd6a9b413" 136 | integrity sha512-7gwxe6lKCGitmkMtGbm1ecnt0Q59KcWwo7AVi2RAd3lQ9VghVN5zX5x3oK6xNhfD9KUMbaYzku43UBn3Ix3RIA== 137 | 138 | is-fullwidth-code-point@^2.0.0: 139 | version "2.0.0" 140 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 141 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 142 | 143 | locate-path@^3.0.0: 144 | version "3.0.0" 145 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 146 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== 147 | dependencies: 148 | p-locate "^3.0.0" 149 | path-exists "^3.0.0" 150 | 151 | make-error@^1.1.1: 152 | version "1.3.5" 153 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" 154 | integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== 155 | 156 | p-limit@^2.0.0: 157 | version "2.2.1" 158 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" 159 | integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== 160 | dependencies: 161 | p-try "^2.0.0" 162 | 163 | p-locate@^3.0.0: 164 | version "3.0.0" 165 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 166 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== 167 | dependencies: 168 | p-limit "^2.0.0" 169 | 170 | p-try@^2.0.0: 171 | version "2.2.0" 172 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 173 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 174 | 175 | path-exists@^3.0.0: 176 | version "3.0.0" 177 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 178 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 179 | 180 | require-directory@^2.1.1: 181 | version "2.1.1" 182 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 183 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 184 | 185 | require-main-filename@^2.0.0: 186 | version "2.0.0" 187 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 188 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 189 | 190 | set-blocking@^2.0.0: 191 | version "2.0.0" 192 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 193 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 194 | 195 | source-map-support@^0.5.13, source-map-support@^0.5.6: 196 | version "0.5.13" 197 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" 198 | integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== 199 | dependencies: 200 | buffer-from "^1.0.0" 201 | source-map "^0.6.0" 202 | 203 | source-map@^0.6.0: 204 | version "0.6.1" 205 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 206 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 207 | 208 | string-width@^3.0.0, string-width@^3.1.0: 209 | version "3.1.0" 210 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 211 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 212 | dependencies: 213 | emoji-regex "^7.0.1" 214 | is-fullwidth-code-point "^2.0.0" 215 | strip-ansi "^5.1.0" 216 | 217 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: 218 | version "5.2.0" 219 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 220 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 221 | dependencies: 222 | ansi-regex "^4.1.0" 223 | 224 | ts-node-to@^0.0.3: 225 | version "0.0.3" 226 | resolved "https://registry.yarnpkg.com/ts-node-to/-/ts-node-to-0.0.3.tgz#1152da3b3860bd705bac66b9185411c8808d1467" 227 | integrity sha512-+5Ebp3y+fhD5qWfVyh5py55yXokdmpKLe9vJn8Z2m/3h+O5dK2FIU1BsfJKPpMz//ynRRd5ZDH4gcqAftsCOBg== 228 | 229 | ts-node@^8.3.0: 230 | version "8.3.0" 231 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.3.0.tgz#e4059618411371924a1fb5f3b125915f324efb57" 232 | integrity sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ== 233 | dependencies: 234 | arg "^4.1.0" 235 | diff "^4.0.1" 236 | make-error "^1.1.1" 237 | source-map-support "^0.5.6" 238 | yn "^3.0.0" 239 | 240 | tslib@^1.10.0: 241 | version "1.10.0" 242 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" 243 | integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== 244 | 245 | twos-complement-buffer@0.0.1: 246 | version "0.0.1" 247 | resolved "https://registry.yarnpkg.com/twos-complement-buffer/-/twos-complement-buffer-0.0.1.tgz#e461a248d5c14a6d90afd031b2677f31da127d29" 248 | integrity sha512-Ev3p2GfB2GO8pcyb7jIvctS9RAjSZrF/K+u5s3KN00ajY11Dda2oMqI72nXaHVU7doGYNXc0mJG6exWAbmzZiA== 249 | dependencies: 250 | uint-buffer "^0.1.0" 251 | 252 | typescript@^3.6.3: 253 | version "3.6.3" 254 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" 255 | integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== 256 | 257 | uint-buffer@^0.1.0: 258 | version "0.1.0" 259 | resolved "https://registry.yarnpkg.com/uint-buffer/-/uint-buffer-0.1.0.tgz#35f2f59fa253d338affcdb6f377750642bd1cf59" 260 | integrity sha512-7xjpjCTijFIXAMxN7OMRfykpCMVfbCrlAmAt2RIlihvkHgvkNV5DBFzyc8OpIQeVpRXJkgXBwmKos4hD8DrX1g== 261 | 262 | utf8-buffer@^0.2.0: 263 | version "0.2.0" 264 | resolved "https://registry.yarnpkg.com/utf8-buffer/-/utf8-buffer-0.2.0.tgz#a4530a606d9d2b0348456a393eaf836b62fed2f3" 265 | integrity sha512-DygDeOmOPQRjxnnv8LdfjoSQgG9EgJFH1m/1QcrKkDOxzoOcLLqZ2ONzRYHmiRqJYQYnAvV+zv2Wgk5tXjr4aA== 266 | 267 | wavefile@^8.4.5: 268 | version "8.4.6" 269 | resolved "https://registry.yarnpkg.com/wavefile/-/wavefile-8.4.6.tgz#8c99809df1a344edb12839da69e588bf53c7e483" 270 | integrity sha512-mKvPtkXTFE3U8Uo8qJr/hCJP3booCI09vsoWY3OHrdKB+8ZefO/5/wSRZSbPSgFDB+WWn3Am3c01h/sguTYuyA== 271 | dependencies: 272 | alawmulaw "^5.0.2" 273 | base64-arraybuffer-es6 "^0.3.1" 274 | bitdepth "^7.0.2" 275 | byte-data "^16.0.3" 276 | imaadpcm "^4.1.2" 277 | 278 | which-module@^2.0.0: 279 | version "2.0.0" 280 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 281 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 282 | 283 | wrap-ansi@^5.1.0: 284 | version "5.1.0" 285 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" 286 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== 287 | dependencies: 288 | ansi-styles "^3.2.0" 289 | string-width "^3.0.0" 290 | strip-ansi "^5.0.0" 291 | 292 | y18n@^4.0.0: 293 | version "4.0.0" 294 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 295 | integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== 296 | 297 | yargs-parser@^13.1.1: 298 | version "13.1.1" 299 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" 300 | integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== 301 | dependencies: 302 | camelcase "^5.0.0" 303 | decamelize "^1.2.0" 304 | 305 | yargs@^14.0.0: 306 | version "14.0.0" 307 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.0.0.tgz#ba4cacc802b3c0b3e36a9e791723763d57a85066" 308 | integrity sha512-ssa5JuRjMeZEUjg7bEL99AwpitxU/zWGAGpdj0di41pOEmJti8NR6kyUIJBkR78DTYNPZOU08luUo0GTHuB+ow== 309 | dependencies: 310 | cliui "^5.0.0" 311 | decamelize "^1.2.0" 312 | find-up "^3.0.0" 313 | get-caller-file "^2.0.1" 314 | require-directory "^2.1.1" 315 | require-main-filename "^2.0.0" 316 | set-blocking "^2.0.0" 317 | string-width "^3.0.0" 318 | which-module "^2.0.0" 319 | y18n "^4.0.0" 320 | yargs-parser "^13.1.1" 321 | 322 | yn@^3.0.0: 323 | version "3.1.1" 324 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 325 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 326 | --------------------------------------------------------------------------------