UNPKG

gibberish-dsp

Version:

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

213 lines (180 loc) 6.89 kB
const g = require( 'genish.js' ), __proxy = require( '../workletProxy.js' ), ugen = require( '../ugen.js' )() module.exports = function( Gibberish ) { const __proto__ = Object.create( ugen ) const proxy = __proxy( Gibberish ) Object.assign( __proto__, { start( delay=0 ) { if( delay !== 0 ) { Gibberish.scheduler.add( delay, ()=> { Gibberish.analyzers.push( this ) Gibberish.dirty( Gibberish.analyzers ) }) }else{ Gibberish.analyzers.push( this ) Gibberish.dirty( Gibberish.analyzers ) } return this }, stop( delay=0 ) { const idx = Gibberish.analyzers.indexOf( this ) if( delay === 0 ) { if( idx > -1 ) { Gibberish.analyzers.splice( idx, 1 ) Gibberish.dirty( Gibberish.analyzers ) } this.phase = 0 this.nextTime = 0 }else{ Gibberish.scheduler.add( delay, ()=> { if( idx > -1 ) { Gibberish.analyzers.splice( idx, 1 ) Gibberish.dirty( Gibberish.analyzers ) } this.phase = 0 this.nextTime = 0 }) } return this }, fire(){ let value = typeof this.values === 'function' ? this.values : this.values[ this.__valuesPhase++ % this.values.length ] if( typeof value === 'function' && this.target === undefined ) { value() }else if( typeof this.target[ this.key ] === 'function' ) { if( typeof value === 'function' ) { value = value() } if( value !== this.DNR ) { this.target[ this.key ]( value ) } }else{ if( typeof value === 'function' ) value = value() if( value !== this.DNR ) this.target[ this.key ] = value } } }) // XXX we need to implement priority, which will in turn determine the order // that the sequencers are added to the callback function. const Seq2 = { create( inputProps ) { const seq = Object.create( __proto__ ), properties = Object.assign({}, Seq2.defaults, inputProps ) seq.phase = 0 seq.inputNames = [ 'rate', 'density' ] seq.inputs = [ 1, 1 ] seq.nextTime = 0 seq.__valuesPhase = 0 seq.__timingsPhase = 0 seq.id = Gibberish.factory.getUID() seq.dirty = true seq.type = 'seq' seq.__addresses__ = {} seq.DNR = -987654321 properties.id = Gibberish.factory.getUID() Object.assign( seq, properties ) seq.__properties__ = properties // support for sequences that are triggered via other means, // in Gibber this is when you provide timing to one sequence // on an object and want to use that one pattern to trigger // multiple sequences. if( seq.timings === null ) { seq.nextTime = Infinity } // XXX this needs to be optimized as much as humanly possible, since it's running at audio rate... seq.callback = function( rate, density ) { while( seq.phase >= seq.nextTime ) { let value = typeof seq.values === 'function' ? seq.values : seq.values[ seq.__valuesPhase++ % seq.values.length ], shouldRun = true let timing = null if( seq.timings !== null && seq.timings !== undefined ) { timing = typeof seq.timings === 'function' ? seq.timings : seq.timings[ seq.__timingsPhase++ % seq.timings.length ] if( typeof timing === 'function' ) timing = timing() } let shouldIncreaseSpeed = density <= 1 ? false : true // XXX this supports an edge case in Gibber, where patterns like Euclid / Hex return // objects indicating both whether or not they should should trigger values as well // as the next time they should run. perhaps this could be made more generalizable? if( timing !== null && typeof timing === 'object' ) { if( timing.shouldExecute === 1 ) { shouldRun = true }else{ shouldRun = false } timing = timing.time }else if( timing !== null ) { if( Math.random() >= density ) shouldRun = false } if( shouldRun ) { if( seq.mainthreadonly !== undefined ) { if( typeof value === 'function' ) { value = value() } Gibberish.processor.messages.push( seq.mainthreadonly, seq.key, value ) }else if( typeof value === 'function' && seq.target === undefined ) { value() }else if( typeof seq.target[ seq.key ] === 'function' ) { if( typeof value === 'function' ) { value = value() } if( value !== seq.DNR ) { seq.target[ seq.key ]( value ) } }else{ if( typeof value === 'function' ) value = value() if( value !== seq.DNR ) seq.target[ seq.key ] = value } } if( timing === null ) return seq.phase -= seq.nextTime if( shouldIncreaseSpeed ) { timing = Math.random() > (2 - density) ? timing / 2 : timing } seq.nextTime = timing } seq.phase += rate return 0 } seq.ugenName = seq.callback.ugenName = 'seq_' + seq.id // since we're not passing our sequencer through the ugen template, we need // to grab a memory address for its rate so it can be sequenced and define // a property that manipulates that memory address. const idx = Gibberish.memory.alloc( 1 ) Gibberish.memory.heap[ idx ] = seq.rate seq.__addresses__.rate = idx let value = seq.rate Object.defineProperty( seq, 'rate', { get() { return value }, set( v ) { if( value !== v ) { if( typeof v === 'number' ) Gibberish.memory.heap[ idx ] = v Gibberish.dirty( Gibberish.analyzers ) value = v } } }) const didx = Gibberish.memory.alloc( 1 ) Gibberish.memory.heap[ didx ] = seq.density seq.__addresses__.density = didx let dvalue = seq.density Object.defineProperty( seq, 'density', { get() { return dvalue }, set( v ) { if( dvalue !== v ) { if( typeof v === 'number' ) Gibberish.memory.heap[ didx ] = v Gibberish.dirty( Gibberish.analyzers ) dvalue = v } } }) if( Gibberish.mode === 'worklet' ) { Gibberish.utilities.createPubSub( seq ) } return proxy( ['Sequencer2'], properties, seq ) } } Seq2.defaults = { rate: 1, density:1, priority:0, phase:0 } Seq2.create.DO_NOT_OUTPUT = -987654321 return Seq2.create }