UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

126 lines (114 loc) 3.19 kB
/** * allows to import multiple audio files to use as samples * * */ import {TypedAudioNode} from './_Base'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {AudioBuilder} from '../../../core/audio/AudioBuilder'; import {Sampler, SamplerOptions} from 'tone/build/esm/instrument/Sampler'; import {PolyDictionary} from '../../../types/GlobalTypes'; import {StringParamLanguage} from '../../params/utils/OptionsController'; import {sanitizeUrl} from '../../../core/UrlHelper'; import {CoreLoaderAudio} from '../../../core/loader/Audio'; import {Poly} from '../../Poly'; import {AudioType} from '../../poly/registers/nodes/types/Audio'; export const SAMPLER_DEFAULTS: Partial<SamplerOptions> = { attack: 0.01, release: 1, }; const DEFAULT_NOTE_MAP = { A0: 'A0', C1: 'C1', 'D#1': 'Ds1', 'F#1': 'Fs1', A1: 'A1', C2: 'C2', 'D#2': 'Ds2', 'F#2': 'Fs2', A2: 'A2', C3: 'C3', 'D#3': 'Ds3', 'F#3': 'Fs3', A3: 'A3', C4: 'C4', 'D#4': 'Ds4', 'F#4': 'Fs4', A4: 'A4', C5: 'C5', 'D#5': 'Ds5', 'F#5': 'Fs5', A5: 'A5', C6: 'C6', 'D#6': 'Ds6', 'F#6': 'Fs6', A6: 'A6', C7: 'C7', 'D#7': 'Ds7', 'F#7': 'Fs7', A7: 'A7', C8: 'C8', }; class SamplerAudioParamsConfig extends NodeParamsConfig { /** @param map of notes to load */ urlsMap = ParamConfig.STRING(JSON.stringify(DEFAULT_NOTE_MAP, null, 2), { language: StringParamLanguage.JSON, }); /** @param base url */ baseUrl = ParamConfig.STRING('./'); /** @param extension */ extension = ParamConfig.STRING('mp3'); } const ParamsConfig = new SamplerAudioParamsConfig(); export class SamplerAudioNode extends TypedAudioNode<SamplerAudioParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return AudioType.SAMPLER; } override initializeNode() { this.io.inputs.setCount(1); } override dispose(): void { super.dispose(); Poly.blobs.clearBlobsForNode(this); } override async cook(inputContents: AudioBuilder[]) { const envelopeBuilder = inputContents[0]; const envelopeParams = envelopeBuilder.envelopeParams() || SAMPLER_DEFAULTS; let urlsJSON: PolyDictionary<string> = {}; let urls: string[] = []; try { const extension = this.pv.extension; urlsJSON = JSON.parse(this.pv.urlsMap); const noteNames = Object.keys(urlsJSON); for (const noteName of noteNames) { const urlFileName = urlsJSON[noteName]; const url = `${this.pv.baseUrl}/${urlFileName}.${extension}`; urlsJSON[noteName] = sanitizeUrl(url); urls.push(url); } } catch (err) { this.states.error.set('urlsMap is invalid JSON'); } // preload urls for export const loaders: CoreLoaderAudio[] = urls.map( (url) => new CoreLoaderAudio(url, this, {multiAssetsForNode: true}) ); await Promise.all(loaders.map((loader) => loader.load())); // create sample const sampler = new Sampler({ urls: urlsJSON, attack: envelopeParams.attack, release: envelopeParams.release, baseUrl: '', onerror: (err) => { this.states.error.set(err.message); }, onload: () => { const audioBuilder = new AudioBuilder(); audioBuilder.setInstrument(sampler); this.setAudioBuilder(audioBuilder); }, }); } }