UNPKG

gibberish-dsp

Version:

Gibberish is designed to be an optimized API for audio synthesis using per-sample techniques.

224 lines (187 loc) 6.75 kB
const g = require( 'genish.js' ), instrument = require( './instrument.js' ) module.exports = function( Gibberish ) { const proto = Object.create( instrument ) const memo = {} Object.assign( proto, { note( rate ) { this.rate = rate if( rate > 0 ) { this.__trigger() }else{ this.__phase__.value = this.end * (this.data.buffer.length - 1) } }, trigger( volume ) { if( volume !== undefined ) this.gain = volume if( Gibberish.mode === 'processor' ) { // if we're playing the sample forwards... if( Gibberish.memory.heap[ this.__rateStorage__.memory.values.idx ] > 0 ) { this.__trigger() }else{ this.__phase__.value = this.end * (this.data.buffer.length - 1) } } }, }) const Sampler = inputProps => { const syn = Object.create( proto ) const props = Object.assign( { onload:null }, Sampler.defaults, inputProps ) syn.isStereo = props.isStereo !== undefined ? props.isStereo : false const start = g.in( 'start' ), end = g.in( 'end' ), bufferLength = g.in( 'bufferLength' ), rate = g.in( 'rate' ), shouldLoop = g.in( 'loops' ), loudness = g.in( 'loudness' ), triggerLoudness = g.in( '__triggerLoudness' ), // rate storage is used to determine whether we're playing // the sample forward or in reverse, for use in the 'trigger' method. rateStorage = g.data([0], 1, { meta:true }) Object.assign( syn, props ) if( Gibberish.mode === 'worklet' ) { syn.__meta__ = { address:'add', name: ['instruments', 'Sampler'], properties: JSON.stringify(props), id: syn.id } Gibberish.worklet.ugens.set( syn.id, syn ) Gibberish.worklet.port.postMessage( syn.__meta__ ) } syn.__createGraph = function() { syn.__bang__ = g.bang() syn.__trigger = syn.__bang__.trigger syn.__phase__ = g.counter( rate, g.mul(start,bufferLength), g.mul( end, bufferLength ), syn.__bang__, shouldLoop, { shouldWrap:false, initialValue:9999999 } ) syn.__rateStorage__ = rateStorage rateStorage[0] = rate // XXX we added our recorded 'rate' param and then effectively subtract it, // so that its presence in the graph will force genish to actually record the // rate as the input. this is extremely hacky... there should be a way to record // value without having to include it in the graph! syn.graph = g.add( g.mul( g.ifelse( g.and( g.gte( syn.__phase__, g.mul(start,bufferLength) ), g.lt( syn.__phase__, g.mul(end,bufferLength) ) ), g.peek( syn.data, syn.__phase__, { mode:'samples' } ), 0 ), g.mul( g.mul( loudness, triggerLoudness ), g.in('gain') ) ), rateStorage[0], g.mul( rateStorage[0], -1 ) ) if( syn.panVoices === true ) { const panner = g.pan( syn.graph, syn.graph, g.in( 'pan' ) ) syn.graph = [ panner.left, panner.right ] } } const onload = (buffer,filename) => { if( buffer === undefined ) return if( Gibberish.mode === 'worklet' ) { //const memIdx = memo[ filename ].idx !== undefined ? memo[ filename ].idx : Gibberish.memory.alloc( syn.data.memory.values.length, true ) const memIdx = Gibberish.memory.alloc( buffer.length, true ) //memo[ filename ].idx = memIdx Gibberish.worklet.port.postMessage({ address:'copy', id: syn.id, idx: memIdx, buffer }) }else if ( Gibberish.mode === 'processor' ) { syn.data.buffer = buffer syn.data.memory.values.length = syn.data.dim = buffer.length syn.__redoGraph() } if( typeof syn.onload === 'function' ){ syn.onload( buffer || syn.data.buffer ) } if( syn.bufferLength === -999999999 && syn.data.buffer !== undefined ) syn.bufferLength = syn.data.buffer.length - 1 } //if( props.filename ) { syn.loadFile = function( filename ) { //if( memo[ filename ] === undefined ) { if( Gibberish.mode !== 'processor' ) { syn.data = g.data( filename, 1, { onload }) // check to see if a promise is returned; a valid // data object is only return if the file has been // previously loaded and the corresponding buffer has // been cached. if( syn.data instanceof Promise ) { syn.data.then( d => { syn.data = d memo[ filename ] = syn.data onload( d.buffer, filename ) }) }else{ // using a cached data buffer, no need // for asynchronous loading. memo[ filename ] = syn.data onload( syn.data.buffer, filename ) } }else{ syn.data = g.data( new Float32Array(), 1, { onload, filename }) //memo[ filename ] = syn.data } //}else{ // syn.data = memo[ filename ] // console.log( 'memo data:', syn.data ) // onload( syn.data.buffer, filename ) //} } syn.loadBuffer = function( buffer ) { if( Gibberish.mode === 'processor' ) { syn.data.buffer = buffer syn.data.memory.values.length = syn.data.dim = buffer.length syn.__redoGraph() } } if( props.filename !== undefined ) { syn.loadFile( props.filename ) }else{ syn.data = g.data( new Float32Array() ) } if( syn.data !== undefined ) { syn.data.onload = onload syn.__createGraph() } const out = Gibberish.factory( syn, syn.graph, ['instruments','sampler'], props ) return out } Sampler.defaults = { gain: 1, pan: .5, rate: 1, panVoices:false, loops: 0, start:0, end:1, bufferLength:-999999999, loudness:1, __triggerLoudness:1 } const envCheckFactory = function( voice, _poly ) { const envCheck = () => { const phase = Gibberish.memory.heap[ voice.__phase__.memory.value.idx ] if( ( voice.rate > 0 && phase > voice.end ) || ( voice.rate < 0 && phase < 0 ) ) { _poly.disconnectUgen.call( _poly, voice ) voice.isConnected = false }else{ Gibberish.blockCallbacks.push( envCheck ) } } return envCheck } const PolySampler = Gibberish.PolyTemplate( Sampler, ['rate','pan','gain','start','end','loops','bufferLength','__triggerLoudness','loudness'], envCheckFactory ) return [ Sampler, PolySampler ] }