├── .gitignore ├── LICENSE.md ├── README.md ├── convolver ├── 85_DnBDrums_03_592.wav ├── KnightsHall.wav ├── README.md └── index.js ├── hoa-encoder ├── 84bpm_DMaj_PluckingAbout.wav ├── README.md └── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | node_modules 4 | 5 | .cache 6 | .parcel-cache 7 | 8 | *.swp 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2022 Elementary Audio, LLC 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Offline Rendering Examples 2 | 3 | This repository holds a set of small examples using Elementary Audio in Node.js to process 4 | audio files. Each subdirectory contains a self-contained script to execute and a README 5 | explaining what the process does and how to use it from your command line. 6 | 7 | If you're new to Elementary Audio, [**Elementary**](https://elementary.audio) is a JavaScript/C++ library for building audio applications. 8 | 9 | * **Declarative:** Elementary makes it simple to create interactive audio processes through functional, declarative programming. Describe your audio process as a function of your application state, and Elementary will efficiently update the underlying audio engine as necessary. 10 | * **Dynamic:** Most audio processing frameworks and tools facilitate building static processes. But what happens as your audio requirements change throughout the user journey? Elementary is designed to facilitate and adapt to the dynamic nature of modern audio applications. 11 | * **Portable:** By decoupling the JavaScript API from the underlying audio engine (the "what" from the "how"), Elementary enables writing portable applications. Whether the underlying engine is running in the browser, an audio plugin, or an embedded device, the JavaScript layer remains the same. 12 | 13 | Find more in the [Elementary repository on GitHub](https://github.com/elemaudio/elementary) and the documentation [on the website](https://elementary.audio/). 14 | 15 | ## Examples 16 | 17 | Before attempting to run the examples below, make sure you first `npm install` in this root 18 | directory to fetch the necessary dependencies. 19 | 20 | * [HOA Encoder](https://github.com/elemaudio/offline-examples/tree/master/hoa-encoder) 21 | * [Multi-Channel Convolver](https://github.com/elemaudio/offline-examples/tree/master/convolver) 22 | 23 | 24 | ## License 25 | 26 | [ISC](LICENSE.md) 27 | -------------------------------------------------------------------------------- /convolver/85_DnBDrums_03_592.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elemaudio/offline-examples/78c750341a4cfeff266234c6cd4a86b2ad7f830d/convolver/85_DnBDrums_03_592.wav -------------------------------------------------------------------------------- /convolver/KnightsHall.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elemaudio/offline-examples/78c750341a4cfeff266234c6cd4a86b2ad7f830d/convolver/KnightsHall.wav -------------------------------------------------------------------------------- /convolver/README.md: -------------------------------------------------------------------------------- 1 | # Multi-Channel Convolver 2 | 3 | A simple command line utility for processing a mono sound file into a multi-channel output file by 4 | convolving the input with each channel of a multi-channel impulse response file. 5 | 6 | ## Usage 7 | 8 | First make sure you've installed the dependencies in the parent directory above this example. 9 | 10 | ```bash 11 | node index.js [--normalize=] 12 | ``` 13 | 14 | ## Example 15 | 16 | Try the following command using the included drum sample and hall impulse to produce a 2-channel output file. 17 | 18 | ```bash 19 | node index.js ./85_DnBDrums_03_592.wav ./KnightsHall.wav ./out.wav --normalize=true 20 | ``` 21 | -------------------------------------------------------------------------------- /convolver/index.js: -------------------------------------------------------------------------------- 1 | import minimist from 'minimist'; 2 | import wavefile from 'wavefile'; 3 | import { readFileSync, writeFileSync } from 'fs'; 4 | 5 | import OfflineRenderer from '@elemaudio/offline-renderer'; 6 | import { el } from '@elemaudio/core'; 7 | 8 | 9 | // Parsing our input arguments 10 | const argv = minimist(process.argv.slice(2)); 11 | const [inFile, irFile, outFile] = argv._; 12 | const {normalize = false} = argv; 13 | 14 | // A quick helper function for reading wav files into Float32Array buffers 15 | function decodeAudioData(path) { 16 | const wav = new wavefile.WaveFile(readFileSync(path)); 17 | const bitRate = wav.fmt.bitsPerSample; 18 | const sampleRate = wav.fmt.sampleRate; 19 | const channelData = wav.getSamples().map(function(chan) { 20 | return Float32Array.from(chan, x => x / (2 ** (bitRate - 1))); 21 | }); 22 | 23 | return { 24 | bitRate, 25 | sampleRate, 26 | channelData, 27 | }; 28 | } 29 | 30 | // We wrap the main bit of work in an async main function basically 31 | // just so that we can `await` the core.initialize call below. 32 | (async function main() { 33 | console.log(`Convolving ${inFile} with ${irFile}...`); 34 | 35 | let core = new OfflineRenderer(); 36 | 37 | let inputData = decodeAudioData(inFile); 38 | let irData = decodeAudioData(irFile); 39 | 40 | if (inputData.sampleRate !== irData.sampleRate) { 41 | throw new Error('Trying to convolve an input file by an IR with different sample rates! Aborting.'); 42 | } 43 | 44 | if (normalize) { 45 | let maxSumMagSquared = irData.channelData.reduce(function(max, chan) { 46 | let chanSumMagSquared = chan.reduce(function(acc, val) { 47 | return acc + (val * val); 48 | }, 0); 49 | 50 | return Math.max(max, chanSumMagSquared); 51 | }, 0); 52 | 53 | let normFactor = (0.125 / Math.sqrt(maxSumMagSquared)); 54 | 55 | // Apply the normalization factor 56 | for (let i = 0; i < irData.channelData.length; ++i) { 57 | for (let j = 0; j < irData.channelData[i].length; ++j) { 58 | irData.channelData[i][j] *= normFactor; 59 | } 60 | } 61 | } 62 | 63 | // Like the web audio renderer, we have to initialize the OfflineRenderer by 64 | // pre-loading any sample data we want to use through the virtual file system. 65 | // In this case, the sample data we're interested in pre-loading is just the 66 | // impulse response that will be referenced by the convolver. 67 | let virtualFileSystem = irData.channelData.reduce(function(acc, next, i) { 68 | return Object.assign(acc, { 69 | // This name can be anything you want, we just need to reference it by the 70 | // same name in our graph below 71 | [`/virtual/impulse.wav:${i}`]: next, 72 | }); 73 | }, {}); 74 | 75 | await core.initialize({ 76 | // We only take mono input, either by using a mono input file or the just first channel of the input 77 | numInputChannels: 1, 78 | // We produce N channels, where N is the number of channels in the IR file 79 | numOutputChannels: irData.channelData.length, 80 | sampleRate: inputData.sampleRate, 81 | virtualFileSystem, 82 | }); 83 | 84 | core.updateVirtualFileSystem(virtualFileSystem); 85 | 86 | // Our processing graph 87 | core.render(...irData.channelData.map(function(chan, i) { 88 | // For each channel, we just convolve the first input channel with the 89 | // appropriate impulse response channel using the named lookup from our 90 | // virtual file system 91 | return el.convolve({path: `/virtual/impulse.wav:${i}`}, el.in({channel: 0})); 92 | })); 93 | 94 | // Pushing samples through the graph 95 | let inps = [inputData.channelData[0]]; 96 | let outs = Array.from({length: irData.channelData.length}).map(_ => new Float32Array(inps[0].length)); 97 | 98 | core.process(inps, outs); 99 | 100 | // Fill our output wav buffer with the process data and write to disk. 101 | // Here we convert back to 16-bit PCM before write. 102 | const outputWav = new wavefile.WaveFile(); 103 | 104 | outputWav.fromScratch(outs.length, inputData.sampleRate, '16', outs.map(function(chan) { 105 | return Int16Array.from(chan, x => x * (2 ** 15)); 106 | })); 107 | 108 | writeFileSync(outFile, outputWav.toBuffer()); 109 | })(); 110 | -------------------------------------------------------------------------------- /hoa-encoder/84bpm_DMaj_PluckingAbout.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elemaudio/offline-examples/78c750341a4cfeff266234c6cd4a86b2ad7f830d/hoa-encoder/84bpm_DMaj_PluckingAbout.wav -------------------------------------------------------------------------------- /hoa-encoder/README.md: -------------------------------------------------------------------------------- 1 | # HOA Encoder 2 | 3 | A simple command line utility for encoding a mono sound file into higher order ambisonics (HOA), with ACN 4 | channel ordering and N3D or SN3D normalization at the provided azimuth, elevation, and order. 5 | 6 | ## Usage 7 | 8 | First make sure you've installed the dependencies in the parent directory above this example. 9 | 10 | ```bash 11 | node index.js [--azim=] [--elev=] [--order=] [--norm=] 12 | ``` 13 | 14 | * `--azim`: the desired azimuth, in degrees 15 | * `--elev`: the desired elevation, in degrees 16 | * `--order`: ambisonics order 17 | * `--norm`: Either `"sn3d"` or `"n3d"` (default) 18 | 19 | ## Example 20 | 21 | Try the following command using the included guitar sample to produce a 16-channel ambisonic-encoded output file. 22 | 23 | ```bash 24 | node index.js ./84bpm_DMaj_PluckingAbout.wav ./out.wav --azim=-90 --elev=0 --order=2 25 | ``` 26 | 27 | The easiest way to test the result is to load the output file into a multi-channel audio environment such as Reaper 28 | or Max/MSP, and send the 16 channels into a binaural decoder to adapt the spatialization to your headphones. 29 | -------------------------------------------------------------------------------- /hoa-encoder/index.js: -------------------------------------------------------------------------------- 1 | import minimist from 'minimist'; 2 | import wavefile from 'wavefile'; 3 | import { readFileSync, writeFileSync } from 'fs'; 4 | 5 | import * as jshlib from 'spherical-harmonic-transform'; 6 | 7 | import OfflineRenderer from '@elemaudio/offline-renderer'; 8 | import { el } from '@elemaudio/core'; 9 | 10 | 11 | // Parsing our input arguments 12 | const argv = minimist(process.argv.slice(2)); 13 | const [inFile, outFile] = argv._; 14 | const {azim = 0, elev = 0, order = 1, norm = "n3d"} = argv; 15 | 16 | // Preparing our wav file assets 17 | const inputWav = new wavefile.WaveFile(readFileSync(inFile)); 18 | const outputWav = new wavefile.WaveFile(); 19 | 20 | if (norm !== "n3d" && norm !== "sn3d") { 21 | console.error(`Unknown normalization mode ${norm}, please choose either "n3d" or "sn3d"`); 22 | process.exit(1); 23 | } 24 | 25 | console.log(`Encoding HOA output file with azim: ${azim}, elev: ${elev}, order: ${order}, norm: ${norm}`); 26 | 27 | 28 | // This function encodes a mono point source with a given azimuth and 29 | // elevation into an Nth order HOA channel array. 30 | // 31 | // Order, azim, and elev are all expected to be primitive numbers. Azim 32 | // and elev are in degrees, not radians. The return value is an array of 33 | // elementary nodes of length (order + 1)^2. 34 | function ambipan(order, azim, elev, xn) { 35 | let gains = jshlib.computeRealSH(order, [ 36 | [azim * Math.PI / 180, elev * Math.PI / 180], 37 | ]); 38 | 39 | return gains.map(function(g, i) { 40 | let gain = g[0]; 41 | 42 | // By default, the spherical harmonic transform library here yields coefficients 43 | // normalized in N3D. If the user asking for SN3D we convert here. 44 | if (norm === "sn3d") { 45 | gain = gain / Math.sqrt(2 * i + 1); 46 | } 47 | 48 | return el.mul(el.sm(el.const({key: `g${i}`, value: gain})), xn); 49 | }); 50 | } 51 | 52 | // We wrap the main bit of work in an async main function basically 53 | // just so that we can `await` the core.initialize call below. 54 | (async function main() { 55 | let core = new OfflineRenderer(); 56 | let numOuts = (order + 1) * (order + 1); 57 | 58 | await core.initialize({ 59 | numInputChannels: 1, 60 | numOutputChannels: numOuts, 61 | sampleRate: inputWav.fmt.sampleRate, 62 | }); 63 | 64 | // Our sample data for processing. We expect that the input wav is 65 | // a mono file, but if it's not we just take the first channel of data 66 | // 67 | // We also have to convert from integer PCM to 32-bit float here. 68 | let inps = [Float32Array.from(inputWav.getSamples()[0], x => x / (2 ** (inputWav.fmt.bitsPerSample - 1)))]; 69 | let outs = Array.from({length: numOuts}).map(_ => new Float32Array(inps[0].length));; 70 | 71 | // Our processing graph 72 | core.render(...ambipan(order, azim, elev, el.in({channel: 0}))); 73 | 74 | // Pushing samples through the graph 75 | core.process(inps, outs); 76 | 77 | // Fill our output wav buffer with the process data and write to disk. 78 | // Here we convert back to 16-bit PCM before write. 79 | outputWav.fromScratch(numOuts, inputWav.fmt.sampleRate, '16', outs.map(function(chan) { 80 | return Int16Array.from(chan, x => x * (2 ** 15)); 81 | })); 82 | 83 | writeFileSync(outFile, outputWav.toBuffer()); 84 | })(); 85 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offline-examples", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "offline-examples", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@elemaudio/core": "^2.0.0", 13 | "@elemaudio/offline-renderer": "^2.0.0", 14 | "minimist": "^1.2.6", 15 | "spherical-harmonic-transform": "^0.1.1", 16 | "wavefile": "^11.0.0" 17 | } 18 | }, 19 | "node_modules/@elemaudio/core": { 20 | "version": "2.0.0", 21 | "resolved": "https://registry.npmjs.org/@elemaudio/core/-/core-2.0.0.tgz", 22 | "integrity": "sha512-gLR1HF2CgLcJ287P9eyeBNuskS5tVXUbzfJdV+cnf5R6D6jYm2rU2tzgddMx+1uWW0+I1bUSMvC2a5BoFFB+wA==", 23 | "dependencies": { 24 | "invariant": "^2.2.4", 25 | "shallowequal": "^1.1.0" 26 | } 27 | }, 28 | "node_modules/@elemaudio/offline-renderer": { 29 | "version": "2.0.0", 30 | "resolved": "https://registry.npmjs.org/@elemaudio/offline-renderer/-/offline-renderer-2.0.0.tgz", 31 | "integrity": "sha512-4O7USPTDjLFUqq5uWj0YerfkfZ2+LS8kHtDPn8x25Xg3F2obHF+XV/URb33a2TYWPEXrt/MRJ8JvYN+HkRBSDQ==", 32 | "dependencies": { 33 | "@elemaudio/core": "^2.0.0", 34 | "events": "^3.3.0", 35 | "invariant": "^2.2.4" 36 | }, 37 | "engines": { 38 | "node": ">=18" 39 | } 40 | }, 41 | "node_modules/events": { 42 | "version": "3.3.0", 43 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 44 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 45 | "engines": { 46 | "node": ">=0.8.x" 47 | } 48 | }, 49 | "node_modules/invariant": { 50 | "version": "2.2.4", 51 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 52 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 53 | "dependencies": { 54 | "loose-envify": "^1.0.0" 55 | } 56 | }, 57 | "node_modules/js-tokens": { 58 | "version": "4.0.0", 59 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 60 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 61 | }, 62 | "node_modules/loose-envify": { 63 | "version": "1.4.0", 64 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 65 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 66 | "dependencies": { 67 | "js-tokens": "^3.0.0 || ^4.0.0" 68 | }, 69 | "bin": { 70 | "loose-envify": "cli.js" 71 | } 72 | }, 73 | "node_modules/minimist": { 74 | "version": "1.2.6", 75 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 76 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 77 | }, 78 | "node_modules/numeric": { 79 | "version": "1.2.6", 80 | "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", 81 | "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" 82 | }, 83 | "node_modules/shallowequal": { 84 | "version": "1.1.0", 85 | "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", 86 | "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" 87 | }, 88 | "node_modules/spherical-harmonic-transform": { 89 | "version": "0.1.1", 90 | "resolved": "https://registry.npmjs.org/spherical-harmonic-transform/-/spherical-harmonic-transform-0.1.1.tgz", 91 | "integrity": "sha512-p7hE9UdGegZsOigPcg/fCkQ1m1nFE196gwQ+iEaT09hc2K8GVbm5AfLKHyGHpaSgbtKRIeue5z2PtyY2fvAVGw==", 92 | "dependencies": { 93 | "numeric": "^1.2.6" 94 | } 95 | }, 96 | "node_modules/wavefile": { 97 | "version": "11.0.0", 98 | "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", 99 | "integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==", 100 | "bin": { 101 | "wavefile": "bin/wavefile.js" 102 | }, 103 | "engines": { 104 | "node": ">=8" 105 | } 106 | } 107 | }, 108 | "dependencies": { 109 | "@elemaudio/core": { 110 | "version": "2.0.0", 111 | "resolved": "https://registry.npmjs.org/@elemaudio/core/-/core-2.0.0.tgz", 112 | "integrity": "sha512-gLR1HF2CgLcJ287P9eyeBNuskS5tVXUbzfJdV+cnf5R6D6jYm2rU2tzgddMx+1uWW0+I1bUSMvC2a5BoFFB+wA==", 113 | "requires": { 114 | "invariant": "^2.2.4", 115 | "shallowequal": "^1.1.0" 116 | } 117 | }, 118 | "@elemaudio/offline-renderer": { 119 | "version": "2.0.0", 120 | "resolved": "https://registry.npmjs.org/@elemaudio/offline-renderer/-/offline-renderer-2.0.0.tgz", 121 | "integrity": "sha512-4O7USPTDjLFUqq5uWj0YerfkfZ2+LS8kHtDPn8x25Xg3F2obHF+XV/URb33a2TYWPEXrt/MRJ8JvYN+HkRBSDQ==", 122 | "requires": { 123 | "@elemaudio/core": "^2.0.0", 124 | "events": "^3.3.0", 125 | "invariant": "^2.2.4" 126 | } 127 | }, 128 | "events": { 129 | "version": "3.3.0", 130 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 131 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" 132 | }, 133 | "invariant": { 134 | "version": "2.2.4", 135 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 136 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 137 | "requires": { 138 | "loose-envify": "^1.0.0" 139 | } 140 | }, 141 | "js-tokens": { 142 | "version": "4.0.0", 143 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 144 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 145 | }, 146 | "loose-envify": { 147 | "version": "1.4.0", 148 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 149 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 150 | "requires": { 151 | "js-tokens": "^3.0.0 || ^4.0.0" 152 | } 153 | }, 154 | "minimist": { 155 | "version": "1.2.6", 156 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 157 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 158 | }, 159 | "numeric": { 160 | "version": "1.2.6", 161 | "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", 162 | "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" 163 | }, 164 | "shallowequal": { 165 | "version": "1.1.0", 166 | "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", 167 | "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" 168 | }, 169 | "spherical-harmonic-transform": { 170 | "version": "0.1.1", 171 | "resolved": "https://registry.npmjs.org/spherical-harmonic-transform/-/spherical-harmonic-transform-0.1.1.tgz", 172 | "integrity": "sha512-p7hE9UdGegZsOigPcg/fCkQ1m1nFE196gwQ+iEaT09hc2K8GVbm5AfLKHyGHpaSgbtKRIeue5z2PtyY2fvAVGw==", 173 | "requires": { 174 | "numeric": "^1.2.6" 175 | } 176 | }, 177 | "wavefile": { 178 | "version": "11.0.0", 179 | "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", 180 | "integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==" 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offline-examples", 3 | "version": "1.0.0", 4 | "description": "Offline rendering examples at the command line with Node.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@elemaudio/core": "^2.0.0", 13 | "@elemaudio/offline-renderer": "^2.0.0", 14 | "minimist": "^1.2.6", 15 | "spherical-harmonic-transform": "^0.1.1", 16 | "wavefile": "^11.0.0" 17 | } 18 | } 19 | --------------------------------------------------------------------------------