├── MusicSource ├── host.js ├── lib │ ├── math.js │ ├── nodes │ │ ├── basic.js │ │ ├── faust.js │ │ ├── loader.js │ │ ├── music.js │ │ ├── params.js │ │ ├── smooth.js │ │ └── teanode.js │ ├── patch │ │ ├── graph.js │ │ ├── seq.js │ │ ├── seqTable.js │ │ └── topo.js │ └── tealib.js ├── patches │ ├── contrabass │ │ ├── faust │ │ │ ├── contrabass.dsp │ │ │ └── post.dsp │ │ └── main.js │ ├── harmonic-series │ │ ├── faust │ │ │ └── harmo.dsp │ │ └── main.js │ ├── melodic-bottle │ │ ├── faust │ │ │ ├── bottle.dsp │ │ │ └── post.dsp │ │ └── main.js │ ├── melodic-soprano │ │ ├── faust │ │ │ ├── post.dsp │ │ │ └── soprano.dsp │ │ └── main.js │ ├── moon-strings │ │ ├── faust │ │ │ ├── pluck.dsp │ │ │ └── post.dsp │ │ └── main.js │ ├── ocean-theme │ │ ├── faust │ │ │ ├── bass.dsp │ │ │ ├── post.dsp │ │ │ └── string.dsp │ │ └── main.js │ ├── rain-stick │ │ ├── faust │ │ │ └── stick.dsp │ │ └── main.js │ ├── resonant-drone │ │ ├── faust │ │ │ ├── post.dsp │ │ │ └── reso.dsp │ │ └── main.js │ ├── sine-drone │ │ ├── faust │ │ │ └── post.dsp │ │ └── main.js │ ├── soprano-drone │ │ ├── faust │ │ │ ├── post.dsp │ │ │ └── soprano.dsp │ │ └── main.js │ ├── sparse-bottle │ │ ├── faust │ │ │ ├── bottle.dsp │ │ │ └── post.dsp │ │ └── main.js │ ├── sparse-soprano │ │ ├── faust │ │ │ ├── post.dsp │ │ │ └── soprano.dsp │ │ └── main.js │ ├── vibraphones │ │ ├── faust │ │ │ ├── post.dsp │ │ │ └── vib.dsp │ │ └── main.js │ ├── vocal-overtones │ │ ├── faust │ │ │ ├── post.dsp │ │ │ └── tuvan.dsp │ │ └── main.js │ ├── water-bell │ │ ├── faust │ │ │ ├── modal.dsp │ │ │ └── post.dsp │ │ └── main.js │ ├── wind-bells │ │ ├── faust │ │ │ ├── chime.dsp │ │ │ └── post.dsp │ │ └── main.js │ ├── wind-chimes │ │ ├── faust │ │ │ ├── chime.dsp │ │ │ └── post.dsp │ │ └── main.js │ └── wind-theme │ │ ├── faust │ │ ├── bass.dsp │ │ ├── post.dsp │ │ └── reed.dsp │ │ └── main.js └── tracks │ ├── 01. diffuse_process.tmac │ ├── 02. reflecting_strings.tmac │ ├── 03. fundamental_breath.tmac │ ├── 04. voiceless_drift.tmac │ ├── 05. distant_timescales.tmac │ ├── 06. occultation.tmac │ ├── 07. resonant_cycle.tmac │ └── 08. passing_tides.tmac ├── Readme.md ├── Scripts ├── CourierPrime-Subset.ttf ├── cover-preview.html ├── cover.js ├── deps.js ├── mastering.js └── overlap.js ├── deno.json └── render.js /MusicSource/host.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module to communicate with the host environment that's running the patches. 3 | * This module can be loaded both by the host and by the patches. This allows 4 | * communication and synchronization beyond the basic "process" function 5 | * exported by patches. 6 | * 7 | * In the browser, the host is an AudioWorklet. In the command line, the host is 8 | * a module loaded by Deno. These can be found in the Teasynth project, under 9 | * core/worklet.js and cli/render.js. 10 | */ 11 | 12 | /** 13 | * @typedef { 14 | * 'got host' 15 | * | 'can read files' 16 | * | 'can init faust' 17 | * | 'done faust init' 18 | * | 'can deduce channels' 19 | * | 'can make processors' 20 | * | 'can play' 21 | * | 'dispose' } HostEventType 22 | */ 23 | 24 | export class EventTarget { 25 | constructor() { 26 | this.handlers = {}; 27 | } 28 | /** @param {HostEventType} eventType */ 29 | on(eventType, handler) { 30 | const handlers = this.handlers[eventType] ?? (this.handlers[eventType] = new Set()); 31 | handlers.add(handler); 32 | } 33 | /** @param {HostEventType} eventType */ 34 | off(eventType, handler) { 35 | let handlers = this.handlers[eventType]; 36 | if (!handlers) throw new Error('Tried removing non-existing handler for ' + eventType); 37 | handlers.delete(handler); 38 | } 39 | /** @param {HostEventType} eventType */ 40 | async trigger(eventType, arg) { 41 | let handlers = this.handlers[eventType]; 42 | if (!handlers) return; 43 | for (let handler of handlers) { 44 | await handler(arg); 45 | } 46 | } 47 | } 48 | 49 | const fileCache = {}; 50 | 51 | /** 52 | * Each patch is played by a host. It could be a web player, another patch, 53 | * or a command line player. 54 | */ 55 | export class Host { 56 | constructor() { 57 | this.events = new EventTarget(); 58 | /** 59 | * Host-controlled parameters. Example: 60 | * this.params['pitch'] = { 61 | * setFn(val) { doSomethingWith(val); }, 62 | * def: 0, min: 0, max: 1 63 | * }; 64 | */ 65 | this.params = {}; 66 | this.sampleRate = 44100; 67 | } 68 | /** 69 | * Fetch the contents of a file relative to the currently running main.js. 70 | * Can only be called after initialization. 71 | */ 72 | async fetchMainRelative(path) { 73 | if (!this.initialized) { 74 | throw new Error(`Couldn't request ${path}, mainHost not initialized!`); 75 | } 76 | return ''; 77 | } 78 | /** 79 | * Get the cached contents of a file relative to the currently running 80 | * main.js. Can only be called after initialization. 81 | */ 82 | async getMainRelative(path) { 83 | if (!(path in fileCache)) fileCache[path] = await this.fetchMainRelative(path); 84 | return fileCache[path]; 85 | } 86 | /** 87 | * Compile Faust code to Wasm. 88 | * Can only be called after initialization. 89 | * 90 | * @param {String} code 91 | * @param {Boolean} internalMemory 92 | */ 93 | async compileFaust(code, internalMemory) { 94 | if (!this.initialized) { 95 | throw new Error(`Couldn't compile Faust, mainHost not initialized!`); 96 | } 97 | return {}; 98 | } 99 | /** 100 | * Initialize the patch. Called before playback. 101 | */ 102 | async init() { 103 | await this.events.trigger('got host'); 104 | await this.events.trigger('can read files'); 105 | await this.events.trigger('can init faust'); 106 | await this.events.trigger('done faust init'); 107 | await this.events.trigger('can deduce channels'); 108 | await this.events.trigger('can make processors'); 109 | await this.events.trigger('can play'); 110 | } 111 | }; 112 | 113 | export const mainHost = new Host(); 114 | -------------------------------------------------------------------------------- /MusicSource/lib/math.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Returns a PRNG function roughly similar to Math.random. 4 | * Based on "Mulberry32" by Tommy Ettinger. 5 | */ 6 | export const randomSeed = (seed=0) => () => { 7 | seed = seed + 1831565813|0; 8 | let t = Math.imul(seed^seed>>>15, 1|seed); 9 | t = t+Math.imul(t^t>>>7, 61|t)^t; 10 | return ((t^t>>>14)>>>0)/2**32; 11 | }; 12 | 13 | /** 14 | * Returns a random number in [0,1] biased towards tgt (also in [0,1]). 15 | * 16 | * Bias=0: No effect on random distribution. 17 | * Bias=inf: Theoretically only tgt comes out. 18 | */ 19 | export const biasedRandom = (tgt, bias, f=Math.random) => { 20 | const x = f(); 21 | const r = Math.pow(x, bias); 22 | return (x*100%1)*r + tgt*(1-r); 23 | }; 24 | 25 | /** 26 | * Returns a rhythm array, emphasizing beats repeating along the given cycle 27 | * lengths. 28 | */ 29 | export const rhythmArray = ({len=16, minOut=0, maxOut=1, cycles=[2,4,8,16,32,64,128]}) => { 30 | const nCycles = cycles.findIndex(x => x >= len); 31 | const incTgt = (maxOut - minOut)/(nCycles !== -1 ? nCycles : cycles.length); 32 | const ret = new Array(len).fill(minOut); 33 | for (const c of cycles) { 34 | if (c >= len) break; 35 | for (let pos=0; pos a.map(x => biasedRandom(x, bias, f)); 41 | 42 | const gold = (3 - Math.sqrt(5)) / 2; 43 | 44 | /** 45 | * Returns a function giving evenly-spaced floats between 0 and 1, with an 46 | * optional starting value. 47 | */ 48 | export const goldSeed = (x=0) => () => { 49 | x = (x+gold) % 1; 50 | return x; 51 | }; 52 | 53 | const toFraction = (x, tolerance, iterations) => { 54 | let num = 1, 55 | den = 1, 56 | i = 0; 57 | const iterate = () => { 58 | const R = num / den; 59 | if (Math.abs((R - x) / x) < tolerance) return; 60 | if (R < x) num++; 61 | else den++; 62 | if (++i < iterations) iterate(); 63 | }; 64 | iterate(); 65 | if (i < iterations) return [num, den]; 66 | }; 67 | 68 | const fuzzyEq = (x, y) => Math.max(x, y) / Math.min(x, y) < 1.001; 69 | 70 | // Helper for "mixFreqs" to find related fractions 71 | const getMixer = locality => { 72 | const dissonance = (a, b) => Math.round(a / 2 + b + (locality * Math.max(a, b)) / Math.min(a, b)); 73 | const base = []; 74 | for (let a = 1; a < 20; a++) { 75 | for (let b = 1; b < 10; b++) { 76 | const x = a / b; 77 | if (base.some(prev => fuzzyEq(prev[0], x))) { 78 | continue; 79 | } 80 | const diss = dissonance(a, b); 81 | base.push([x, diss]); 82 | } 83 | } 84 | const related = x => base.map(ele => [ele[0] * x, ele[1]]); 85 | const mix = (rel1, rel2) => { 86 | const ret = []; 87 | for (let ele1 of rel1) { 88 | const ele2 = rel2.find(e => fuzzyEq(e[0], ele1[0])); 89 | if (!ele2) continue; 90 | ret.push([ele1[0], ele1[1] * ele2[1]]); 91 | } 92 | ret.sort((a, b) => a[1] - b[1]); 93 | return ret; 94 | }; 95 | return { related, mix }; 96 | }; 97 | 98 | /** 99 | * Get frequencies consonant with both input frequencies in just intonation. 100 | * @param locality - How much to clump around the inputs 101 | */ 102 | export const mixFreqs = (freq1, freq2, locality) => { 103 | [freq1, freq2] = [Math.min(freq1, freq2), Math.max(freq1, freq2)]; 104 | const mixer = getMixer(locality); 105 | const frac = toFraction(freq2/freq1, 0.001, 50); 106 | // console.log('Mixer relative fraction: ', frac); 107 | const rel1 = mixer.related(1); 108 | const rel2 = mixer.related(frac[0] / frac[1]); 109 | const mixed = mixer.mix(rel1, rel2); 110 | return mixed.map(ele => ele[0]*freq1); 111 | }; 112 | -------------------------------------------------------------------------------- /MusicSource/lib/nodes/basic.js: -------------------------------------------------------------------------------- 1 | import { TeaNode } from './teanode.js'; 2 | 3 | /** 4 | * A single OutputNode should be accessible as Graph::out. 5 | */ 6 | export class OutputNode extends TeaNode { 7 | constructor() { 8 | super(); 9 | this.on('can deduce channels', () => { 10 | for (const node of this.inNodes) { 11 | if (node.numOutChannels === 2) { 12 | this.numInChannels = 2; 13 | this.numOutChannels = 2; 14 | } 15 | } 16 | }); 17 | } 18 | describe() { 19 | return 'output'; 20 | } 21 | } 22 | 23 | /** 24 | * Extend this class to process audio per-sample without state. 25 | * `processSample` will be applied to every sample of every incoming channel. 26 | * The default I/O channels are based on the connected input nodes. 27 | * Use this for effects like distortion, amplitude, etc. 28 | */ 29 | export class SampleProcessor extends TeaNode { 30 | constructor(fn) { 31 | super(); 32 | if (fn) this.processSample = fn; 33 | this.on('can deduce channels', () => this.useInputChannelCount()); 34 | } 35 | /** 36 | * Override this to process audio. 37 | * @param {Number} sample 38 | */ 39 | processSample(sample) { 40 | return sample; 41 | } 42 | /** 43 | * @param {Array.} frame 44 | * @returns {Array.} 45 | */ 46 | process(frame) { 47 | return frame.map(s => this.processSample(s)); 48 | } 49 | } 50 | 51 | /** 52 | * Extend this class to combine samples from each input node. 53 | * mixSamples is called for each channel, and gets one sample per 54 | * input node. 55 | * The default I/O channels are based on the connected input nodes. 56 | * Use this for operators like multiply, etc. 57 | */ 58 | export class SampleMixer extends TeaNode { 59 | constructor() { 60 | super(); 61 | this.on('can deduce channels', () => this.useInputChannelCount()); 62 | } 63 | /** 64 | * Override this to mix audio. 65 | * @param {Array.} inputs 66 | */ 67 | mixSamples(inputs) { 68 | return inputs.reduce((a, b) => a + b, 0); 69 | } 70 | /** 71 | * @param {Array.>} frames 72 | * @returns {Array.} 73 | */ 74 | mix(frames) { 75 | const ret = Array(this.numInChannels); 76 | for (let chani = 0; chani < this.numInChannels; chani++) { 77 | ret[chani] = this.mixSamples(frames.map(f => f[chani])); 78 | } 79 | return ret; 80 | } 81 | } 82 | 83 | /** 84 | * Extend this class to process audio per-sample with state. 85 | * `processChannel` will be applied to every incoming channel. 86 | * For example: delay, filters, etc. 87 | * Override `initChannel` instead of `init`. 88 | */ 89 | export class ChannelProcessor extends TeaNode { 90 | constructor() { 91 | super(); 92 | this.on('can deduce channels', () => { 93 | this.useInputChannelCount(); 94 | this.channelStates = Array.from({ length: this.numInChannels }, () => {}); 95 | }); 96 | } 97 | /** 98 | * Override this to process audio. Store state in the channel object. 99 | * @param {Object} channel 100 | * @param {Number} sample 101 | */ 102 | processChannel(_channel, sample) { 103 | return sample; 104 | } 105 | /** 106 | * Override to initialize state. Store state in the channel object. 107 | * @param {Object} channel 108 | */ 109 | initChannel(_channel) {} 110 | /** @param {import('../patch/graph.js').Graph} graph */ 111 | baseInit(graph) { 112 | super.baseInit(graph); 113 | this.useInputChannelCount(); 114 | } 115 | /** 116 | * @param {Array.} frame 117 | * @returns {Array.} 118 | */ 119 | process(frame) { 120 | return this.channelStates.map((cs, idx) => this.processChannel(cs, frame[idx])); 121 | } 122 | } 123 | 124 | /** 125 | * Gain nodes can be created directly, or are automatically inserted 126 | * when calling node1.connectWithGain(node2). Gain nodes with 1 input 127 | * node and 1 output node are `connection nodes`, and are expected by 128 | * Graph convenience methods (get/mute/unmute)Connection. 129 | */ 130 | export class Gain extends SampleProcessor { 131 | /** 132 | * @param {Number} initialGain 133 | */ 134 | constructor(initialGain = 1) { 135 | super(); 136 | this.gain = this.addParam(initialGain); 137 | } 138 | processSample(s) { 139 | return s * this.gain.value; 140 | } 141 | } 142 | 143 | // Triangular panning node. 144 | export class TriPan extends TeaNode { 145 | constructor() { 146 | super(); 147 | this.numInChannels = 2; 148 | this.numOutChannels = 2; 149 | this.pan = this.addParam(0.5); 150 | } 151 | /** 152 | * @param {Array.} frame 153 | * @returns {Array.} 154 | */ 155 | process(frame) { 156 | const p = this.pan.value; 157 | return [ 158 | p<.5 ? frame[0] : frame[0]*(1-p)*2, 159 | p>.5 ? frame[1] : frame[1]*p*2, 160 | ]; 161 | } 162 | } 163 | 164 | // Multiply node 165 | export class Mul extends SampleMixer { 166 | mixSamples(inputs) { 167 | return inputs.reduce((a, b) => a * b); 168 | } 169 | } 170 | 171 | // Number node - usually not necessary but could be convenient 172 | export class NumNode extends TeaNode { 173 | constructor(initValue = 0) { 174 | super(); 175 | this.num = this.addParam(initValue); 176 | this.bangin = false; 177 | } 178 | bang() { 179 | this.bangin = true; 180 | } 181 | process() { 182 | if (this.bangin) { 183 | this.bangin = false; 184 | return [1]; 185 | } 186 | return [this.num.value]; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /MusicSource/lib/nodes/faust.js: -------------------------------------------------------------------------------- 1 | import { TeaNode } from './teanode.js'; 2 | import { mainHost } from '../../host.js'; 3 | 4 | // Extract parameters from Faust-generated metadata 5 | const findParams = meta => { 6 | const param2index = {}; 7 | const visit = obj => { 8 | let { address, index, label, type } = obj; 9 | if (address && label && type) { 10 | param2index[label] = index; 11 | return; 12 | } 13 | if (!Array.isArray(obj) && !obj.type) return; 14 | Object.values(obj).forEach(visit); 15 | }; 16 | visit(meta.ui); 17 | return param2index; 18 | }; 19 | 20 | // Imports to pass to Faust WebAssembly modules 21 | const importObject = mem => ({ 22 | env: { 23 | memory: mem, 24 | memoryBase: 0, 25 | tableBase: 0, 26 | _abs: Math.abs, 27 | // Float version 28 | _acosf: Math.acos, 29 | _asinf: Math.asin, 30 | _atanf: Math.atan, 31 | _atan2f: Math.atan2, 32 | _ceilf: Math.ceil, 33 | _cosf: Math.cos, 34 | _expf: Math.exp, 35 | _floorf: Math.floor, 36 | _fmodf: (x, y) => x % y, 37 | _logf: Math.log, 38 | _log10f: Math.log10, 39 | _max_f: Math.max, 40 | _min_f: Math.min, 41 | _remainderf: (x, y) => x - Math.round(x / y) * y, 42 | _powf: Math.pow, 43 | _roundf: Math.fround, 44 | _sinf: Math.sin, 45 | _sqrtf: Math.sqrt, 46 | _tanf: Math.tan, 47 | _acoshf: Math.acosh, 48 | _asinhf: Math.asinh, 49 | _atanhf: Math.atanh, 50 | _coshf: Math.cosh, 51 | _sinhf: Math.sinh, 52 | _tanhf: Math.tanh, 53 | // Double version 54 | _acos: Math.acos, 55 | _asin: Math.asin, 56 | _atan: Math.atan, 57 | _atan2: Math.atan2, 58 | _ceil: Math.ceil, 59 | _cos: Math.cos, 60 | _exp: Math.exp, 61 | _floor: Math.floor, 62 | _fmod: (x, y) => x % y, 63 | _log: Math.log, 64 | _log10: Math.log10, 65 | _max_: Math.max, 66 | _min_: Math.min, 67 | _remainder: (x, y) => x - Math.round(x / y) * y, 68 | _pow: Math.pow, 69 | _round: Math.fround, 70 | _sin: Math.sin, 71 | _sqrt: Math.sqrt, 72 | _tan: Math.tan, 73 | _acosh: Math.acosh, 74 | _asinh: Math.asinh, 75 | _atanh: Math.atanh, 76 | _cosh: Math.cosh, 77 | _sinh: Math.sinh, 78 | _tanh: Math.tanh, 79 | table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' }), 80 | }, 81 | }); 82 | 83 | /** 84 | * Create a WebAssembly module from the given Faust source code. Instancing 85 | * (a Faust feature) saves resources by placing multiple instances of the same 86 | * Faust processor in one module. 87 | */ 88 | const initFaustModule = async (source, numInstances) => { 89 | const { ui8Code, dspMeta } = await mainHost.compileFaust(source); 90 | const mod = { 91 | numInChannels: dspMeta.inputs, 92 | numOutChannels: dspMeta.outputs, 93 | param2index: findParams(dspMeta), 94 | instances: [], 95 | }; 96 | let sz = mod.numInChannels * 8 + mod.numOutChannels * 8; 97 | sz += dspMeta.size * numInstances; 98 | sz = Math.ceil(sz / 65536); 99 | const memory = new WebAssembly.Memory({ initial: sz, maximum: sz }); 100 | const dspModule = await WebAssembly.compile(ui8Code); 101 | if (!dspModule) throw new Error('Faust DSP factory cannot be compiled'); 102 | const dspInstance = await WebAssembly.instantiate(dspModule, importObject(memory)); 103 | mod.factory = dspInstance.exports; 104 | let mempos = 0; 105 | const HEAP32 = new Int32Array(memory.buffer); 106 | const HEAPF32 = new Float32Array(memory.buffer); 107 | for (let i = 0; i < numInstances; i++) { 108 | const dspOfs = mempos; 109 | mempos += dspMeta.size; 110 | const inPtrsOfs = mempos; 111 | mempos += mod.numInChannels * 4; 112 | const outPtrsOfs = mempos; 113 | mempos += mod.numOutChannels * 4; 114 | const inBufOfs = mempos; 115 | mempos += mod.numInChannels * 4; 116 | const outBufOfs = mempos; 117 | mempos += mod.numOutChannels * 4; 118 | for (let i = 0; i < mod.numInChannels; i++) { 119 | HEAP32[inPtrsOfs / 4 + i] = inBufOfs + i * 4; 120 | } 121 | for (let i = 0; i < mod.numOutChannels; i++) { 122 | HEAP32[outPtrsOfs / 4 + i] = outBufOfs + i * 4; 123 | } 124 | mod.factory.init(dspOfs, mainHost.sampleRate); 125 | mod.instances.push({ 126 | dspOfs, 127 | inPtrsOfs, 128 | outPtrsOfs, 129 | module: mod, 130 | inView: HEAPF32.subarray(inBufOfs / 4, inBufOfs / 4 + mod.numInChannels), 131 | outView: HEAPF32.subarray(outBufOfs / 4, outBufOfs / 4 + mod.numOutChannels), 132 | }); 133 | } 134 | return mod; 135 | }; 136 | 137 | /** 138 | * Cache of Faust modules to create. This accumulates instances of each Faust 139 | * source file before creating any modules. 140 | */ 141 | export const faustCache = { 142 | reservedPaths: [], 143 | reservedSources: {}, 144 | modules: {}, 145 | reservePath({ path, host = mainHost, node, n = 1 }) { 146 | this.reservedPaths.push({ path, host, node, n }); 147 | }, 148 | reserveSource(src, n = 1) { 149 | if (!this.reservedSources[src]) this.reservedSources[src] = 0; 150 | this.reservedSources[src] += n; 151 | }, 152 | takeInstance(source, node) { 153 | for (let inst of this.modules[source].instances) { 154 | if (inst.owner) continue; 155 | inst.owner = node; 156 | return inst; 157 | } 158 | }, 159 | returnInstance(instance) { 160 | if (!instance.owner) throw new Error('Returned an orphan Faust instance!'); 161 | instance.owner = undefined; 162 | instance.module.factory.instanceInit(instance.dspOfs, mainHost.sampleRate); 163 | }, 164 | }; 165 | 166 | mainHost.events.on('can read files', async () => { 167 | for (let res of faustCache.reservedPaths) { 168 | const host = res.host ?? res.node.host; 169 | const src = await host.getMainRelative(res.path); 170 | faustCache.reserveSource(src, res.n); 171 | } 172 | }); 173 | 174 | mainHost.events.on('can init faust', async () => { 175 | for (let [src, i] of Object.entries(faustCache.reservedSources)) { 176 | faustCache.modules[src] = await initFaustModule(src, i); 177 | } 178 | }); 179 | 180 | /** 181 | * Faust processor node using the given Faust source code. Upon construction, 182 | * instances are counted globally. Wasm modules are then created before 183 | * playback starts. 184 | */ 185 | export class FaustNode extends TeaNode { 186 | constructor(path, params = {}) { 187 | super(); 188 | this.path = path; 189 | faustCache.reservePath({ path, node: this }); 190 | this.on('done faust init', async () => { 191 | this.source = await this.host.getMainRelative(this.path); 192 | this.module = faustCache.modules[this.source]; 193 | if (!this.module) throw new Error('Never built faust module for ' + this.describe()); 194 | this.numInChannels = this.module.numInChannels; 195 | this.numOutChannels = this.module.numOutChannels; 196 | this.instance = faustCache.takeInstance(this.source, this); 197 | if (!this.instance) throw new Error('Out of Faust instances: ' + this.path); 198 | for (let usedParam of this.usedParams) { 199 | if (!(usedParam.name in this.module.param2index)) { 200 | throw new Error(`Param "${usedParam.name}" does not exist in ${this.describe()}!`); 201 | } 202 | usedParam.faustIndex = this.module.param2index[usedParam.name]; 203 | } 204 | }); 205 | this.on('dispose', () => { 206 | faustCache.returnInstance(this.instance); 207 | }); 208 | this.usedParams = []; 209 | for (let [k, v] of Object.entries(params)) { 210 | this[k] = this.faustParam(k, v); 211 | } 212 | } 213 | faustParam(name, value) { 214 | let teaParam; 215 | if (typeof value == 'number') { 216 | teaParam = this.addParam(value); 217 | } else if (value instanceof TeaNode) { 218 | teaParam = this.addParam(0); 219 | value.connect(teaParam); 220 | } else { 221 | throw new Error(`Tried connecting ${value} as parameter ${name}!`); 222 | } 223 | this.usedParams.push({ name, teaParam }); 224 | return teaParam; 225 | } 226 | process(frame) { 227 | if (this.muted) return this.instance.outView; 228 | this.usedParams.forEach(p => 229 | this.module.factory.setParamValue(this.instance.dspOfs, p.faustIndex, p.teaParam.value) 230 | ); 231 | if (this.module.numInChannels > 0) { 232 | this.instance.inView.set(frame); 233 | } 234 | this.module.factory.compute(this.instance.dspOfs, 1, this.instance.inPtrsOfs, this.instance.outPtrsOfs); 235 | return this.instance.outView; 236 | } 237 | describe() { 238 | return this.path; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /MusicSource/lib/nodes/loader.js: -------------------------------------------------------------------------------- 1 | import { TeaNode } from './teanode.js'; 2 | import { EventTarget } from '../../host.js'; 3 | 4 | /** 5 | * Node to load other patches. Experimental. 6 | */ 7 | export class LoaderNode extends TeaNode { 8 | /** 9 | * @param {string} mainPath 10 | */ 11 | constructor(mainPath, instantiate) { 12 | super(); 13 | if (!mainPath.endsWith('/main.js')) { 14 | throw new Error('invalid path to subpatch main file: ' + mainPath); 15 | } 16 | this.mainPath = mainPath; 17 | this.instantiate = instantiate; 18 | this.instances = new Set(); 19 | } 20 | add(params = {}) { 21 | const instance = { paused: false }; 22 | instance.started = this.initInstance(instance, params); 23 | return instance; 24 | } 25 | async initInstance(instance, params) { 26 | const parentNode = this; 27 | instance.host = { 28 | async getMainRelative(path) { 29 | const joined = parentNode.mainPath.slice(0, -7) + path; 30 | return await parentNode.host.getMainRelative(joined); 31 | }, 32 | events: new EventTarget(), 33 | async init() { 34 | await this.events.trigger('got host'); 35 | await this.events.trigger('can read files'); 36 | // await this.events.trigger('can init faust'); 37 | await this.events.trigger('done faust init'); 38 | await this.events.trigger('can deduce channels'); 39 | await this.events.trigger('can make processors'); 40 | await this.events.trigger('can play'); 41 | }, 42 | params: {}, 43 | sampleRate: parentNode.host.sampleRate, 44 | }; 45 | if (this.hostProps) Object.assign(instance.host, this.hostProps); 46 | instance.process = await this.instantiate(instance.host); 47 | await instance.host.init(); 48 | for (let [k, v] of Object.entries(params)) { 49 | instance.host.params[k].setFn(v); 50 | } 51 | this.instances.add(instance); 52 | console.log('worklet: done starting subpatch ' + this.mainPath); 53 | return instance; 54 | } 55 | async remove(instance) { 56 | if (!this.instances.has(instance)) throw new Error('Removed an orphan Loader instance!'); 57 | await instance.host.events.trigger('dispose'); 58 | this.instances.delete(instance); 59 | console.log('worklet: stopped subpatch ' + this.mainPath); 60 | } 61 | /** 62 | * @param {Array.} frame 63 | * @returns {Array.} 64 | */ 65 | process(frame) { 66 | const ret = [0, 0]; 67 | for (let inst of this.instances) { 68 | if (inst.paused) continue; 69 | const out = inst.process(); 70 | ret[0] += out[0]; 71 | ret[1] += out[1]; 72 | } 73 | return ret; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /MusicSource/lib/nodes/music.js: -------------------------------------------------------------------------------- 1 | import { TeaNode } from './teanode.js'; 2 | 3 | // White noise node 4 | export class Noise extends TeaNode { 5 | constructor({ amp = 1 } = {}) { 6 | super(); 7 | this.amp = this.addParam(amp); 8 | } 9 | process() { 10 | return [(Math.random() - 0.5) * this.amp.value]; 11 | } 12 | } 13 | 14 | const tau = 2 * Math.PI; 15 | 16 | // Sine wave node 17 | export class Sine extends TeaNode { 18 | constructor({ amp = 1, freq = 440, phase = 0 } = {}) { 19 | super(); 20 | this.amp = this.addParam(amp); 21 | this.freq = this.addParam(freq); 22 | this.pos = phase / tau; 23 | } 24 | process() { 25 | this.pos += (this.freq.value * tau) / this.sampleRate; 26 | if (this.pos > tau) this.pos -= tau; 27 | return [Math.sin(this.pos) * this.amp.value]; 28 | } 29 | } 30 | 31 | // Control-rate sine wave (LFO) node 32 | export class CtrlSine extends TeaNode { 33 | constructor({ amp = 1, freq = 440, phase = 0 } = {}) { 34 | super(); 35 | this.amp = this.addParam(amp); 36 | this.freq = this.addParam(freq); 37 | this.pos = phase / tau; 38 | this.ctrlOut = 0; 39 | } 40 | ctrlRateProcess() { 41 | this.pos += ((this.freq.value * tau) / this.sampleRate) * 128; 42 | if (this.pos > tau) this.pos -= tau; 43 | this.ctrlOut = Math.sin(this.pos) * this.amp.value; 44 | } 45 | process() { 46 | return [this.ctrlOut]; 47 | } 48 | } 49 | 50 | // Polyphonic node with managed voices 51 | export class Poly { 52 | constructor(n, makeVoice, out) { 53 | this.voices = [...Array(n).keys()].map(makeVoice); 54 | this.voices.forEach(v => v.connect(out)); 55 | this.order = [...Array(n).keys()]; 56 | } 57 | note(freq, ...args) { 58 | const vn = this.order.pop(); 59 | this.voices[vn].note(freq, ...args); 60 | this.voices[vn].polyFreq = freq; 61 | this.order.unshift(vn); 62 | } 63 | off(freq) { 64 | for (const voice of this.voices) { 65 | if (!voice.polyFreq) continue; 66 | if (!freq || voice.polyFreq === freq) { 67 | voice.off(); 68 | voice.polyFreq = 0; 69 | } 70 | } 71 | } 72 | forEach(fn) { 73 | this.voices.forEach(fn); 74 | } 75 | } 76 | 77 | // Metronome node that sends a "1" value regularly 78 | export class Metronome extends TeaNode { 79 | constructor({ bpm }) { 80 | super(); 81 | this.bpm = bpm; 82 | this.on('got host', () => { 83 | this.sampleInterval = (this.sampleRate * 60) / this.bpm; 84 | this.sampleCounter = this.sampleInterval - 0.5; 85 | }); 86 | } 87 | process() { 88 | this.sampleCounter++; 89 | if (this.sampleCounter >= this.sampleInterval) { 90 | this.sampleCounter -= this.sampleInterval; 91 | return [1]; 92 | } else { 93 | return [0]; 94 | } 95 | } 96 | } 97 | 98 | // Attack-Release Envelope node 99 | export class AREnv extends TeaNode { 100 | constructor({ attack, release }) { 101 | super(); 102 | this.attack = attack; 103 | this.release = release; 104 | this.on('got host', () => { 105 | this.attSamples = this.attack * this.sampleRate; 106 | this.relSamples = this.release * this.sampleRate; 107 | this.attPos = this.attSamples; 108 | this.relPos = this.relSamples; 109 | }); 110 | } 111 | process(trig) { 112 | if (trig[0]) { 113 | this.attPos = 0; 114 | this.relPos = 0; 115 | } 116 | if (this.attPos < this.attSamples) { 117 | const ret = this.attPos / this.attSamples; 118 | this.attPos++; 119 | return [ret * ret * ret]; 120 | } else if (this.relPos < this.relSamples) { 121 | const ret = 1 - this.relPos / this.relSamples; 122 | this.relPos++; 123 | return [ret * ret * ret]; 124 | } else { 125 | return [0]; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /MusicSource/lib/nodes/params.js: -------------------------------------------------------------------------------- 1 | import { TeaNode } from './teanode.js'; 2 | 3 | // Externally controlled parameter for patches 4 | export class HostParam extends TeaNode { 5 | constructor(name, { def = 0, min, max } = {}) { 6 | super(); 7 | let defStr; 8 | if ((typeof def) === 'string') { 9 | defStr = def; 10 | def = Function(`"use strict"; return parseFloat(${def})`)(); 11 | if (min === undefined) min = Number.MIN_VALUE; 12 | if (max === undefined) max = Number.MAX_VALUE; 13 | } else { 14 | if (min === undefined) min = 0; 15 | if (max === undefined) max = 1; 16 | } 17 | this.value = def; 18 | this._changed = true; 19 | const setFn = value => { 20 | this.value = value; 21 | this.host.params[name].val = value; 22 | this._changed = true; 23 | }; 24 | this.on('got host', () => { 25 | this.host.params[name] = { setFn, def, defStr, min, max }; 26 | }); 27 | } 28 | process() { 29 | return [this.value]; 30 | } 31 | changed() { 32 | const ret = this._changed 33 | this._changed = false; 34 | return ret; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MusicSource/lib/nodes/smooth.js: -------------------------------------------------------------------------------- 1 | import { SampleProcessor } from './basic.js'; 2 | 3 | // Non-node-based smoothing that can be used internally by a class 4 | export class PrivateSmooth { 5 | /** @param {import('./teanode.js').NodeParam} param */ 6 | constructor(param, { init = param.value, speed = 0.01 } = {}) { 7 | this.param = param; 8 | this.tgt = init; 9 | this.speed = speed; 10 | } 11 | reset(val = this.tgt) { 12 | this.param.value = val; 13 | } 14 | go(tgt, speed = this.speed) { 15 | this.tgt = tgt; 16 | this.speed = speed; 17 | } 18 | process() { 19 | const dif = this.tgt - this.param.value; 20 | if (Math.abs(dif) < this.speed * 1.5) this.param.value = this.tgt; 21 | else this.param.value += Math.sign(dif) * this.speed; 22 | } 23 | get value() { 24 | return this.param.value; 25 | } 26 | } 27 | 28 | // Linear smoothing node with fixed speed 29 | export class LinSmooth extends SampleProcessor { 30 | constructor(speed = 0.01) { 31 | super(); 32 | this.speed = speed; 33 | } 34 | processSample(s) { 35 | if (this.val === undefined) this.val = s; 36 | const dif = s - this.val; 37 | if (Math.abs(dif) < this.speed * 1.5) this.val = s; 38 | else this.val += Math.sign(dif) * this.speed; 39 | return this.val; 40 | } 41 | } 42 | 43 | const watcher = () => { 44 | let lastValue; 45 | return (newValue) => { 46 | const changed = (lastValue !== undefined) && (newValue !== lastValue); 47 | lastValue = newValue; 48 | return changed; 49 | }; 50 | }; 51 | 52 | /** 53 | * Fixed duration smoothing node. Whenever the input changes, the output will 54 | * take `duration` seconds to reach the new value. Useful for sequencing slides 55 | * with exact durations. 56 | */ 57 | export class DurSmooth extends SampleProcessor { 58 | constructor(duration = 1) { 59 | super(); 60 | this.duration = this.addParam((typeof duration === 'number') ? duration : 1); 61 | if (typeof duration === 'object') duration.connect(this.duration); 62 | this.durChanged = watcher(); 63 | this.inChanged = watcher(); 64 | this.x = 0; 65 | this.xmax = 0; 66 | } 67 | processSample(s) { 68 | if (this.durChanged(this.duration.value) || this.inChanged(s)) { 69 | this.src = this.value; 70 | this.tgt = s; 71 | this.x = 0; 72 | this.xmax = this.duration.value * this.sampleRate; 73 | } 74 | this.x++; 75 | if (this.x >= this.xmax) this.value = s; 76 | else this.value = this.src + (this.tgt - this.src) * (this.x / this.xmax); 77 | return this.value; 78 | } 79 | } -------------------------------------------------------------------------------- /MusicSource/lib/nodes/teanode.js: -------------------------------------------------------------------------------- 1 | import { Gain } from './basic.js'; 2 | 3 | /** 4 | * Parent class for audio nodes. This is NOT a Web Audio Node managed by the 5 | * browser, it exists purely in JS. Extend this class to create specific nodes. 6 | * 7 | * - Overriding the constructor is a good way to take parameters. 8 | * 9 | * - The default I/O channels are 1/1. This can be changed in `init` or 10 | * in the constructor, where they can be hardcoded or taken as parameters. 11 | * 12 | * - The process method should take one frame and return one frame. 13 | * 14 | * - The graph and sampleRate properties are available during or after `init` 15 | */ 16 | export class TeaNode { 17 | constructor() { 18 | this.numInChannels = 1; 19 | this.numOutChannels = 1; 20 | /** 21 | * Only available during or after init. 22 | * @type {import('../patch/graph.js').Graph} 23 | */ 24 | this.graph; 25 | /** 26 | * Only available during or after init. 27 | * @type {import('../../host.js').Host} 28 | */ 29 | this.host; 30 | /** 31 | * Only available during or after `init`. 32 | * @type {Number} 33 | */ 34 | this.sampleRate; 35 | /** @type {Set.} */ 36 | this.inNodes = new Set(); 37 | /** @type {Array.} */ 38 | this.params = []; 39 | this.playing = true; 40 | this.outFrame = [0]; 41 | this.topoDone = false; 42 | this.topoCycle = false; 43 | this.handlers = {}; 44 | if (this.ctrlRateProcess) { 45 | let ctr = 127; 46 | const ctrlRateProcess = this.ctrlRateProcess.bind(this); 47 | const audioRateProcess = this.process.bind(this); 48 | this.process = frame => { 49 | if (++ctr > 127) ctr = 0; 50 | if (!ctr) ctrlRateProcess(); 51 | return audioRateProcess(frame); 52 | }; 53 | } 54 | } 55 | /** 56 | * @param {TeaNode|NodeParam} target 57 | * @returns TeaNode 58 | */ 59 | connect(target) { 60 | if (this.graph && this.graph.ready) 61 | throw new Error("Can't connect nodes after graph is initialized! Node: " + this.describe()); 62 | if (target instanceof TeaNode) { 63 | target.inNodes.add(this); 64 | return target; 65 | } else if (target instanceof NodeParam) { 66 | if (target.source) throw new Error('Tried connecting twice to ' + describe(target.owner)); 67 | target.source = this; 68 | return target.owner; 69 | } else { 70 | throw new Error(`Can't connect "${this.describe()}" to "${target}"`); 71 | } 72 | } 73 | /** 74 | * @param {TeaNode|NodeParam} target 75 | * @returns TeaNode 76 | */ 77 | connectWithGain(target, initialGain = 1) { 78 | return this.connect(new Gain(initialGain)).connect(target); 79 | } 80 | /** 81 | * @param {Array.} frame 82 | * @returns {Array.} 83 | */ 84 | process(frame) { 85 | return frame; 86 | } 87 | /** Optionally override this for initialization. */ 88 | init() {} 89 | /** 90 | * When there are multiple input nodes, this method is called to combine 91 | * their frames before `process` is called. Frames are normalized to 92 | * this.numInChannels. 93 | * @param {Array.>} frames 94 | * @returns {Array.} 95 | */ 96 | mix(frames) { 97 | // assuming a loop is faster than a functional approach 98 | const ret = Array(this.numInChannels).fill(0); 99 | for (let chani = 0; chani < this.numInChannels; chani++) { 100 | for (let framei = 0; framei < frames.length; framei++) { 101 | ret[chani] += frames[framei][chani]; 102 | } 103 | ret[chani] /= this.numInChannels; 104 | } 105 | return ret; 106 | } 107 | /** 108 | * Create automatable parameter. 109 | */ 110 | addParam(value = 0) { 111 | if (this.graph && this.graph.ready) 112 | throw new Error("Can't add param after graph is initialized! Node: " + this.describe()); 113 | const param = new NodeParam(this, value); 114 | this.params.push(param); 115 | return param; 116 | } 117 | /** 118 | * Call in `init` to match this node's channel count to its inputs. 119 | */ 120 | useInputChannelCount() { 121 | if (!this.inNodes.size) { 122 | this.numInChannels = this.numOutChannels; 123 | return; 124 | } 125 | const nChannels = Math.max(...[...this.inNodes].map(n => n.numOutChannels)); 126 | this.numInChannels = nChannels; 127 | this.numOutChannels = nChannels; 128 | } 129 | /** @param {import('../patch/graph.js').Graph} graph */ 130 | baseInit(graph) { 131 | this.graph = graph; 132 | this.sampleRate = graph.sampleRate; 133 | } 134 | stop() { 135 | this.stoppedProcess = this.process; 136 | this.process = () => Array(this.numOutChannels).fill(0); 137 | this.playing = false; 138 | } 139 | start() { 140 | if (this.playing || !this.stoppedProcess) 141 | throw new Error(`Tried starting ${this.describe}, but it wasn't stopped!`); 142 | this.process = this.stoppedProcess; 143 | this.playing = true; 144 | } 145 | makeSignalProcessor() { 146 | this.inNodes.forEach(inNode => checkConnectionValid(inNode, this)); 147 | const numInNodes = this.inNodes.size; 148 | if (!numInNodes) { 149 | const makeInFrame = () => Array(this.numInChannels).fill(0); 150 | return () => { 151 | this.outFrame = this.process(makeInFrame()); 152 | }; 153 | } 154 | const in0 = [...this.inNodes][0]; 155 | if (numInNodes === 1 && in0.numOutChannels === this.numInChannels) { 156 | return () => { 157 | this.outFrame = this.process(in0.outFrame); 158 | }; 159 | } else if (numInNodes === 1) { 160 | const adapter = makeFrameAdapter(in0, this); 161 | return () => { 162 | this.outFrame = this.process(adapter()); 163 | }; 164 | } else { 165 | const adapters = [...this.inNodes].map(inNode => makeFrameAdapter(inNode, this)); 166 | return () => { 167 | const frames = adapters.map(adapter => adapter()); 168 | const mixedFrame = this.mix(frames); 169 | this.outFrame = this.process(mixedFrame); 170 | }; 171 | } 172 | } 173 | makeParamProcessor() { 174 | const connectedParams = this.params.filter(p => p.source); 175 | if (!connectedParams.length) return; 176 | return () => { 177 | for (const param of connectedParams) { 178 | param.value = param.source.outFrame[0]; 179 | } 180 | }; 181 | } 182 | /** @param { import('../../host.js').HostEventType } eventType */ 183 | on(eventType, handler) { 184 | const handlers = this.handlers[eventType] ?? (this.handlers[eventType] = new Set()); 185 | handlers.add(handler); 186 | if (this.host) this.host.events.on(eventType, handler); 187 | } 188 | setGraph(graph) { 189 | if (this.graph) return; 190 | this.graph = graph; 191 | this.host = graph.host; 192 | this.sampleRate = graph.sampleRate; 193 | for (const [eventType, handlers] of Object.entries(this.handlers)) { 194 | for (const handler of handlers) { 195 | this.host.events.on(eventType, handler); 196 | } 197 | } 198 | } 199 | describe() { 200 | return this.constructor.name; 201 | } 202 | } 203 | 204 | // Pluggable parameter for nodes. Gets created by TeaNode::addParam 205 | export class NodeParam { 206 | /** 207 | * @param {TeaNode} owner 208 | * @param {Number} value 209 | */ 210 | constructor(owner, value) { 211 | this.owner = owner; 212 | this.value = value; 213 | /** @type {TeaNode} */ 214 | this.source; 215 | } 216 | describe() { 217 | return 'parameter of ' + this.owner; 218 | } 219 | } 220 | 221 | export const describe = thing => { 222 | if (thing.describe) return thing.describe(); 223 | else return thing; 224 | }; 225 | 226 | /** 227 | * @param {TeaNode} srcNode 228 | * @param {TeaNode} tgtNode 229 | */ 230 | const checkConnectionValid = (srcNode, tgtNode) => { 231 | const msgBase = `Can't connect "${describe(srcNode)}" to "${describe(tgtNode)}": `; 232 | const numSrc = srcNode.numOutChannels; 233 | const numTgt = tgtNode.numInChannels; 234 | if (!numSrc) throw new Error(msgBase + 'source node has 0 output channels'); 235 | else if (!numTgt) throw new Error(msgBase + 'target node has 0 input channels'); 236 | else if (numSrc > 1 && numTgt > 1 && numSrc !== numTgt) { 237 | throw new Error(msgBase + `can't auto-adapt ${numSrc} channels to ${numTgt} channels`); 238 | } 239 | }; 240 | 241 | /** 242 | * @param {TeaNode} srcNode 243 | * @param {TeaNode} tgtNode 244 | */ 245 | const makeFrameAdapter = (srcNode, tgtNode) => { 246 | const numSrc = srcNode.numOutChannels; 247 | const numTgt = tgtNode.numInChannels; 248 | if (numSrc === numTgt) { 249 | return () => srcNode.outFrame; 250 | } else if (numSrc === 1) { 251 | return () => Array(numTgt).fill(srcNode.outFrame[0]); 252 | } else if (numTgt === 1) { 253 | return () => { 254 | // assuming a loop is faster than a functional approach 255 | let ret = 0; 256 | for (let i = 0; i < numSrc; i++) ret += srcNode.outFrame[i]; 257 | return [ret / numSrc]; 258 | }; 259 | } else { 260 | throw new Error('Unhandled invalid connection from ' + `"${describe(srcNode)}" to "${describe(tgtNode)}"`); 261 | } 262 | }; 263 | -------------------------------------------------------------------------------- /MusicSource/lib/patch/graph.js: -------------------------------------------------------------------------------- 1 | import { TeaNode, NodeParam, describe } from '../nodes/teanode.js'; 2 | import { OutputNode, Gain } from '../nodes/basic.js'; 3 | import { HostParam } from '../nodes/params.js'; 4 | import { topologicalSort } from './topo.js'; 5 | import { mainHost } from '../../host.js'; 6 | 7 | /** 8 | * A graph to contain all TeaNodes in a patch. Once all nodes have been added 9 | * and the host has signalled that it's ready, the "process" method runs the 10 | * graph and returns samples. 11 | */ 12 | export class Graph { 13 | constructor({ sampleRate = 44100, host = mainHost } = {}) { 14 | this.sampleRate = sampleRate; 15 | /** @type {import('../../host.js').Host} */ 16 | this.host = host; 17 | /** @param {import('../../host.js').HostEventType} eventType */ 18 | this.on = (eventType, handler) => this.host.events.on(eventType, handler); 19 | this.out = new OutputNode(); 20 | /** @type {Array.} */ 21 | this.sortedNodes; 22 | /** @type {Array.} */ 23 | this.processors = []; 24 | /** @type {Set.} */ 25 | this.eventProcessors = new Set(); 26 | this.ready = false; 27 | this.skipping = false; 28 | this.skipTgt = 0; 29 | this.timeSmp = 0; 30 | this.splicePoints = []; 31 | this.ampParam = this.addParam('amp', { def: 1, max: 10 }); 32 | } 33 | addParam(name, { def = 0, min, max } = {}) { 34 | const node = new HostParam(name, { def, min, max }); 35 | node.setGraph(this); 36 | return node; 37 | } 38 | makeProcessor() { 39 | /** @type {Array.} */ 40 | const allNodes = []; 41 | /** @param {TeaNode} node */ 42 | const addNode = node => { 43 | if (allNodes.includes(node)) return; 44 | node.setGraph(this); 45 | allNodes.push(node); 46 | node.inNodes.forEach(inNode => addNode(inNode)); 47 | node.params.forEach(param => { 48 | if (param.source) addNode(param.source); 49 | }); 50 | }; 51 | addNode(this.out); 52 | this.sortedNodes = topologicalSort(allNodes); 53 | if (this.sortedNodes.length !== allNodes.length) { 54 | throw new Error('These nodes are... half-connected. It simply should not be.'); 55 | } 56 | this.host.events.on('can make processors', () => { 57 | for (const node of this.sortedNodes) { 58 | const paramProcessor = node.makeParamProcessor(); 59 | if (paramProcessor) this.processors.push(paramProcessor); 60 | this.processors.push(node.makeSignalProcessor()); 61 | } 62 | }); 63 | this.host.events.on('can play', async () => await this.onReady()); 64 | return () => this.process(); 65 | } 66 | async onReady() { 67 | this.ready = true; 68 | if (this.skipTgt) { 69 | console.log(`Skipping to ${Math.round(this.skipTgt * 100) / 100}...`); 70 | this.skipping = true; 71 | let awaits = 0; 72 | for (let t = 0; t < this.skipTgt * this.sampleRate; t++) { 73 | this.eventProcessors.forEach(process => process()); 74 | if (!(t % 100)) { 75 | // you better start believing in fucked up async functions 76 | awaits += await 1; 77 | // you're in one 78 | if (!this.skipping) break; 79 | } 80 | if (t && !(t % (this.sampleRate * 10))) { 81 | console.log(`...skipped ${t / this.sampleRate}s`); 82 | } 83 | } 84 | this.skipping = false; 85 | console.log(`Caught up ${awaits} times`); 86 | } 87 | } 88 | process() { 89 | this.eventProcessors.forEach(process => process()); 90 | this.processors.forEach(process => process()); 91 | for (let i = 0; i < this.out.outFrame.length; i++) { 92 | this.out.outFrame[i] *= this.ampParam.value; 93 | } 94 | this.timeSmp++; 95 | return this.out.outFrame; 96 | } 97 | /** 98 | * @param {TeaNode} src 99 | * @param {TeaNode|NodeParam} tgt 100 | * @returns {Gain} 101 | */ 102 | getConnection(src, tgt) { 103 | const checkMidNode = midNode => { 104 | if (midNode === src) { 105 | throw new Error( 106 | 'tried calling getConnection between ' + 107 | describe(src) + 108 | ' and ' + 109 | describe(tgt) + 110 | ', but they are directly connected. ' + 111 | 'Connect them using connectWithGain.' 112 | ); 113 | } 114 | if (midNode.inNodes.has(src)) { 115 | const numInNodes = midNode.inNodes.size; 116 | // hmm, this doesn't work when calling before init 117 | // const numOutNodes = this.sortedNodes.filter(n => n.inNodes.has(midNode)).length; 118 | if (numInNodes !== 1) { 119 | throw new Error( 120 | 'tried calling getConnection between ' + 121 | describe(src) + 122 | ' and ' + 123 | describe(tgt) + 124 | ', but the connection includes other nodes.' 125 | ); 126 | } 127 | if (!(midNode instanceof Gain)) { 128 | throw new Error( 129 | 'tried calling getConnection between ' + 130 | describe(src) + 131 | ' and ' + 132 | describe(tgt) + 133 | ', but the connection is not a Gain.' 134 | ); 135 | } 136 | return midNode; 137 | } 138 | }; 139 | if (tgt instanceof TeaNode) { 140 | for (const midNode of tgt.inNodes) { 141 | const connection = checkMidNode(midNode); 142 | if (connection) return connection; 143 | } 144 | } else if (tgt instanceof NodeParam && tgt.source) { 145 | const connection = checkMidNode(tgt.source); 146 | if (connection) return connection; 147 | } else { 148 | throw new Error(`Can't get connection from ${describe(src)} to ${describe(tgt)}!`); 149 | } 150 | throw new Error(`Can't get connection from ${describe(src)} to ${describe(tgt)}!`); 151 | } 152 | ctrl(fn, rate = 128) { 153 | let sample = 0; 154 | const delta = rate / this.sampleRate; 155 | this.eventProcessors.add(() => { 156 | if (sample++ % rate) return; 157 | const sec = sample / this.sampleRate; 158 | fn(sec, delta); 159 | }); 160 | } 161 | ctrlDuration(dur, fn) { 162 | let sample = 0; 163 | const evt = () => { 164 | if (sample++ % 128) return; 165 | const sec = sample / this.sampleRate; 166 | if (sec > dur) this.eventProcessors.delete(evt); 167 | else fn(sec); 168 | }; 169 | this.eventProcessors.add(evt); 170 | } 171 | /** 172 | * @param {TeaNode} src 173 | * @param {TeaNode|NodeParam} tgt 174 | */ 175 | muteConnection(src, tgt) { 176 | this.getConnection(src, tgt).gain.value = 0; 177 | } 178 | /** 179 | * @param {TeaNode} src 180 | * @param {TeaNode|NodeParam} tgt 181 | */ 182 | unmuteConnection(src, tgt) { 183 | this.getConnection(src, tgt).gain.value = 1; 184 | } 185 | skipToMarker(id) { 186 | this.wantMarker = id; 187 | this.skipTgt = 10 * 60 * 48000; 188 | } 189 | skipToSecond(s) { 190 | this.skipTgt = s; 191 | } 192 | setMarker(id) { 193 | if (id === this.wantMarker) { 194 | this.skipping = false; 195 | } 196 | } 197 | setSplicePoint(id) { 198 | if (id) { 199 | if (this.splicePoints.includes(id)) return; 200 | this.splicePoints.push(id); 201 | } 202 | this.host.splicePoint = true; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /MusicSource/lib/patch/seq.js: -------------------------------------------------------------------------------- 1 | import { TeaNode, NodeParam } from '../nodes/teanode.js'; 2 | import { playTable } from './seqTable.js'; 3 | 4 | // Sequencer class using async/await 5 | export class Seq { 6 | /** 7 | * @param {import('./graph.js').Graph} graph 8 | */ 9 | constructor(graph) { 10 | this.graph = graph; 11 | this.timeError = 0; 12 | } 13 | /** 14 | * @param {function():Promise} cb 15 | */ 16 | schedule(cb) { 17 | const wrap = async () => { 18 | await cb(); 19 | console.log('Sequence finished playing.'); 20 | }; 21 | const startProcessor = () => { 22 | wrap(); 23 | this.graph.eventProcessors.delete(startProcessor); 24 | }; 25 | this.graph.eventProcessors.add(startProcessor); 26 | } 27 | /** 28 | * @param {function():Promise} cb 29 | */ 30 | solo(cb) { 31 | const wrap = async () => { 32 | if (cb.then) { 33 | while (true) await cb(); 34 | } else { 35 | cb(); 36 | } 37 | }; 38 | const startProcessor = () => { 39 | wrap(); 40 | this.graph.eventProcessors.delete(startProcessor); 41 | }; 42 | this.graph.eventProcessors.clear(); 43 | this.graph.eventProcessors.add(startProcessor); 44 | } 45 | /** 46 | * Wait for `dur` seconds to play before resolving. Note this will actually 47 | * resolve at the end of a 128-frame block, as of current implementations. 48 | */ 49 | async play(dur) { 50 | if (!this.graph.sampleRate) throw new Error('Seq.play: graph not ready!'); 51 | let countDown = dur * this.graph.sampleRate; 52 | // prevent error from accumulating 53 | const compensation = Math.min(this.timeError, countDown); 54 | countDown -= compensation; 55 | this.timeError -= compensation; 56 | await new Promise(resolve => { 57 | const eventProcessor = () => { 58 | if (countDown-- > 0) return; 59 | this.graph.eventProcessors.delete(eventProcessor); 60 | this.resolveTimeSmp = this.graph.timeSmp; 61 | resolve(); 62 | }; 63 | this.graph.eventProcessors.add(eventProcessor); 64 | eventProcessor(); 65 | }); 66 | this.timeError += this.graph.timeSmp - this.resolveTimeSmp; 67 | } 68 | async rep(n, f) { 69 | for (let i = 0; i < n; i++) { 70 | await f(); 71 | } 72 | } 73 | /** 74 | * @param {Object} arg 75 | * @param {NodeParam} arg.param 76 | * @param {Array.} arg.connection 77 | * @param {Number} arg.endVal 78 | * @param {Number} arg.dur 79 | * @param {('cos'|'lin')} arg.type 80 | */ 81 | slide({ param, connection, endVal, dur, type = 'cos' }) { 82 | if (connection) param = this.graph.getConnection(...connection).gain; 83 | let countUp = 0; 84 | const countMax = dur * this.graph.sampleRate; 85 | const f = this.makeSlideFn({ param, endVal, type }); 86 | const eventProcessor = () => { 87 | let pos = countUp++ / countMax; 88 | if (pos > 1) pos = 1; 89 | param.value = f(pos); 90 | if (pos === 1) this.graph.eventProcessors.delete(eventProcessor); 91 | }; 92 | this.graph.eventProcessors.add(eventProcessor); 93 | } 94 | /** 95 | * @param {Object} arg 96 | * @param {NodeParam} arg.param 97 | * @param {Array.} arg.connection 98 | * @param {Number} arg.endVal 99 | * @param {Number} arg.dur 100 | * @param {('cos'|'lin')} arg.type 101 | */ 102 | ctrlSlide({ param, connection, endVal, dur, type = 'cos' }) { 103 | if (connection) param = this.graph.getConnection(...connection).gain; 104 | let countUp = 0; 105 | const countMax = Math.floor(dur * this.graph.sampleRate); 106 | const f = this.makeSlideFn({ param, endVal, type }); 107 | const eventProcessor = () => { 108 | if (!(countUp % 128)) { 109 | param.value = f(countUp / countMax); 110 | } 111 | if (++countUp > countMax) { 112 | param.value = endVal; 113 | this.graph.eventProcessors.delete(eventProcessor); 114 | } 115 | }; 116 | this.graph.eventProcessors.add(eventProcessor); 117 | } 118 | makeSlideFn({ param, endVal, type }) { 119 | const v1 = param.value; 120 | const v2 = endVal; 121 | if (type === 'cos') { 122 | return pos => v1 + (0.5 - 0.5 * Math.cos(pos * Math.PI)) * (v2 - v1); 123 | } else if (type === 'lin') { 124 | return pos => v1 + pos * (v2 - v1); 125 | } else { 126 | } 127 | } 128 | /** 129 | * @param {String} table 130 | * @param {Object} tableVars 131 | */ 132 | playTable(table, tableVars) { 133 | return playTable({ seq: this, graph: this.graph, table, tableVars }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /MusicSource/lib/patch/seqTable.js: -------------------------------------------------------------------------------- 1 | import { Seq } from './seq.js'; 2 | import { Graph } from './graph.js'; 3 | 4 | /** 5 | * Function to use a monospaced table-like string as a sequence. 6 | * @param {Seq} seq 7 | * @param {Graph} graph 8 | * @param {String} table 9 | * @param {Object} tableVars 10 | */ 11 | export const playTable = ({ seq, graph, table, tableVars }) => { 12 | const getVar = varName => { 13 | if (!(varName in tableVars)) { 14 | throw new Error('Table seq could not find var: ' + varName); 15 | } 16 | return tableVars[varName]; 17 | }; 18 | const rows = table 19 | .split('\n') 20 | .map(line => [...line.matchAll(/([^\s|_~]+)/g)].map(match => ({ str: match[0], col: match.index, line }))) 21 | .filter(tokens => tokens.length > 1 && tokens[0].str[0] !== '-'); 22 | for (let row of rows) { 23 | row.head = row[0].str; 24 | row.bodyTokens = row.slice(1); 25 | } 26 | const events = []; 27 | const parseStepRow = row => { 28 | let stepDur = 1; 29 | const stepName = row.head.split(':')[1]; 30 | if (stepName) stepDur = getVar(stepName); 31 | const colTimes = {}; 32 | let timeStepper = 0, 33 | prevInterval = 0; 34 | for (let token of row.bodyTokens) { 35 | colTimes[token.col] = timeStepper * stepDur; 36 | if (isNaN(Number(token.str))) { 37 | throw new Error('Could not parse time row of seq table at column ' + token.col); 38 | } 39 | events.push({ 40 | time: timeStepper * stepDur, 41 | prevInterval: prevInterval * stepDur, 42 | col: token.col, 43 | actions: [], 44 | }); 45 | prevInterval = Number(token.str); 46 | timeStepper += prevInterval; 47 | } 48 | for (let updateRow of rows) { 49 | for (let token of updateRow.bodyTokens) { 50 | const time = colTimes[token.col]; 51 | if (time === undefined) { 52 | throw new Error(`Row ${updateRow.head}, col ${token.col}: no time reference`); 53 | } 54 | token.time = time; 55 | token.event = events.find(e => e.time === time); 56 | } 57 | } 58 | }; 59 | const parseMarkersRow = row => { 60 | for (let token of row.bodyTokens) { 61 | token.event.actions.push(() => graph.setMarker(token.str)); 62 | } 63 | }; 64 | const parseAutoRow = row => { 65 | // for example: (0-1):vib-bouncy -> ['0', '1', 'vib', 'bouncy'] 66 | const match = row.head.match(/\((.+)-(.+)\):([^-]+)-?(.+)?/); 67 | if (!match) throw new Error('Invalid seq table row header: ' + row.head); 68 | const min = Number(match[1]), 69 | max = Number(match[2]); 70 | const name1 = match[3], 71 | name2 = match[4]; 72 | let param; 73 | if (name2) param = graph.getConnection(getVar(name1), getVar(name2)).gain; 74 | else param = getVar(name1); 75 | const convertToken = token => { 76 | const val = token.str === 'A' ? 10 : Number(token.str); 77 | if (isNaN(val)) throw new Error(`invalid value ${token.str} in ` + row.head); 78 | return 0.1 * val * (max - min) + min; 79 | }; 80 | let lastValue = param.value; 81 | for (let [i, token] of row.bodyTokens.entries()) { 82 | const tokenVal = convertToken(token); 83 | const prevConn = token.line[token.col - 1].trim(); 84 | const nextToken = row.bodyTokens[i + 1]; 85 | const nextConn = nextToken?.line[nextToken.col - 1].trim(); 86 | if (!prevConn && token.time === 0) { 87 | param.value = tokenVal; 88 | lastValue = tokenVal; 89 | } else if (!prevConn) { 90 | token.event.actions.push(() => { 91 | param.value = tokenVal; 92 | }); 93 | lastValue = tokenVal; 94 | } 95 | if (!nextToken || !nextConn) continue; 96 | const endVal = convertToken(nextToken); 97 | if (endVal === lastValue) continue; 98 | const dur = nextToken.time - token.time; 99 | const type = { ['~']: 'cos', ['_']: 'lin' }[nextConn]; 100 | token.event.actions.push(() => seq.ctrlSlide({ param, endVal, dur, type })); 101 | lastValue = endVal; 102 | } 103 | }; 104 | const parseFnRow = row => { 105 | const fnName = row.head.split(':')[1]; 106 | if (!fnName) throw new Error('Invalid seq table row header: ' + row.head); 107 | const fn = getVar(fnName); 108 | for (let token of row.bodyTokens) { 109 | if (token.str === 'x') { 110 | token.event.actions.push(fn); 111 | } else { 112 | token.event.actions.push(fn[token.str]); 113 | } 114 | } 115 | }; 116 | const rowSignatures = [ 117 | ['step', parseStepRow], 118 | ['markers', parseMarkersRow], 119 | ['(', parseAutoRow], 120 | ['fn', parseFnRow], 121 | ]; 122 | for (let row of rows) { 123 | const sig = rowSignatures.find(s => row[0].str.startsWith(s[0])); 124 | if (!sig) throw new Error('Unknown seq table row type: ' + row[0].str); 125 | sig[1](row); 126 | } 127 | seq.schedule(async () => { 128 | for (let event of events) { 129 | if (event.prevInterval) await seq.play(event.prevInterval); 130 | console.log(`running ${event.actions.length} actions`); 131 | for (let action of event.actions) action(); 132 | } 133 | }); 134 | }; 135 | -------------------------------------------------------------------------------- /MusicSource/lib/patch/topo.js: -------------------------------------------------------------------------------- 1 | import { TeaNode } from '../nodes/teanode.js'; 2 | 3 | /** 4 | * @param {TeaNode} node 5 | * @param {Array.} unsorted 6 | * @param {Array.} sorted 7 | */ 8 | const topologicalSortHelper = (node, unsorted, sorted) => { 9 | node.topoCycle = true; 10 | const paramInputNodes = node.params.filter(p => p.source).map(p => p.source); 11 | const allInputNodes = new Set([...node.inNodes, ...paramInputNodes]); 12 | for (let inNode of allInputNodes) { 13 | if (inNode.topoCycle) throw new Error('Cyclic dependency detected.'); 14 | if (!inNode.topoDone) topologicalSortHelper(inNode, unsorted, sorted); 15 | } 16 | node.topoCycle = false; 17 | node.topoDone = true; 18 | sorted.push(node); 19 | }; 20 | 21 | /** 22 | * @param {Array.} unsorted 23 | * @returns {Array.} 24 | */ 25 | export const topologicalSort = unsorted => { 26 | const sorted = []; 27 | for (let node of unsorted) { 28 | if (!node.topoDone && !node.topoCycle) topologicalSortHelper(node, unsorted, sorted); 29 | } 30 | return sorted; 31 | }; 32 | -------------------------------------------------------------------------------- /MusicSource/lib/tealib.js: -------------------------------------------------------------------------------- 1 | export * from './nodes/basic.js'; 2 | export * from './nodes/faust.js'; 3 | export * from './nodes/loader.js'; 4 | export * from './nodes/music.js'; 5 | export * from './nodes/params.js'; 6 | export * from './nodes/smooth.js'; 7 | export * from './nodes/teanode.js'; 8 | export * from './patch/graph.js'; 9 | export * from './patch/seq.js'; 10 | -------------------------------------------------------------------------------- /MusicSource/patches/contrabass/faust/contrabass.dsp: -------------------------------------------------------------------------------- 1 | // Ambient contrabass instrument in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 80, 30, 200, 0.001); 7 | lp1 = hslider("lp1", 3300, 100, 10000, 0.0001); 8 | lp2 = hslider("lp2", 580, 100, 1000, 0.0001); 9 | noise1amt = hslider("noise1", 0, 0, 1, 0.0001); 10 | texamp = hslider("texamp", 0, 0, 1, 0.0001); 11 | texvar = hslider("texvar", 0, 0, 1, 0.0001); 12 | 13 | // Convert frequency to delay line length in samples 14 | f2samp(f) = (f : ma.inv : ba.sec2samp) - 1; 15 | 16 | // Exciter: a combination of filtered oscillators 17 | saw = os.sawtooth(freq*2)+os.sawtooth(freq*3.97)*0.7+os.sawtooth(freq*4.03)*0.5; 18 | noise1 = no.noise * noise1amt * 0.5 : fi.lowpass(1, lp1) : fi.lowpass(2, lp2); 19 | noise2 = saw * 0.05 * texamp : fi.lowpass(2, 6000) : fi.highpass(1, 1500) : _ * (no.lfnoise(10) > 0.5*texvar) 20 | : fi.fb_fcomb(8192, 5000 + 200 * os.osc(0.07) : f2samp, 1, 0.3) 21 | : fi.fb_fcomb(8192, 7000 + 1000 * os.osc(0.13) : f2samp, 1, 0.4) 22 | : fi.fb_fcomb(8192, 13000 + 2000 * os.osc(0.23) : f2samp, 1, 0.6); 23 | exc = (noise1) + noise2 : fi.highpass(2, 80); 24 | 25 | // Resonator with artifacts 26 | reso = loop(vibrato-alias) : loop(vibrato+alias) with { 27 | vibrato = freq * (1 + os.osc(4) * 0.008); 28 | alias = (3 / ma.SR) * freq * freq; 29 | loop(f) = + ~ (de.fdelay2(9000, f2samp(f)) * 0.8); 30 | }; 31 | 32 | process = exc : reso; -------------------------------------------------------------------------------- /MusicSource/patches/contrabass/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add some reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 6 | process = _*preamp <: rev_st : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/contrabass/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ambient contrabass. 3 | * 4 | * The core of this patch is a waveguide-inspired bowed string algorithm. It 5 | * makes deliberate use of aliasing artifacts, and has an additional layer of 6 | * ambient noise. 7 | * 8 | * Audio-rate code is written in Faust and can be found in the faust directory. 9 | * 10 | * This file contains sequencing, envelope, and other control code. 11 | */ 12 | 13 | import { Graph, Seq, FaustNode } from '../../lib/tealib.js'; 14 | 15 | export const sampleRate = 44100; 16 | const graph = new Graph({ sampleRate }); 17 | 18 | // Create some nodes: Faust instrument and post-processing 19 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 20 | const fau = new FaustNode('faust/contrabass.dsp', { 21 | freq: 0, noise1: 0, texamp: 0, texvar: 0, lp1: 3300, lp2: 580 22 | }); 23 | fau.connect(post).connect(graph.out); 24 | 25 | // Basic parameters to control the patch 26 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 27 | graph.addParam('freq1', { def: '100*1/2' }).connect(fau.freq); 28 | // Params: dynamics flattening; Lowpass; Texture amplitude and variability 29 | const flatten = graph.addParam('flatten'); 30 | graph.addParam('lp1', { def: 3300, min: 100, max: 10000 }).connect(fau.lp1); 31 | graph.addParam('texamp').connect(fau.texamp); 32 | graph.addParam('texvar').connect(fau.texvar); 33 | 34 | // At control rate, ensure the first few seconds are loud 35 | let loudness; 36 | graph.ctrl(tSec => { 37 | loudness = flatten.value * (1 - 1 / (tSec * tSec * 0.15 + 1)); 38 | }); 39 | 40 | // Utility to cause a "wave" shape in a given parameter 41 | const playWave = (targetParam, amp) => { 42 | graph.ctrlDuration(Math.PI * 2, sec => { 43 | targetParam.value = (0.5 - Math.cos(sec) * 0.5) * amp; 44 | targetParam.value = 0.3 * loudness + targetParam.value * (1 - loudness); 45 | }); 46 | }; 47 | 48 | // Some lowpass frequencies to cycle though 49 | let lp2s = [580, 750, 650, 1200]; 50 | 51 | // Automate the instrument's noise source and lowpass 52 | const seq = new Seq(graph); 53 | seq.schedule(async () => { 54 | while (true) { 55 | fau.lp2.value = lp2s[0]; 56 | playWave(fau.noise1, 0.3); 57 | await seq.play(2); 58 | await seq.play(Math.PI * 2 - 2 + 0.5); 59 | lp2s = [...lp2s.slice(1), lp2s[0]]; 60 | } 61 | }); 62 | export const process = graph.makeProcessor(); 63 | -------------------------------------------------------------------------------- /MusicSource/patches/harmonic-series/faust/harmo.dsp: -------------------------------------------------------------------------------- 1 | // Harmonic series instrument in Faust. An abstract waveguide. 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | // "sdelay"s are delay line lengths in samples 7 | fb = hslider("fb", 0, 0, 2, 0.0001); 8 | amp = hslider("amp", 0, 0, 1, 0.001); 9 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 10 | sdelay1 = hslider("sdelay1", 1000, 1, 9000, 0.1); 11 | sdelay2 = hslider("sdelay2", 1000, 1, 9000, 0.1); 12 | locut = hslider("locut", 500, 50, 2050, 0.001); 13 | hicut = hslider("hicut", 500, 50, 2050, 0.001); 14 | 15 | // Non-linearity 16 | NL(x) = x / ( (x*x) + 1 ); 17 | 18 | // Exciter: filtered oscillator 19 | exc = no.noise * 0.01 : fi.lowpass(2, 600); 20 | 21 | // Waveguide loop 22 | body = _ <: de.fdelay2(9000, sdelay1), de.fdelay2(9000, sdelay2) 23 | :> fi.highpass(1, locut) : fi.lowpass(1, hicut) 24 | : NL * fb; 25 | loop = + ~ body; 26 | 27 | // Post-processing 28 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 29 | post = _*preamp <: rev_st : co.limiter_1176_R4_stereo : *(0.7), *(0.7); 30 | 31 | process = exc : loop : *(amp) : post; -------------------------------------------------------------------------------- /MusicSource/patches/harmonic-series/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Harmonic series synth. 3 | * 4 | * This patch uses a waveguide model to play natural harmonics. The notes are 5 | * not sequenced, rather, a single note is effectively played, and a sweeping 6 | * filter inside the waveguide causes successive harmonics to resonate. Similar 7 | * to the "harmonic flute" described in: https://www.osar.fr/notes/waveguides/ 8 | * 9 | * Audio-rate code is written in Faust and can be found in the faust directory. 10 | * 11 | * This file contains envelopes, automation, and other control code. 12 | */ 13 | 14 | import { Graph, Seq, FaustNode } from '../../lib/tealib.js'; 15 | import { randomSeed } from '../../lib/math.js'; 16 | 17 | export const sampleRate = 44100; 18 | const graph = new Graph({ sampleRate }); 19 | 20 | // Create a Faust node running all the audio-rate code 21 | // "sdelay"s are delay line lengths in samples 22 | const fau = new FaustNode('faust/harmo.dsp', { fb: 0, amp: 1, preamp: 1, sdelay1: 1, sdelay2: 1, locut: 1, hicut: 1 }); 23 | fau.connect(graph.out); 24 | 25 | // Parameters to control the patch: amplitude and frequency 26 | graph.addParam('preamp', { def: 1 }).connect(fau.preamp); 27 | const f1param = graph.addParam('freq1', { def: '100' }); 28 | const harmo = { value: 4 }; 29 | 30 | // Convert frequency to delay line length, compensating for filters in the waveguide 31 | const sDelay = f => { 32 | f += (0.16 * f * f) / (fau.locut.value + fau.hicut.value); 33 | return (sampleRate * 1.011) / f - 1; 34 | }; 35 | 36 | // At control rate, set the tremolo and filters 37 | graph.ctrl(tSec => { 38 | const lfo = 1 + 0.01 * Math.sin(tSec * Math.PI * 2 * 4); 39 | // const f2 = f2param.value; 40 | const f2 = f1param.value * harmo.value; 41 | fau.locut.value = 0.4 * f2 * (1 - 1 / (f2 * f2 * 0.00002 + 1)); 42 | fau.hicut.value = f2 * 1.3; 43 | fau.sdelay1.value = sDelay(f1param.value); 44 | fau.sdelay2.value = sDelay(f2) * lfo; 45 | }); 46 | 47 | // Create pseudorandom slides from one frequency to another 48 | const gold = 0.382; 49 | let inc = 0; 50 | const rand = randomSeed(1); 51 | const movement = () => { 52 | inc = (inc + gold) % 0.99; 53 | return Math.floor(inc * (9 - 4 + 1) + 4); 54 | }; 55 | const seq = new Seq(graph); 56 | const slide = async () => { 57 | const dur = rand() * 4 + 1; 58 | seq.ctrlSlide({ dur, param: harmo, endVal: movement(), type: 'cos' }); 59 | await seq.play(dur + 0.1); 60 | return dur; 61 | }; 62 | seq.schedule(async () => { 63 | while (true) { 64 | harmo.value = movement(); 65 | seq.ctrlSlide({ dur: 0.5, param: fau.fb, endVal: 0.8 }); 66 | let dur = rand(3) + 3; 67 | while (dur > 0) { 68 | dur -= await slide(); 69 | } 70 | seq.ctrlSlide({ dur: 2, param: fau.fb, endVal: 0 }); 71 | await seq.play(5); 72 | } 73 | }); 74 | 75 | export const process = graph.makeProcessor(); 76 | -------------------------------------------------------------------------------- /MusicSource/patches/melodic-bottle/faust/bottle.dsp: -------------------------------------------------------------------------------- 1 | // Bottle instrument in Faust using modal synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 5, 0.0001); 8 | tremAmt = hslider("trem", 0, 0, 1, 0.0001); 9 | bounce = hslider("bounce", 0, 0, 1, 0.0001); 10 | lp1 = hslider("lp1", 6000, 100, 10000, 0.0001); 11 | resMul = hslider("resmul", 1, 0, 1, 0.0001); 12 | modes = hslider("modes", 4, 0, 4, 0.0001); 13 | 14 | amp = (1+bounce*2+trem*2)*0.02; 15 | 16 | // Exciter: a filtered oscillator with tremolo 17 | exc = no.noise * noiseAmt * amp : fi.lowpass(1, lp1) : fi.lowpass(2, 4000); 18 | tremFreq = (4+noiseAmt*4)*(1+bounce*2) : an.amp_follower_ar(1, 0.1); 19 | tremPos = 1 - tremAmt*(0.5+0.5*os.osc(tremFreq)); 20 | trem = _ : fi.lowpass(2, 100+(7000 - 3000*tremAmt)*tremPos*tremPos); 21 | 22 | // Modal body 23 | barCore = _ <: 24 | pm.modeFilter(freq, 2*resMul, 0.16*(modes > 3)), 25 | pm.modeFilter(freq*1.47*2, 0.8*resMul, 0.11*(modes > 2)), 26 | pm.modeFilter(freq*2.09*2, 0.3*resMul, 0.1*(modes > 1)), 27 | pm.modeFilter(freq*2.56*3, 0.1*resMul, 0.07), 28 | 29 | pm.modeFilter(2*freq*2.09*2, 0.3*resMul, 0.02*(modes > 1)), 30 | pm.modeFilter(2*freq*2.56*3, 0.1*resMul, 0.01), 31 | 32 | pm.modeFilter(4*freq*2.09*2, 0.3*resMul, 0.05*(modes > 1)), 33 | pm.modeFilter(4*freq*2.56*3, 0.1*resMul, 0.03) 34 | :> _; 35 | 36 | process = exc : trem : barCore; -------------------------------------------------------------------------------- /MusicSource/patches/melodic-bottle/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add some delay, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | bounce = hslider("bounce", 0, 0, 1, 0.0001); 6 | 7 | no_bounce = 1 - 0.8*bounce; 8 | del_st = (+ : @(1.1 : ba.sec2samp) * 0.4*bounce) ~ _, (+ : @(1.8 : ba.sec2samp) * 0.4*bounce) ~ _; 9 | del_mix = _ <: _*no_bounce, _*no_bounce, del_st :> _,_; 10 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 7, 7, 44100); 11 | 12 | process = _*preamp : fi.highpass(1, 400) : del_mix <: _*0.5, _*0.5, rev_st :> _, _ : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/melodic-bottle/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Melodic bottle. This is a classic waveguide model for a closed-ended wind 3 | * instrument. 4 | * 5 | * Audio-rate code is written in Faust and can be found in the faust directory. 6 | * 7 | * This file contains sequencing, envelopes, and other control code. 8 | */ 9 | 10 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 11 | import { mixFreqs } from '../../lib/math.js'; 12 | 13 | export const sampleRate = 44100; 14 | const graph = new Graph({ sampleRate }); 15 | const post = new FaustNode('faust/post.dsp', { preamp: 1, bounce: 0 }); 16 | 17 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 18 | const fParam1 = graph.addParam('freq1', { def: '100*4' }); 19 | const fParam2 = graph.addParam('freq2', { def: '100*6' }); 20 | const hiLen = graph.addParam('hilen', { def: 9, min: 4, max: 105 }); 21 | const breath = graph.addParam('breath'); 22 | const trem = graph.addParam('trem'); 23 | const modes = graph.addParam('modes', { def: 4, min: 0, max: 4 }); 24 | const bounce = graph.addParam('bounce'); 25 | bounce.connect(post.bounce) 26 | // resMul = 1/Math.pow(10, breath.value) 27 | // ampMul = 1+breath.value*2 28 | 29 | const mkVoice = i => { 30 | const ret = new FaustNode('faust/bottle.dsp', { freq: 500, noise: 0, resmul: 1, modes: 4, trem: 0, bounce: 0 }); 31 | ret.notePos = 100; 32 | ret.amp = 1; 33 | ret.note = (freq, amp) => { 34 | ret.notePos = 0; 35 | ret.amp = amp * (1+breath.value*4) * (5/(modes.value+1)); 36 | ret.freq.value = freq; 37 | ret.modes.value = modes.value; 38 | ret.trem.value = trem.value; 39 | ret.resmul.value = 1/Math.pow(10, breath.value*2); 40 | ret.bounce.value = bounce.value; 41 | }; 42 | ret.ctrl = () => { 43 | ret.notePos += 0.002; 44 | ret.noise.value = pulse(2*Math.pow(10, 1+ret.bounce.value), ret.notePos) * ret.amp; 45 | }; 46 | return ret; 47 | }; 48 | const poly = new Poly(3, mkVoice, post); 49 | const pulse = (k, x) => Math.max(0, (2 * Math.sqrt(k) * x) / (1 + k * x * x) - x * 0.1); 50 | graph.ctrl(tSeconds => { 51 | poly.forEach(voice => voice.ctrl()); 52 | }); 53 | let spliceFreq, 54 | noteN = 0; 55 | 56 | post.connect(graph.out); 57 | const evil = (a, b) => Math.max(a, b) / Math.min(a, b) < 10 / 9; 58 | let intro = 3; 59 | const seq = new Seq(graph); 60 | seq.schedule(async () => { 61 | let flowlen = 3, 62 | flowdir = 2; 63 | while (true) { 64 | for (let fpos = 0; fpos < flowlen * 2; fpos++) { 65 | const mfs = mixFreqs(fParam1.value, fParam2.value, 6); 66 | const ofs = Math.floor((flowlen - 2) * 0.13); 67 | let freqs = mfs.filter((_, i) => i >= ofs && i < flowlen + ofs && !(i % 2)); 68 | freqs.push(...mfs.filter((_, i) => i >= ofs && i < flowlen + ofs && i % 2).reverse()); 69 | freqs = freqs.filter((f, i) => i < 3 || !evil(f, freqs[i - 1])); 70 | 71 | intro = Math.max(intro - 0.5, 0); 72 | if (hiLen.value > 4) poly.note(freqs[fpos % freqs.length], 0.5 + 0.5 / (fpos + 1)); 73 | await seq.play(0.3 + 2 / (flowlen * 0.5 + 5) + Math.round(intro)); 74 | if (noteN++ > 15 && fpos % 2 && spliceFreq === freqs[fpos % freqs.length]) { 75 | noteN = 0; 76 | } 77 | if (noteN > 10 && !spliceFreq && fpos % 2) { 78 | noteN = 0; 79 | spliceFreq = freqs[fpos % freqs.length]; 80 | } 81 | await seq.play(fpos % 2); 82 | } 83 | if (flowdir > 0 && flowlen >= hiLen.value) flowdir = -flowdir; 84 | if (flowdir < 0 && flowlen <= 3) flowdir = -flowdir; 85 | flowlen += flowdir; 86 | } 87 | }); 88 | 89 | export const process = graph.makeProcessor(); 90 | -------------------------------------------------------------------------------- /MusicSource/patches/melodic-soprano/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add a phaser, delay, and reverb 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | slowness = hslider("slowness", 1, 0, 1, 0.0001); 6 | 7 | phaser = pf.phaser2_mono(4, 0, 1000, 400, 1.5, 1500, 0.7, 1.5, 0.6, 1); 8 | dry = _*(0.2 - slowness*0.2); 9 | del(t) = + ~ @(t : ba.sec2samp) * 0.8 * slowness; 10 | 11 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 7, 44100); 12 | 13 | process = phaser*preamp <: dry, dry, rev_st :> del(1.13), del(1.7); -------------------------------------------------------------------------------- /MusicSource/patches/melodic-soprano/faust/soprano.dsp: -------------------------------------------------------------------------------- 1 | // Soprano vocal synth in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | f1 = hslider("f1", 200, 30, 1000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | sawAmt = hslider("saw", 0, 0, 1, 0.0001); 9 | highness = hslider("highness", 0, 0, 1, 0.0001); 10 | 11 | // freqs, Qs and amps for soprano formants 12 | // http://www.csounds.com/manual/html/MiscFormants.html 13 | sop_a_freq = (800, 1150, 2900, 3900, 4950); 14 | sop_e_freq = (350, 2000, 2800, 3600, 4950); 15 | sop_i_freq = (270, 2140, 2950, 3900, 4950); 16 | sop_o_freq = (450, 800, 2830, 3800, 4950); 17 | sop_u_freq = (325, 700, 2700, 3800, 4950); 18 | sop_a_amp = (0.75, 0.501, 0.1, 0.1, 0.02); 19 | sop_e_amp = (1, 0.1, 0.178, 0.01, 0.002); 20 | sop_i_amp = (1, 0.251, 0.05, 0.05, 0.006); 21 | sop_o_amp = (0.75, 0.282, 0.1, 0.1, 0.02); 22 | sop_u_amp = (1, 0.158, 0.018, 0.01, 0.001); 23 | sop_a_q = (10, 13, 24, 30, 35); 24 | sop_e_q = (6, 20, 23, 24, 25); 25 | sop_i_q = (5, 24, 30, 33, 41); 26 | sop_o_q = (11, 10, 28, 32, 41); 27 | sop_u_q = (7, 12, 16, 21, 25); 28 | 29 | // Exciter: filtered oscillators 30 | saw = os.sawtooth(f1) * sawAmt * 0.4 : fi.lowpass(1, f1*2) : fi.lowpass(1, 500+f1); 31 | noise = no.noise * 0.01 * noiseAmt 32 | : fi.ffbcombfilter(1/50 : ba.sec2samp, 1/f1 : ba.sec2samp, 0.9) 33 | : fi.lowpass(2, (600 + f1*1.5)*(0.2+0.8*highness)); 34 | exc = saw*0.8 + noise; 35 | 36 | // Body: formant bank 37 | vowel = os.osc(0.3)*0.12+0.18;//0.3;//os.osc(4)*0.7+0.71; 38 | linterp(lst1, lst2, i) = ba.take(i+1, lst1), ba.take(i+1, lst2) : si.interpolate(vowel); 39 | form(i) = fi.resonbp(f, q, a) with { 40 | f = linterp(sop_a_freq, sop_o_freq, i); 41 | q = linterp(sop_a_q, sop_o_q, i); 42 | a = linterp(sop_a_amp, sop_o_amp, i); 43 | }; 44 | hform = fi.resonbp(5500, 10, 0.2); 45 | anti = fi.notchw(300, 2000) : fi.notchw(600, 3500); 46 | forms = _ <: par(i, 5, form(i)), hform :> _; 47 | syn = exc : forms; 48 | 49 | // Per-voice post-processing with compression 50 | timbre = fi.highpass(1, 3000) : fi.high_shelf(8, 1500+1000*highness) : fi.notchw(150, 720); 51 | limiter = *(10) : co.limiter_1176_R4_mono; 52 | post = timbre : limiter; 53 | 54 | process = syn : post; 55 | -------------------------------------------------------------------------------- /MusicSource/patches/melodic-soprano/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Melodic soprano. This is a simple vocal synth, starting with a sawtooth wave 3 | * combined with noise, which are then fed into a formant filter bank. 4 | * 5 | * Audio-rate code is written in Faust and can be found in the faust directory. 6 | * 7 | * This file contains sequencing, envelopes, and other control code. 8 | */ 9 | 10 | import { Graph, Seq, FaustNode, CtrlSine, SampleProcessor } from '../../lib/tealib.js'; 11 | import { mixFreqs, randomSeed } from '../../lib/math.js'; 12 | 13 | export const sampleRate = 44100; 14 | const graph = new Graph({ sampleRate }); 15 | 16 | // Post-processing Faust node 17 | const post = new FaustNode('faust/post.dsp', { preamp: 1, slowness: 0 }); 18 | post.connect(graph.out); 19 | 20 | 21 | // Basic parameters to control the patch 22 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 23 | const fParam1 = graph.addParam('freq1', { def: '100*8' }); 24 | const fParam2 = graph.addParam('freq2', { def: '100*9' }); 25 | // Params to control the generative melody 26 | const fMin = graph.addParam('minFreq', { def: 350, min: 50, max: 3000 }); 27 | const fMax = graph.addParam('maxFreq', { def: 1000, min: 50, max: 3000 }); 28 | const complexity = graph.addParam('complexity', { def: 1, max: 3 }); 29 | const skip = graph.addParam('skip', { max: 12 }); 30 | const slowness = graph.addParam('slowness'); 31 | 32 | const rand = randomSeed(); 33 | 34 | /** 35 | * Class for a vocal synth Faust node with additional control-rate processing. 36 | * Control it by setting the "fTgt" and "pressTgt" properties to the desired 37 | * frequency and pressure level. Call "ctrl" at control rate and get the output 38 | * from the Faust node. 39 | */ 40 | export class Soprano extends FaustNode { 41 | constructor() { 42 | super('faust/soprano.dsp', { f1: 0, noise: 0, saw: 0, highness: 0 }); 43 | this.freq = 100; 44 | this.fTgt = 100; 45 | this.fChange = 100; 46 | this.press = 0; 47 | this.pressTgt = 0; 48 | this.vibNode = new CtrlSine({ phase: rand() }); 49 | this.vibNode 50 | .connect(new SampleProcessor(v => (1 + v * 0.02) * this.freq)) 51 | .connect(this.f1); 52 | } 53 | ctrl(t) { 54 | // slowness 0 -> df 0.03 ; slowness 1 -> df 0.005 55 | const fDif = (this.fTgt - this.freq) * (0.03-0.025*slowness.value); 56 | const fAbs = Math.abs(fDif); 57 | 58 | if (fAbs > this.fChange) this.fChange += (fAbs - this.fChange) * 0.1; 59 | else this.fChange += (fAbs - this.fChange) * 0.007; 60 | if (this.fChange > 1) this.fChange = 1; 61 | 62 | if (this.pressTgt > this.press) this.press += (this.pressTgt - this.press) * 0.02; 63 | else this.press += (this.pressTgt - this.press) * 0.002; 64 | 65 | this.freq += fDif; 66 | this.vibNode.freq.value = 3 + Math.sqrt(this.fChange) * 4; 67 | this.vibNode.amp.value = 1 - this.fChange; 68 | this.noise.value = this.fChange * 0.2 + 0.2; 69 | this.noise.value *= this.press; 70 | this.saw.value = (1 - this.fChange) * 0.2 + 0.15; 71 | this.saw.value *= this.press * this.press; 72 | this.highness.value = 1-30/(t*t+30); 73 | } 74 | init(freq) { 75 | this.freq = freq 76 | this.fTgt = freq 77 | this.fChange = 1; 78 | } 79 | } 80 | 81 | const sop1 = new Soprano(); 82 | const sop2 = new Soprano(); 83 | sop1.connect(post); 84 | sop2.connect(post); 85 | slowness.connect(post.slowness); 86 | 87 | let freqs1, freqs2, freqi = 0; 88 | 89 | // Generate a set of pitches for the melody 90 | const setFreqs = () => { 91 | const all = mixFreqs(fParam1.value, fParam2.value, 6); 92 | let mfs = all.filter(f => f > fMin.value && f < fMax.value); 93 | if (mfs.length > 6) mfs.length = 6; 94 | mfs.sort((a, b) => b - a); // descending 95 | freqs1 = []; 96 | for (let i = 0; i < mfs.length; i += 2) freqs1.push(mfs[i]); 97 | freqs1.reverse(); 98 | for (let i = 1; i < mfs.length; i += 2) freqs1.push(mfs[i]); 99 | freqs2 = freqs1.map(ref => { 100 | for (const f of all) { 101 | if (f < ref*0.84 && f > ref*0.5) return f; 102 | if (f/2 < ref*0.84 && f/2 > ref*0.5) return f/2; 103 | } 104 | throw new Error('No backing pitch found.'); 105 | }) 106 | if (!sop1.pressTgt) sop1.init(freqs1[0] * 0.75); 107 | if (!sop2.pressTgt) sop2.init(freqs2[0] * 0.75); 108 | }; 109 | setFreqs(); 110 | 111 | graph.ctrl(t => { 112 | sop1.ctrl(t); 113 | sop2.ctrl(t); 114 | }); 115 | 116 | // Play a melody according to the parameters and generated pitches 117 | const seq = new Seq(graph); 118 | seq.schedule(async () => { 119 | if (skip.value) { 120 | freqi = Math.floor(skip.value) % freqs1.length; 121 | sop1.init(freqs1[freqi] * 0.75); 122 | sop2.init(freqs2[freqi] * 0.75); 123 | } 124 | while (true) { 125 | if ([fParam1, fParam2, fMin, fMax].some(p => p.changed())) setFreqs(); 126 | if (complexity.value > 1.5) sop2.pressTgt = 1; 127 | sop2.fTgt = freqs2[freqi]; 128 | (async () => { 129 | await seq.play(rand()*0.5); 130 | sop1.pressTgt = 1; 131 | sop1.fTgt = freqs1[freqi]; 132 | })(); 133 | await seq.play(3); 134 | if ([fParam1, fParam2, fMin, fMax].some(p => p.changed())) setFreqs(); 135 | sop1.fTgt = freqs1[(freqi + 1) % freqs1.length]; 136 | sop2.fTgt = freqs2[(freqi + 1) % freqs1.length]; 137 | await seq.play(3); 138 | if ((complexity.value % 2) > 0.5) { 139 | sop1.fTgt = freqs1[freqi]; 140 | sop2.fTgt = freqs2[freqi]; 141 | } 142 | await seq.play(3); 143 | sop1.pressTgt = 0; 144 | sop2.pressTgt = 0; 145 | await seq.play(1); 146 | await seq.play(2); 147 | freqi = (freqi + 1) % freqs1.length; 148 | sop1.fTgt = freqs1[freqi] * 0.75; 149 | sop2.fTgt = freqs2[freqi] * 0.75; 150 | await seq.play(1); 151 | } 152 | }); 153 | 154 | 155 | export const process = graph.makeProcessor(); 156 | -------------------------------------------------------------------------------- /MusicSource/patches/moon-strings/faust/pluck.dsp: -------------------------------------------------------------------------------- 1 | // Plucked string instrument in Faust based on Karplus–Strong synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | lp1 = hslider("lp1", 400, 100, 15000, 0.0001); 9 | fb = hslider("fb", 0, 0, 1, 0.00001); 10 | 11 | // Convert frequency to delay line length in samples 12 | f2samp(f) = (f : ma.inv : ba.sec2samp) - 1; 13 | 14 | // Exciter: a filtered oscillator 15 | exc = no.noise * noiseAmt * 0.6 : fi.lowpass(1, lp1); 16 | 17 | // Delay line in both directions 18 | loop1 = + ~ (de.fdelay2(9000, f2samp(freq/1.004)) * -fb); 19 | loop2 = + ~ (de.fdelay2(9000, f2samp(freq*1.004)) * fb); 20 | 21 | process = exc <: (loop1 : @(0.02 : ba.sec2samp)), loop2*0.7 :> _; -------------------------------------------------------------------------------- /MusicSource/patches/moon-strings/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | delfb1 = hslider("delfb", 0.5, 0, 1, 0.0001); 6 | 7 | // Phaser and reverb 8 | kill_dc = fi.highpass(2, 60); 9 | phaser = pf.phaser2_mono(4, 0, 1000, 200, 1.5, 900, 0.6, 1, -0.6, 0); 10 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 15, 6, 44100); 11 | 12 | // Resonant body 13 | body = _ <: 14 | fi.lowpass(1, 1500)*0.7, 15 | fi.resonbp(2000, 10, 0.1), 16 | fi.resonbp(3200, 10, 0.2), 17 | fi.resonbp(7700, 2, 0.3) 18 | :> _; 19 | 20 | // Delays 21 | delfb2 = 0.5 + delfb1*0.49; 22 | loop1 = + ~ (@(0.283 : ba.sec2samp) * -delfb2 : fi.lowpass(1, 5000) : fi.highpass(2, 100)); 23 | loop2 = + ~ (@(0.937 : ba.sec2samp) * -delfb2 : fi.lowpass(1, 5000) : fi.highpass(2, 100)); 24 | delmix(dry, del1, del2) = dry*0.006+del1, dry*0.012+del2; 25 | del = _ <: _, loop1, loop2 : delmix; 26 | mixer(rev1, rev2, del1, del2) = del1+rev1*0.8, del2+rev2*0.8; 27 | 28 | process = kill_dc*preamp : phaser <: (_ <: rev_st), (body : del) : mixer : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/moon-strings/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Plucked string arpeggio based on Karplus–Strong synthesis. 3 | * 4 | * Audio-rate code is written in Faust and can be found in the faust directory. 5 | * 6 | * This file contains sequencing, envelopes, and other control code. 7 | */ 8 | 9 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 10 | import { mixFreqs, randomSeed, rhythmArray, biasedRandom } from '../../lib/math.js'; 11 | 12 | export const sampleRate = 44100; 13 | const graph = new Graph({ sampleRate }); 14 | 15 | // Post-processing Faust node 16 | const post = new FaustNode('faust/post.dsp', { preamp: 1, delfb: .5 }); 17 | post.connect(graph.out); 18 | 19 | // Parameters to control the patch 20 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 21 | // Frequency centers for the generative strums 22 | const fParam1 = graph.addParam('freq1', { def: '100*8' }); 23 | const fParam2 = graph.addParam('freq2', { def: '100*9' }); 24 | // Timbral paramters: lowpass and feedback 25 | const lpParam = graph.addParam('lp', { def: 350, min: 50, max: 2000 }); 26 | const fbParam = graph.addParam('fb', { max: .9 }); 27 | // Post-processing delay feedback 28 | graph.addParam('delfb', { def: .5 }).connect(post.delfb); 29 | // Parameters to control the generative strumming intensity 30 | const density = graph.addParam('density', { def: 1, min: 0, max: 1 }); 31 | const flatParam = graph.addParam('flatness'); 32 | 33 | // The string synth is polyphonic. This creates one polyphonic voice 34 | const mkVoice = i => { 35 | const ret = new FaustNode('faust/pluck.dsp', { freq: 500, noise: 0, lp1: 3000, fb: 0 }); 36 | lpParam.connect(ret.lp1); 37 | ret.notePos = 100; 38 | ret.amp = 1; 39 | ret.note = (freq, amp) => { 40 | ret.freq.value = freq; 41 | ret.fb.value = (1 - (1 - fbParam.value)*(3/freq)); 42 | ret.notePos = 0; 43 | ret.amp = amp; 44 | }; 45 | return ret; 46 | }; 47 | 48 | // Use the Poly class to make an instrument with 6 managed voices 49 | const poly = new Poly(6, mkVoice, post); 50 | 51 | // At control rate, apply an envelope to all voices 52 | const atk = 0.007; 53 | const env = (x) => { 54 | if (x < atk) return (x/atk)*(x/atk); 55 | else return Math.max(0, (1.5)/((x-atk)*300+1)-0.5); 56 | } 57 | graph.ctrl((tSeconds, delta) => { 58 | poly.forEach(voice => { 59 | voice.notePos += delta; 60 | voice.noise.value = env(voice.notePos) * voice.amp; 61 | }); 62 | }); 63 | 64 | const seq = new Seq(graph); 65 | const scaleLen = 10; 66 | 67 | // "mod soup" arpeggiator functions: 68 | // use a graph tool (eg js-graphy) to edit them with visual feedback 69 | const fl = Math.floor; 70 | const arpNote = (t) => fl(t%5)+5+fl(-t*0.1)%(fl(t*0.025)%5+2); 71 | const arpVel = (t) => 0.6+fl(t%5)*0.1-0.1*arpNote(fl(t/5)*5+0.1); 72 | const arpDel = (t) => { 73 | let d = 0.4*Math.abs(fl((t+1)%5)-2.5); 74 | return (d*d*d+1.5)*0.055876; 75 | }; 76 | const swell = (t) => { 77 | let s = t+200/(t+7); 78 | s = 0.5+Math.cos(s*Math.PI*2/200)*0.5; 79 | return 1-s*s*s; 80 | }; 81 | 82 | // Generate a set of pitches for the strumming 83 | let freqs; 84 | const setFreqs = () => { 85 | freqs = mixFreqs(fParam1.value, fParam2.value, 4); 86 | freqs = freqs.slice(0,scaleLen).sort((a, b) => a - b); 87 | }; 88 | const tRand = randomSeed(1); 89 | const thresholds = rhythmArray({len: 40, cycles: [2,5,10,15]}) 90 | .map(x => biasedRandom(x, 4, tRand)) 91 | .map((x,i) => 1 - Math.sqrt(x)*(Math.sin(i*.25)*.5+.5)); 92 | 93 | // Strum according to the parameters and generated pitches 94 | seq.schedule(async () => { 95 | let t = 4; 96 | while (true) { 97 | if (fParam1.changed() || fParam2.changed()) setFreqs(); 98 | // if (t%200 === 0) console.log('loop len: '+(graph.timeSmp/graph.sampleRate)) 99 | if (t === 400) t = 0; 100 | if (thresholds[t%40] < density.value) 101 | poly.note(freqs[arpNote(t+0.1)], Math.pow(arpVel(t+0.1)*swell(t), 1-0.9*flatParam.value)); 102 | await seq.play(arpDel(t+0.1)); 103 | t++; 104 | } 105 | }); 106 | 107 | export const process = graph.makeProcessor(); 108 | -------------------------------------------------------------------------------- /MusicSource/patches/ocean-theme/faust/bass.dsp: -------------------------------------------------------------------------------- 1 | // Contrabass instrument in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 7 | freq = hslider("freq", 80, 30, 200, 0.001); 8 | lp1 = hslider("lp1", 3300, 100, 10000, 0.0001); 9 | lp2 = hslider("lp2", 580, 100, 1000, 0.0001); 10 | noise1amt = hslider("noise1", 0, 0, 1, 0.0001); 11 | 12 | // Convert frequency to delay line length in samples 13 | f2samp(f) = (f : ma.inv : ba.sec2samp) - 1; 14 | 15 | // Exciter: filtered oscillator 16 | noise1 = no.noise * noise1amt * 0.5 : fi.lowpass(1, lp1) : fi.lowpass(2, lp2); 17 | exc = noise1 : fi.highpass(2, 80); 18 | 19 | // Resonator with artifacts 20 | reso = loop(vibrato-alias) : loop(vibrato+alias) with { 21 | vibrato = freq * (1 + os.osc(4) * 0.008); 22 | alias = (3 / ma.SR) * freq * freq; 23 | loop(f) = + ~ (de.fdelay2(9000, f2samp(f)) * 0.8); 24 | }; 25 | 26 | // Post-processing: reverb and compression 27 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 28 | process = exc*preamp : reso <: rev_st : co.limiter_1176_R4_stereo; 29 | -------------------------------------------------------------------------------- /MusicSource/patches/ocean-theme/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust with delay, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | lpf = hslider("lpf", 500, 20, 10000, 0.001); 6 | 7 | lp = fi.lowpass(2, lpf); 8 | hp = fi.highpass(3, 200); 9 | 10 | // mono -> stero ping pong delay 11 | loopy_mst(dry) = dry*0.7 + l1*0.2 + l2*0.05, dry*0.7 + l1*0.1 + l2*0.2 with { 12 | l1 = dry : + ~ (de.fdelay2(9000, 0.001+0.0003*os.oscp(0.1, 2) : ba.sec2samp) * 0.6); 13 | l2 = dry : + ~ (de.fdelay2(9000, 0.001+0.0003*os.oscp(0.1, 0) : ba.sec2samp) * 0.6); 14 | }; 15 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 2.4, 2.7, 44100); 16 | del_st = + ~ @(1.57 : ba.sec2samp) * 0.8, + ~ @(1.7 : ba.sec2samp) * 0.8; 17 | 18 | process = _*preamp*0.3 : hp : lp : loopy_mst : del_st : rev_st : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/ocean-theme/faust/string.dsp: -------------------------------------------------------------------------------- 1 | // String-like waveguide instrument in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 80, 30, 2000, 0.001); 7 | lp1 = hslider("lp1", 3300, 100, 15000, 0.0001); 8 | lp2 = hslider("lp2", 580, 100, 15000, 0.0001); 9 | noise1amt = hslider("noise", 0, 0, 1, 0.0001); 10 | 11 | // Exciter: filtered oscillator 12 | noise1 = no.noise * noise1amt * 0.5 : fi.lowpass(1, lp1) : fi.lowpass(2, lp2)*2; 13 | exc = noise1 : fi.highpass(2, freq*1.2) : fi.notchw(10, freq) : fi.notchw(5, freq*2) : _*2; 14 | 15 | // Waveguide filters and non-linearity 16 | wgbottom = 10 + freq * 0.1; 17 | wgtop = 140 + freq*1.75; 18 | NL(x) = x / ( (x*x) + 1 ); 19 | 20 | // Convert frequency to delay line length in samples 21 | wgf2samp(f) = 1.011 / (f + ((0.1786*f*f)/(wgtop+wgbottom)) ) : ba.sec2samp; 22 | 23 | // Waveguide loop 24 | reso = loop(vibrato) with { 25 | vibrato = freq * (1 + os.osc(5) * 0.006); 26 | alias = 0.013 * freq; 27 | wgf = fi.lowpass(1, wgtop) : fi.highpass(1, wgbottom); 28 | wg(f) = de.fdelay2(9000, wgf2samp(f)) : wgf : NL * 1.4; 29 | loop(f) = + ~ wg(f); 30 | }; 31 | 32 | process = exc : reso; 33 | -------------------------------------------------------------------------------- /MusicSource/patches/ocean-theme/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * String-like polyphonic patch playing a slow and simple progression. 3 | * 4 | * Audio-rate code is written in Faust and can be found in the faust directory. 5 | * 6 | * This file contains sequencing, envelopes, and other control code. 7 | */ 8 | 9 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 10 | import { mixFreqs } from '../../lib/math.js'; 11 | 12 | export const sampleRate = 44100; 13 | const graph = new Graph({ sampleRate }); 14 | 15 | // Create some nodes: Faust bass, and post-processing for the strings 16 | const post = new FaustNode('faust/post.dsp', { preamp: 1, lpf: 16000 }); 17 | const bass = new FaustNode('faust/bass.dsp', { preamp: 1, freq: 0, noise1: 0, lp1: 700, lp2: 3500 }); 18 | post.connect(graph.out); 19 | bass.connect(graph.out); 20 | 21 | //Parameters to control the patch 22 | const preamp = graph.addParam('preamp', { def: 1 }); 23 | preamp.connect(post.preamp); 24 | preamp.connect(bass.preamp); 25 | // Frequency centers for the generative progression 26 | const fParam1 = graph.addParam('freq1', { def: '100*8' }); 27 | const fParam2 = graph.addParam('freq2', { def: '100*8*4/5' }); 28 | // Timbral parameters: lowpasses, bass amount 29 | const lp1Param = graph.addParam('lp1', { def: 700, min: 100, max: 15000 }); 30 | const lp2Param = graph.addParam('lp2', { def: 1200, min: 100, max: 15000 }); 31 | const bassiness = graph.addParam('bassiness'); 32 | // Parameter to pause the progression 33 | const freeze = graph.addParam('freeze'); 34 | 35 | // The strings are polyphonic. This creates one polyphonic voice 36 | const mkVoice = i => { 37 | const ret = new FaustNode('faust/string.dsp', { freq: 500, noise: 0, lp1: 3300, lp2: 580 }); 38 | lp1Param.connect(ret.lp1); 39 | lp2Param.connect(ret.lp2); 40 | ret.notePos = 100; 41 | ret.amp = 1; 42 | ret.note = (freq, amp) => { 43 | ret.freq.value = freq/2; 44 | ret.notePos = 0; 45 | ret.amp = amp; 46 | }; 47 | return ret; 48 | }; 49 | 50 | // Use the Poly class to make an instrument with 10 managed voices 51 | const poly = new Poly(10, mkVoice, post); 52 | 53 | // At control rate, apply an envelope to all voices 54 | const atk = 4, rel = 3; 55 | const env = (x) => { 56 | if (x < atk) return 1-Math.cos(Math.PI*x/atk); 57 | if (x < atk+rel) return 1+Math.cos(Math.PI*(x-atk)/rel); 58 | return 0; 59 | }; 60 | let bassTgt = 0; 61 | graph.ctrl((tSec, delta) => { 62 | post.lpf.value = 16000*(1-.8/(tSec*tSec*0.002+1)); 63 | bass.noise1.value += (bassTgt - bass.noise1.value)*0.001; 64 | poly.forEach(voice => { 65 | voice.notePos += delta; 66 | voice.noise.value = env(voice.notePos) * voice.amp; 67 | }); 68 | }); 69 | 70 | const seq = new Seq(graph); 71 | 72 | // Generate a set of pitches for the progression 73 | const scaleLen = 6; 74 | let freqs; 75 | const setFreqs = () => { 76 | freqs = mixFreqs(fParam1.value, fParam2.value, 0.5); 77 | freqs = freqs.filter(f => f>567 && f<1380).slice(0,scaleLen).sort((a, b) => a - b); 78 | }; 79 | const noteGen = function * () { 80 | let i = 0; 81 | while (true) { 82 | yield i % scaleLen; 83 | yield (i+4) % scaleLen; 84 | i++; 85 | } 86 | }; 87 | 88 | // Run the progression according to the parameters and generated pitches 89 | seq.schedule(async () => { 90 | const gen = noteGen(); 91 | while (true) { 92 | if (fParam1.changed() || fParam2.changed()) setFreqs(); 93 | const i = gen.next().value; 94 | poly.note(freqs[i], 0.7); 95 | if (i < scaleLen/2) { 96 | bass.freq.value = freqs[i]; 97 | bass.noise1.value = 0; 98 | bassTgt = bassiness.value*2; 99 | while (bass.freq.value > 110) bass.freq.value /= 2; 100 | } 101 | while (i === 0 && freeze.value) await seq.play(1); 102 | await seq.play(5); 103 | } 104 | }); 105 | 106 | export const process = graph.makeProcessor(); 107 | -------------------------------------------------------------------------------- /MusicSource/patches/rain-stick/faust/stick.dsp: -------------------------------------------------------------------------------- 1 | // Rainstick instrument in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 7 | density = hslider("density", 0, 0, 1, 0.0001); 8 | noiseamp = hslider("noiseamp", 0, 0, 1, 0.0001); 9 | bodyf = hslider("bodyf", 800, 200, 2000, 0.001); 10 | 11 | // Rainstick resonant body 12 | durmul = 0.003; 13 | body = _*0.5 : fi.highpass(3, 900) <: 14 | fi.highpass(3, 5000)*2, 15 | pm.modeFilter(bodyf*1.0, 4.58*durmul, 0.07), 16 | pm.modeFilter(bodyf*1.53, 4.59*durmul, 0.05), 17 | pm.modeFilter(bodyf*1.97, 4.6*durmul, 0.06), 18 | pm.modeFilter(bodyf*2.98, 4.7*durmul, 0.05), 19 | pm.modeFilter(bodyf*5.41, 0.65*durmul, 0.02), 20 | pm.modeFilter(bodyf*9, 0.65*durmul, 0.04) 21 | :> _; 22 | 23 | // Noise grains using "sparse_noise" as a trigger 24 | noise2 = no.sparse_noise(density) : an.amp_follower(0.2) : _*no.pink_noise*noiseamp*preamp 25 | : fi.highpass(2, 3700); 26 | n3atk = 0.01; 27 | noise3 = no.sparse_noise(density*10) : ba.peakholder(n3atk : ba.sec2samp) 28 | : en.are(n3atk, 1.5) : _*no.pink_noise*noiseamp*preamp 29 | : fi.highpass(2, 3200); 30 | 31 | // Post-processing: reverb, delay, compression 32 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 0.4, 0.7, 44100); 33 | del_st = + ~ @(0.05 : ba.sec2samp) * 0.6, + ~ @(0.07 : ba.sec2samp) * 0.6; 34 | del_st2 = + ~ @(1.3 : ba.sec2samp) * 0.9, + ~ @(1.5 : ba.sec2samp) * 0.9; 35 | 36 | post = _ : body+noise2*0.2+noise3*0.1 <: _,_,del_st :> _,_,del_st2 :> rev_st : @(1.5 : ba.sec2samp),_ : co.limiter_1176_R4_stereo; 37 | process = _*preamp*(0.07 + no.pink_noise*1.5)+noise2*0.6+noise3*0.6 : post; 38 | -------------------------------------------------------------------------------- /MusicSource/patches/rain-stick/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ambient rainstick. 3 | * 4 | * A fairly obvious application of granular synthesis, with a mix of sine and 5 | * noise grains. This file contains control code and generates the dry sine 6 | * grains. 7 | * 8 | * All other audio-rate code can be found in the faust directory. 9 | */ 10 | 11 | import { Graph, FaustNode, Sine } from '../../lib/tealib.js'; 12 | import { randomSeed, goldSeed } from '../../lib/math.js'; 13 | 14 | // set to false to manually control density: 15 | const autoDensity = true; 16 | 17 | export const sampleRate = 44100; 18 | const graph = new Graph({ sampleRate }); 19 | 20 | // Create Faust node for noise grains, body and post-processing 21 | const stick = new FaustNode('faust/stick.dsp', { preamp: 1, density: 0, noiseamp: 1 }); 22 | stick.connect(graph.out); 23 | 24 | // Parameters to control the patch: amp, pitch, density 25 | graph.addParam('preamp', { def: 1 }).connect(stick.preamp); 26 | const pitchParam = graph.addParam('pitch', { def: .5 }); 27 | const density = (autoDensity) ? 28 | stick.density : 29 | graph.addParam('density', { def: 10, min: 1, max: 80 }); 30 | 31 | // Create sine grains and connect them to the stick node 32 | const rand = randomSeed(0); 33 | const gold = goldSeed(0); 34 | const nsines = 10; 35 | const sines = [...new Array(nsines)].map(i => { 36 | const ret = new Sine(); 37 | ret.f1 = 1000; 38 | ret.f2 = 1000; 39 | ret.amax = 0; 40 | ret.t = 1000; 41 | return ret; 42 | }); 43 | sines.forEach(s => s.connect(stick)); 44 | let sinei = 0, lfo1 = 0; 45 | const sig = x => Math.atan(x*2*4-4)/Math.PI+.5; 46 | const isig = x => ((x-.5)*1.5874)**3+.5; 47 | const trigger = () => { 48 | const sine = sines[sinei]; 49 | sine.f1 = 3000 + 8000*pitchParam.value + 200*lfo1 - 1300*isig(gold()); 50 | sine.f2 = sine.f1 - 2000 - 200*rand(); 51 | sine.amax = (0.2 + (rand()**2)*0.8)*(sig(density.value/30)); 52 | sine.t = 0; 53 | sinei = (sinei+1) % nsines; 54 | }; 55 | 56 | // Control rate envelopes for the grains 57 | const atk = 0.01, rel = 0.015, mid = 0.015; 58 | const env = (x) => { 59 | if (x < atk) return .5-.5*Math.cos(Math.PI*x/atk); 60 | if (x < atk+rel*3) return Math.pow((.5+.5*Math.cos(Math.PI*(x-atk)/(rel*10))), 100); 61 | return 0; 62 | }; 63 | const latk = 10, lrel = 20 64 | const lenv = x => { 65 | const xr = x % (latk+lrel); 66 | return Math.min(xr/latk, (1-(xr-latk)/lrel)**2); 67 | }; 68 | 69 | // At control rate, trigger sine grains and control noise rate in the stick 70 | let countdown = 0; 71 | graph.ctrl((t, delta) => { 72 | if (autoDensity) density.value = lenv(t)*20+1; 73 | stick.noiseamp.value = 0.05 + 0.12*sig(density.value / 15); 74 | lfo1 = Math.sin(t*0.2); 75 | countdown -= delta; 76 | if (countdown < 0) { 77 | countdown = (0.3+0.5*rand()) / Math.max(5,density.value); 78 | trigger(); 79 | } 80 | for (const sine of sines) { 81 | sine.amp.value = env(sine.t) * sine.amax; 82 | sine.freq.value = sine.t < mid ? sine.f1 : sine.f2; 83 | if (sine.f2 > 100) sine.f2 *= 0.97; 84 | sine.t += delta; 85 | } 86 | }); 87 | 88 | export const process = graph.makeProcessor(); 89 | -------------------------------------------------------------------------------- /MusicSource/patches/resonant-drone/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add reverb 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | lp1 = hslider("lp1", 15000, 100, 15000, 0.0001); 6 | 7 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 8 | 9 | process = _*preamp : fi.lowpass(1, lp1) <: rev_st; -------------------------------------------------------------------------------- /MusicSource/patches/resonant-drone/faust/reso.dsp: -------------------------------------------------------------------------------- 1 | // "Resodrone" synthesizer in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | lp1 = hslider("lp1", 100, 50, 800, 0.001); 7 | f1 = hslider("f1", 100, 30, 330, 0.0001); 8 | f2 = hslider("f2", 123, 30, 330, 0.0001); 9 | f3 = hslider("f3", 87, 30, 330, 0.0001); 10 | pulseAmt = hslider("pulse", 0, 0, 1, 0.0001); 11 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 12 | 13 | // Convert frequency to delay line length in samples 14 | freq2len(f) = 1.0 / f : ba.sec2samp; 15 | 16 | // Exciter: filtered oscillators 17 | noise = no.noise * 0.3 * noiseAmt : fi.lowpass(2, lp1); 18 | pulse = os.sawtooth(f1) * 4 * pulseAmt : fi.lowpass(1, 500); 19 | no2 = no.sparse_noise(5) * 0.2 : fi.resonbp(15000+2000*os.osc(0.3), 5, 1); 20 | srcs = noise + pulse + no2; 21 | exc = srcs : fi.highpass(1, 300) : fi.bandstop(1, 2500, 9000) : fi.lowpass(2, 11000); 22 | 23 | // Series of comb filters (feedback delay lines) 24 | loop(f) = + ~ (de.fdelay2(9000, freq2len(f)) : _*0.8); 25 | res = loop(f1 - 1 + 2*no.lfnoise0(1)) : loop(f2) : loop(f3); 26 | 27 | // Extreme compressor 28 | comp = *(5) : co.limiter_1176_R4_mono : *(0.35); 29 | 30 | process = exc : res : comp; -------------------------------------------------------------------------------- /MusicSource/patches/resonant-drone/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Resonant drone. 3 | * 4 | * The "resodrone" algorithm is very simple and consists of noise run through a 5 | * small number of high-feedback comb filters in series. The resulting timbre is 6 | * complex, emphasizing harmonics that are near-multiples of all comb filter 7 | * frequencies combined. 8 | * 9 | * Audio-rate code is written in Faust and can be found in the faust directory. 10 | * 11 | * This file contains envelopes, automation, and other control code. 12 | */ 13 | 14 | import { Graph, Seq, FaustNode, SampleProcessor, DurSmooth } from '../../lib/tealib.js'; 15 | 16 | export const sampleRate = 44100; 17 | const graph = new Graph({ sampleRate }); 18 | 19 | // Create some nodes: Faust instrument and post-processing 20 | const fau = new FaustNode('faust/reso.dsp', { lp1: 1, f1: 1, f2: 1, f3: 1, pulse: 1, noise: 1 }); 21 | const post = new FaustNode('faust/post.dsp', { preamp: 1, lp1: 1 }); 22 | fau.connect(post).connect(graph.out); 23 | 24 | // Basic parameters to control the patch: amp, frequency inertia, initial impact 25 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 26 | const inertia = graph.addParam('inertia', { max: 60 }); 27 | const impact = graph.addParam('impact'); 28 | // Params: frequency offset, lowpass, individual comb frequencies 29 | const ofs = graph.addParam('ofs', { def: 0.09 }); 30 | const mulOfs = mul => new SampleProcessor(x => x * (1 + ofs.value * mul * 0.1)); 31 | graph.addParam('lp1', { def: 15000, min: 100, max: 15000 }).connect(post.lp1); 32 | graph.addParam('freq1', { def: '100*1' }).connect(new DurSmooth(inertia)).connect(fau.f1); 33 | graph.addParam('freq2', { def: '100*7/4' }).connect(new DurSmooth(inertia)).connect(mulOfs(-1)).connect(fau.f2); 34 | graph.addParam('freq3', { def: '100*1/2' }).connect(new DurSmooth(inertia)).connect(mulOfs(1)).connect(fau.f3); 35 | 36 | // Initial control rate: add impact according to the impact parameter 37 | graph.ctrlDuration(1, t => { 38 | fau.pulse.value = Math.max(0, 1 / (t + 1) - 1 / 1.51) * impact.value; 39 | }); 40 | 41 | // Long-running control rate: modulate the filter 42 | const lpCurve = (t) => 100 + (350 + 150 * Math.cos(t * 0.25)) * (1 - 1 / (t * t * (0.07 - impact.value * 0.03) + 1)); 43 | graph.ctrl(tSec => { 44 | fau.lp1.value = lpCurve(tSec); 45 | }); 46 | 47 | export const process = graph.makeProcessor(); 48 | -------------------------------------------------------------------------------- /MusicSource/patches/sine-drone/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | 6 | rev_st = re.zita_rev1_stereo(0, 600, 7000, 10, 10, 44100); 7 | 8 | process = _*preamp <: _*0.1, _*0.1, rev_st :> _, _ : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/sine-drone/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sine drone. 3 | * 4 | * A simple additive synthesizer. At its core is a bank of sine waves, tuned to 5 | * be multiples of both input frequencies. 6 | * 7 | * Post-processing code is written in Faust and can be found in faust/post.dsp 8 | */ 9 | 10 | import { Graph, FaustNode, Sine } from '../../lib/tealib.js'; 11 | import { mixFreqs } from '../../lib/math.js'; 12 | 13 | export const sampleRate = 44100; 14 | const graph = new Graph({ sampleRate }); 15 | 16 | // Post-processing Faust node 17 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 18 | 19 | // Parameters to control the patch 20 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 21 | // Frequency centers for the generative chord 22 | const fParam1 = graph.addParam('freq1', { def: '100*4' }); 23 | const fParam2 = graph.addParam('freq2', { def: '100*6' }); 24 | 25 | post.connect(graph.out); 26 | const baseAmp = (freq, i) => 1 / (i + 1); 27 | const sines = [...new Array(10)].map(i => new Sine()); 28 | sines.forEach(s => s.connect(post)); 29 | 30 | // Generate a set of pitches for the chord 31 | let setFreqs = () => { 32 | const mfs = mixFreqs(fParam1.value, fParam2.value, 3); 33 | if (mfs.length < 10) throw new Error("fracsin can't intersect freqs"); 34 | sines.forEach((sine, i) => { 35 | sine.baseAmp = baseAmp(mfs[i], i) * 0.3; 36 | sine.baseFreq = mfs[i]; 37 | sine.lfRate = 1 / (2 + ((i * 79.6789) % 3)); 38 | sine.lfPhase = i; 39 | }); 40 | }; 41 | 42 | // At control rate, modulate components of the chord 43 | graph.ctrl(tSec => { 44 | if (fParam1.changed() || fParam2.changed()) setFreqs(); 45 | const env = 1 - 1 / (tSec * tSec * 0.15 + 1); 46 | for (let sine of sines) { 47 | sine.amp.value = sine.baseAmp * env * (0.5 + 0.5 * Math.sin(tSec * sine.lfRate + sine.lfPhase)); 48 | sine.freq.value = sine.baseFreq * (1 + 0.018 * Math.sin(tSec * sine.lfRate * 10)); 49 | } 50 | }); 51 | 52 | export const process = graph.makeProcessor(); 53 | -------------------------------------------------------------------------------- /MusicSource/patches/soprano-drone/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processing in Faust: limiter, flanger, phaser, reverb, delay 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | 6 | timbre = fi.highpass(1, 3000) : fi.high_shelf(8, 2500) : fi.notchw(150, 720); 7 | limiter = *(10) : co.limiter_1176_R4_mono; 8 | fl = _ <: _*0.9, de.fdelay2(200, 45+30*os.osc(0.7))*0.2 :> _; 9 | post1 = timbre : limiter : fl; 10 | 11 | phaser = pf.phaser2_mono(4, 0, 1000, 400, 1.5, 1500, 0.7, 1.5, 0.6, 1); 12 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 7, 44100); 13 | del = + ~ @(1.13 : ba.sec2samp) * 0.8; 14 | 15 | process = post1*preamp : phaser : del <: rev_st; 16 | -------------------------------------------------------------------------------- /MusicSource/patches/soprano-drone/faust/soprano.dsp: -------------------------------------------------------------------------------- 1 | // Soprano vocal synth in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | f1 = hslider("f1", 200, 30, 1000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | sawAmt = hslider("saw", 0, 0, 1, 0.0001); 9 | 10 | // freqs, Qs and amps for soprano formants 11 | // http://www.csounds.com/manual/html/MiscFormants.html 12 | sop_a_freq = (800, 1150, 2900, 3900, 4950); 13 | sop_e_freq = (350, 2000, 2800, 3600, 4950); 14 | sop_i_freq = (270, 2140, 2950, 3900, 4950); 15 | sop_o_freq = (450, 800, 2830, 3800, 4950); 16 | sop_u_freq = (325, 700, 2700, 3800, 4950); 17 | sop_a_amp = (1, 0.501, 0.025, 0.1, 0.003); 18 | sop_e_amp = (1, 0.1, 0.178, 0.01, 0.002); 19 | sop_i_amp = (1, 0.251, 0.05, 0.05, 0.006); 20 | sop_o_amp = (1, 0.282, 0.079, 0.079, 0.003); 21 | sop_u_amp = (1, 0.158, 0.018, 0.01, 0.001); 22 | sop_a_q = (10, 13, 24, 30, 35); 23 | sop_e_q = (6, 20, 23, 24, 25); 24 | sop_i_q = (5, 24, 30, 33, 41); 25 | sop_o_q = (11, 10, 28, 32, 41); 26 | sop_u_q = (7, 12, 16, 21, 25); 27 | 28 | // Exciter: filtered oscillators 29 | saw = os.sawtooth(f1) * sawAmt * 0.4 : fi.lowpass(1, f1*1.5) : fi.lowpass(1, 500+f1); 30 | noise = no.noise * 0.01 * noiseAmt : fi.ffbcombfilter(1/50 : ba.sec2samp, 1/f1 : ba.sec2samp, 0.9) : fi.lowpass(2, 600 + f1*1.5); 31 | exc = saw*0.8 + noise; 32 | 33 | // Body: formant bank 34 | vowel = os.osc(0.3)*0.12+0.18;//0.3;//os.osc(4)*0.7+0.71; 35 | linterp(lst1, lst2, i) = ba.take(i+1, lst1), ba.take(i+1, lst2) : si.interpolate(vowel); 36 | form(i) = fi.resonbp(f, q, a) with { 37 | f = linterp(sop_a_freq, sop_o_freq, i); 38 | q = linterp(sop_a_q, sop_o_q, i); 39 | a = linterp(sop_a_amp, sop_o_amp, i); 40 | }; 41 | 42 | // Per-voice post-processing with compression and flanger 43 | hform = fi.resonbp(5500, 10, 0.2); 44 | anti = fi.notchw(300, 2000) : fi.notchw(600, 3500); 45 | forms = _ <: par(i, 5, form(i)), hform :> anti; 46 | process = exc : forms; 47 | -------------------------------------------------------------------------------- /MusicSource/patches/soprano-drone/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Soprano drone based on vocal synthesis. This patch is similar to "sine drone" 3 | * but using a more advanced synth. 4 | * 5 | * Audio-rate code is written in Faust and can be found in the faust directory. 6 | * 7 | * This file contains sequencing, envelopes, and other control code. 8 | */ 9 | 10 | import { Graph, FaustNode, CtrlSine, SampleProcessor, DurSmooth, Gain } from '../../lib/tealib.js'; 11 | import { mixFreqs, randomSeed } from '../../lib/math.js'; 12 | 13 | export const sampleRate = 44100; 14 | const graph = new Graph({ sampleRate }); 15 | 16 | // Post-processing Faust node 17 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 18 | 19 | // Parameters to control the patch 20 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 21 | // Frequency centers for the generative chord 22 | const fParam1 = graph.addParam('freq1', { def: '100*4' }); 23 | const fParam2 = graph.addParam('freq2', { def: '100*6' }); 24 | // Timbral parameters 25 | const slowness = graph.addParam('slowness'); 26 | const inertia = graph.addParam('inertia', { max: 60 }); 27 | const hiCut = graph.addParam('hicut', {max: 9, def: 9}); 28 | const smoothCut = new DurSmooth(inertia); 29 | 30 | // Hack: ensure the smoothing node gets processed automatically 31 | hiCut.connect(smoothCut).connect(new Gain(0)).connect(graph.out); 32 | post.connect(graph.out); 33 | const rand = randomSeed(); 34 | 35 | /** 36 | * Class for a vocal synth Faust node with additional control-rate processing. 37 | * Control it by setting the "fTgt" and "pressTgt" properties to the desired 38 | * frequency and pressure level. Call "ctrl" at control rate and get the output 39 | * from the Faust node. 40 | */ 41 | export class Soprano extends FaustNode { 42 | constructor() { 43 | super('faust/soprano.dsp', { 44 | f1: 0, noise: 0, saw: 0, 45 | // highness: 0 46 | }); 47 | this.freq = 100; 48 | this.fTgt = 100; 49 | this.fChange = 100; 50 | this.press = 0; 51 | this.pressTgt = 0; 52 | this.vibNode = new CtrlSine({ phase: rand() }); 53 | this.vibNode 54 | .connect(new SampleProcessor(v => (1 + v * 0.02) * this.freq)) 55 | .connect(this.f1); 56 | } 57 | ctrl(t) { 58 | // slowness 0 -> df 0.03 ; slowness 1 -> df 0.005 59 | const fDif = (this.fTgt - this.freq) * (0.03-0.025*slowness.value); 60 | const fAbs = Math.abs(fDif); 61 | 62 | if (fAbs > this.fChange) this.fChange += (fAbs - this.fChange) * 0.1; 63 | else this.fChange += (fAbs - this.fChange) * 0.007; 64 | if (this.fChange > 1) this.fChange = 1; 65 | 66 | if (this.pressTgt > this.press) this.press += (this.pressTgt - this.press) * 0.02; 67 | else this.press += (this.pressTgt - this.press) * 0.002; 68 | 69 | this.freq += fDif; 70 | this.vibNode.freq.value = 3 + Math.sqrt(this.fChange) * 4; 71 | this.vibNode.amp.value = 1 - this.fChange; 72 | this.noise.value = this.fChange * 0.2 + 0.2; 73 | this.noise.value *= this.press; 74 | this.saw.value = (1 - this.fChange) * 0.2 + 0.15; 75 | this.saw.value *= this.press * this.press; 76 | // this.highness.value = 1-30/(t*t+30); 77 | } 78 | init(freq) { 79 | this.freq = freq 80 | this.fTgt = freq 81 | this.fChange = 1; 82 | } 83 | } 84 | 85 | const sops = [...new Array(10)].map(i => new Soprano()); 86 | sops.forEach(s => s.connect(post)); 87 | 88 | // Generate a set of pitches for the chord 89 | const baseAmp = (freq, i) => 0.6 + 0.4 / (i + 1); 90 | let setFreqs = () => { 91 | // hacky: make sure the initial call sets the right values 92 | smoothCut.process([hiCut.value]); 93 | const mfs = mixFreqs(fParam1.value, fParam2.value, 3); 94 | if (mfs.length < 10) throw new Error("fracsin can't intersect freqs"); 95 | sops.forEach((sop, i) => { 96 | if (!sop.pressTgt) sop.init(mfs[i] * 0.75); 97 | sop.fTgt = mfs[i]; 98 | sop.baseAmp = baseAmp(mfs[i], i); 99 | sop.baseFreq = mfs[i]; 100 | sop.lfRate = 1 / (2 + ((i * 79.6789) % 3)); 101 | sop.lfPhase = i; 102 | }); 103 | sops.sort((a, b) => a.fTgt - b.fTgt); 104 | sops.forEach((sop, i) => { sop.i = i; }); 105 | }; 106 | 107 | // At control rate, modulate individual voices 108 | graph.ctrl(t => { 109 | if (fParam1.changed() || fParam2.changed()) setFreqs(); 110 | const env = 1 - 1 / (t * t * 0.15 + 1); 111 | for (const sop of sops) { 112 | sop.pressTgt = sop.baseAmp * env * (0.5 + 0.5 * Math.sin(t * sop.lfRate + sop.lfPhase)); 113 | sop.fTgt = sop.baseFreq * (1 + 0.018 * Math.sin(t * sop.lfRate * 10)); 114 | if (sop.i > smoothCut.value) { 115 | const amt = 1 - Math.min(1, sop.i - smoothCut.value); 116 | sop.pressTgt *= amt; 117 | sop.fTgt = 0.7 + amt*0.3; 118 | } 119 | sop.ctrl(t); 120 | } 121 | }); 122 | 123 | export const process = graph.makeProcessor(); 124 | -------------------------------------------------------------------------------- /MusicSource/patches/sparse-bottle/faust/bottle.dsp: -------------------------------------------------------------------------------- 1 | // Bottle instrument in Faust using modal synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 5, 0.0001); 8 | lp1 = hslider("lp1", 6000, 100, 10000, 0.0001); 9 | resMul = hslider("resmul", 1, 0, 1, 0.0001); 10 | modes = hslider("modes", 4, 0, 4, 0.0001); 11 | 12 | // Exciter: a filtered oscillator with tremolo 13 | exc = no.noise * noiseAmt * 0.007 : fi.lowpass(1, lp1) : fi.lowpass(1, 4000); 14 | 15 | // Modal body 16 | barCore = exc <: 17 | pm.modeFilter(freq, 2*resMul, 0.16*(modes > 3)), 18 | pm.modeFilter(freq*1.47*2, 0.8*resMul, 0.11*(modes > 2)), 19 | pm.modeFilter(freq*2.09*2, 0.3*resMul, 0.1*(modes > 1)), 20 | pm.modeFilter(freq*2.56*3, 0.1*resMul, 0.07), 21 | 22 | pm.modeFilter(2*freq*2.09*2, 0.3*resMul, 0.02*(modes > 1)), 23 | pm.modeFilter(2*freq*2.56*3, 0.1*resMul, 0.01), 24 | 25 | pm.modeFilter(4*freq*2.09*2, 0.3*resMul, 0.05*(modes > 1)), 26 | pm.modeFilter(4*freq*2.56*3, 0.1*resMul, 0.03) 27 | :> _; 28 | 29 | process = barCore; -------------------------------------------------------------------------------- /MusicSource/patches/sparse-bottle/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add reverb, delay and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | 6 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 10, 44100); 7 | del(t) = + ~ @(t : ba.sec2samp) * 0.7; 8 | 9 | process = _*preamp <: del(0.3), del(0.5) <: rev_st : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/sparse-bottle/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sparse bottle. This is a classic waveguide model for a closed-ended wind 3 | * instrument. This patch just plays the two notes that are given as parameters. 4 | * 5 | * Audio-rate code is written in faust and can be found in the faust directory. 6 | * 7 | * This file contains sequencing, envelopes, and other control code. 8 | */ 9 | 10 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 11 | 12 | export const sampleRate = 44100; 13 | const graph = new Graph({ sampleRate }); 14 | 15 | // Post-processing Faust node 16 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 17 | 18 | // Parameters to control the patch 19 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 20 | // Frequencies to play 21 | const fParam1 = graph.addParam('freq1', { def: '100*2' }); 22 | const fParam2 = graph.addParam('freq2', { def: '100*3' }); 23 | // Timbral parameters: breathiness and number of modes 24 | const breath = graph.addParam('breath'); 25 | const modes = graph.addParam('modes', { def: 4, min: 0, max: 4 }); 26 | 27 | // The bottle instrument is polyphonic. This creates one polyphonic voice 28 | const mkVoice = i => { 29 | const ret = new FaustNode('faust/bottle.dsp', { freq: 500, noise: 0, resmul: 1, modes: 4 }); 30 | ret.notePos = 100; 31 | ret.amp = 1; 32 | ret.note = (freq, amp) => { 33 | ret.notePos = 0; 34 | ret.amp = amp * (1+breath.value*4) * (5/(modes.value+1)); 35 | ret.freq.value = freq; 36 | ret.modes.value = modes.value; 37 | ret.resmul.value = 1/Math.pow(10, breath.value*2); 38 | }; 39 | return ret; 40 | }; 41 | 42 | // Use the Poly class to make an instrument with 3 managed voices 43 | const poly = new Poly(3, mkVoice, post); 44 | 45 | // At control rate, apply an envelope to all voices 46 | const pulse = (k, x) => Math.max(0, (2 * Math.sqrt(k) * x) / (1 + k * x * x) - x * 0.01); 47 | graph.ctrl(tSeconds => { 48 | poly.forEach(voice => { 49 | voice.notePos += 0.002; 50 | voice.noise.value = pulse(5, voice.notePos) * voice.amp; 51 | }); 52 | }); 53 | 54 | // Play the specified notes in a cycle 55 | let cycle = 0; 56 | post.connect(graph.out); 57 | const seq = new Seq(graph); 58 | seq.schedule(async () => { 59 | poly.note(fParam1.value, 0.4); 60 | await seq.play(3); 61 | poly.note(fParam2.value, 0.5); 62 | await seq.play(4); 63 | while (true) { 64 | poly.note(fParam1.value, 0.5); 65 | await seq.play(2); 66 | await seq.play((cycle * 0.382) % 1); 67 | if (cycle % 2) poly.note(fParam2.value, 0.7); 68 | await seq.play(4); 69 | await seq.play((cycle * 0.382 * 2 + 1) % 2); 70 | cycle++; 71 | } 72 | }); 73 | 74 | export const process = graph.makeProcessor(); 75 | -------------------------------------------------------------------------------- /MusicSource/patches/sparse-soprano/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add a phaser, delay, and reverb 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | 6 | phaser = pf.phaser2_mono(4, 0, 1000, 400, 1.5, 1500, 0.7, 1.5, 0.6, 1); 7 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 7, 44100); 8 | del = + ~ @(1.13 : ba.sec2samp) * 0.8; 9 | 10 | process = phaser*preamp : del <: rev_st; -------------------------------------------------------------------------------- /MusicSource/patches/sparse-soprano/faust/soprano.dsp: -------------------------------------------------------------------------------- 1 | // Soprano vocal synth in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | f1 = hslider("f1", 200, 30, 1000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | sawAmt = hslider("saw", 0, 0, 1, 0.0001); 9 | 10 | // freqs, Qs and amps for soprano formants 11 | // http://www.csounds.com/manual/html/MiscFormants.html 12 | sop_a_freq = (800, 1150, 2900, 3900, 4950); 13 | sop_e_freq = (350, 2000, 2800, 3600, 4950); 14 | sop_i_freq = (270, 2140, 2950, 3900, 4950); 15 | sop_o_freq = (450, 800, 2830, 3800, 4950); 16 | sop_u_freq = (325, 700, 2700, 3800, 4950); 17 | sop_a_amp = (1, 0.501, 0.025, 0.1, 0.003); 18 | sop_e_amp = (1, 0.1, 0.178, 0.01, 0.002); 19 | sop_i_amp = (1, 0.251, 0.05, 0.05, 0.006); 20 | sop_o_amp = (1, 0.282, 0.079, 0.079, 0.003); 21 | sop_u_amp = (1, 0.158, 0.018, 0.01, 0.001); 22 | sop_a_q = (10, 13, 24, 30, 35); 23 | sop_e_q = (6, 20, 23, 24, 25); 24 | sop_i_q = (5, 24, 30, 33, 41); 25 | sop_o_q = (11, 10, 28, 32, 41); 26 | sop_u_q = (7, 12, 16, 21, 25); 27 | 28 | // Exciter: filtered oscillators 29 | saw = os.sawtooth(f1) * sawAmt * 0.4 : fi.lowpass(1, f1*1.5) : fi.lowpass(1, 500+f1); 30 | noise = no.noise * 0.01 * noiseAmt : fi.ffbcombfilter(1/50 : ba.sec2samp, 1/f1 : ba.sec2samp, 0.9) : fi.lowpass(2, 600 + f1*1.5); 31 | exc = saw*0.8 + noise; 32 | 33 | // Body: formant bank 34 | vowel = os.osc(0.3)*0.12+0.18;//0.3;//os.osc(4)*0.7+0.71; 35 | linterp(lst1, lst2, i) = ba.take(i+1, lst1), ba.take(i+1, lst2) : si.interpolate(vowel); 36 | form(i) = fi.resonbp(f, q, a) with { 37 | f = linterp(sop_a_freq, sop_o_freq, i); 38 | q = linterp(sop_a_q, sop_o_q, i); 39 | a = linterp(sop_a_amp, sop_o_amp, i); 40 | }; 41 | hform = fi.resonbp(5500, 10, 0.2); 42 | anti = fi.notchw(300, 2000) : fi.notchw(600, 3500); 43 | forms = _ <: par(i, 5, form(i)), hform :> anti; 44 | syn = exc : forms; 45 | 46 | // Per-voice post-processing with compression and flanger 47 | timbre = fi.highpass(1, 3000) : fi.high_shelf(8, 2500) : fi.notchw(150, 720); 48 | limiter = *(10) : co.limiter_1176_R4_mono; 49 | fl = _ <: _*0.9, de.fdelay2(200, 45+30*os.osc(0.7))*0.2 :> _; 50 | post = timbre : limiter : fl; 51 | 52 | process = syn : post; 53 | -------------------------------------------------------------------------------- /MusicSource/patches/sparse-soprano/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sparse soprano. This is a simple vocal synth, starting with a sawtooth wave 3 | * combined with noise, which are then fed into a formant filter bank. This 4 | * patch just plays the note that's given as parameter. 5 | * 6 | * Audio-rate code is written in Faust and can be found in the faust directory. 7 | * 8 | * This file contains sequencing, envelopes, and other control code. 9 | */ 10 | 11 | import { Graph, Seq, FaustNode } from '../../lib/tealib.js'; 12 | 13 | export const sampleRate = 44100; 14 | const graph = new Graph({ sampleRate }); 15 | 16 | // Create some nodes: Faust instrument and post-processing 17 | const fau = new FaustNode('faust/soprano.dsp', { f1: 0, noise: 1, saw: 0.4 }); 18 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 19 | 20 | // Basic parameters to control the patch: amp, frequency 21 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 22 | const f1param = graph.addParam('freq1', { def: '100*3' }); 23 | fau.connectWithGain(post).connect(graph.out); 24 | 25 | // At control rate, add some movement to the voice 26 | graph.ctrl(tSec => { 27 | const vibrato = Math.sin(tSec * 17 + 4 * Math.sin(tSec * 2)); 28 | const vibAmt = Math.cos(tSec * 3) * 0.3 + 0.6; 29 | graph.getConnection(fau, post).gain.value = 0.5 - 0.5 * Math.cos(tSec * 0.5); 30 | fau.f1.value = f1param.value * (1 + vibrato * vibAmt * 0.02); 31 | }); 32 | 33 | export const process = graph.makeProcessor(); 34 | -------------------------------------------------------------------------------- /MusicSource/patches/vibraphones/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: resonant body, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 4, 0.0001); 5 | 6 | barBod = _ <: _*0.2, fi.allpass_comb(1024, 877, -0.96) :> _; 7 | 8 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 9 | 10 | bandComp = _*2 : co.limiter_1176_R4_mono : _*0.4; 11 | comp = _ : fi.filterbank(3,(600)) : _, bandComp : +; 12 | 13 | process = _*2*preamp : comp <: _*0.5, _*0.5, rev_st :> _, _; -------------------------------------------------------------------------------- /MusicSource/patches/vibraphones/faust/vib.dsp: -------------------------------------------------------------------------------- 1 | // Vibraphone instrument in Faust using modal synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | lp1 = hslider("lp1", 400, 100, 10000, 0.0001); 9 | 10 | // Exciter: a filtered oscillator 11 | exc = no.noise * noiseAmt * 0.05 : fi.lowpass(1, lp1) : fi.lowpass(2, 3000); 12 | 13 | // Modal body 14 | barCore = exc <: 15 | pm.modeFilter(freq, 2, 0.1), 16 | pm.modeFilter(freq*3.984, 0.8, 0.2), 17 | pm.modeFilter(freq*10.668, 0.3, 0.06) 18 | :> _; 19 | 20 | process = barCore; -------------------------------------------------------------------------------- /MusicSource/patches/vibraphones/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A vibraphone model based on modal synthesis. Audio-rate code is written in 3 | * Faust and can be found in the faust directory. This file contains sequencing, 4 | * envelopes, and other control code. It just plays the two notes that are given 5 | * as parameters. 6 | */ 7 | 8 | import { Graph, FaustNode, Seq } from '../../lib/tealib.js'; 9 | 10 | export const sampleRate = 44100; 11 | const graph = new Graph({ sampleRate }); 12 | 13 | // Create some nodes: Faust instrument and post-processing 14 | const fau = new FaustNode('faust/vib.dsp', { freq: 1, noise: 1 }); 15 | const fau2 = new FaustNode('faust/vib.dsp', { freq: 1, noise: 1 }); 16 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 17 | fau.connect(post); 18 | fau2.connect(post); 19 | post.connect(graph.out); 20 | 21 | // Basic parameters to control the patch: amp, initial impact, frequencies to play 22 | graph.addParam('preamp', { def: 1, max: 4 }).connect(post.preamp); 23 | const impact = graph.addParam('impact'); 24 | graph.addParam('freq1', { def: '100*3' }).connect(fau.freq); 25 | graph.addParam('freq2', { def: '100*7/2' }).connect(fau2.freq); 26 | 27 | // Initial control rate: add impact according to the impact parameter 28 | const env = x => 1 - 1 / (x * (0.5 - 0.3 * impact.value) + 1); 29 | new Seq(graph).schedule(() => { 30 | fau.noise.value = impact.value; 31 | fau2.noise.value = fau.noise.value; 32 | }); 33 | 34 | // Long-running control rate: play the notes with random timing 35 | graph.ctrl(tSec => { 36 | fau.noise.value *= 0.7; 37 | fau2.noise.value *= 0.7; 38 | if (Math.random() < 0.01) { 39 | fau.noise.value = Math.max(fau.noise.value, Math.random() * env(tSec)); 40 | } 41 | if (Math.random() < 0.01) { 42 | fau2.noise.value = Math.max(fau2.noise.value, Math.random() * env(tSec)); 43 | } 44 | }); 45 | 46 | export const process = graph.makeProcessor(); 47 | -------------------------------------------------------------------------------- /MusicSource/patches/vocal-overtones/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add a delay, and reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | 6 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 12, 6, 44100); 7 | 8 | delayz = fi.highpass(1, 1500) <: @(0.28 : ba.sec2samp), @(1 : ba.sec2samp); 9 | mix(dry, rev1, rev2, del1, del2) = dry+rev1+del1+del2*0.3, dry+rev2+del2+del1*0.3; 10 | process = _*preamp <: _*0.2, rev_st, delayz : mix : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/vocal-overtones/faust/tuvan.dsp: -------------------------------------------------------------------------------- 1 | // Overtone singing vocal synth in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | f1 = hslider("f1", 228, 30, 400, 0.001); 7 | f2amtbase = hslider("f2amt", 0, 0, 0.5, 0.0001); 8 | 9 | // freqs, Qs and amps for "tenor a/e" formants 10 | // http://www.csounds.com/manual/html/MiscFormants.html 11 | fs1 = (650, 1080, 2650, 2900, 3250); 12 | fs2 = (400, 1700, 2600, 3200, 3580); 13 | qs1 = (8, 12, 22, 22, 23); 14 | qs2 = (6, 21, 26, 27, 30); 15 | as1 = (1, 0.5, 0.45, 0.4, 0.08); 16 | as2 = (1, 0.12, 0.25, 0.12, 0.08); 17 | 18 | // freqs, Qs and amps for tuned filters 19 | tfs = (8, 16, 32, 64); 20 | tqs = (4, 5, 8, 5); 21 | tas = (0.3, 0.4, 0.25, 0.1); 22 | 23 | // automation 24 | atk1 = ba.time != 0 : en.are(12, 12); 25 | atk2 = ba.time != 0 : en.are(2, 2); 26 | press = 0.6; 27 | f2 = f1*6 + f1*4*no.lfnoise(0.2); 28 | f2amt = f2amtbase * (1 + no.lfnoise(1)*0.2); 29 | ampatk = press : si.lag_ud(0.1, 0.01); 30 | vowel = 0.25 + f2amt*0.5 + 0.25*no.lfnoise(0.6); 31 | f1vib = f1 * (1 + atk1*0.01*os.osc(5+os.osc(0.5))) * (0.5 + 0.5*atk2); 32 | 33 | // Exciter: filtered oscillators 34 | saw = os.sawtooth(f1vib)*0.5; 35 | noise = no.noise * (0.2 - 0.2*f2amt); 36 | exc = (saw + noise) * 0.1; 37 | 38 | // Body: formants, tuned filters, controlled harmonic singing bands 39 | linterp(lst1, lst2, i) = ba.take(i+1, lst1), ba.take(i+1, lst2) : si.interpolate(vowel); 40 | form(i) = fi.resonbp(linterp(fs1, fs2, i), linterp(qs1, qs2, i), linterp(as1, as2, i)); 41 | forms(amp) = par(i, 5, form(i)*amp); 42 | ctrlforms(amt) = * (0.5*amt) : fi.lowpass(1, 15000) <: 43 | (fi.resonbp(f2, 1, 1) : fi.resonbp(f2, 1, 1)), 44 | (fi.resonbp(f2*4, 2, 1) : fi.resonbp(f2*4, 2, 1))*0.4, 45 | fi.highpass(1, 1600); 46 | 47 | tuner(i) = fi.resonlp(f1*ba.take(i+1, tfs), ba.take(i+1, tqs), 1)*ba.take(i+1, tas); 48 | tuners(amp) = par(i, 4, tuner(i)*amp); 49 | ctrlend(amp) = _ <: fi.resonbp(f2, 9, 1)*amp, fi.resonbp(f2*2, 6, 1)*amp, 50 | fi.highpass(1, 1600) :> _; 51 | 52 | om = fi.lowpass(3, 15000*atk1*atk1 + 50); 53 | 54 | body = _ 55 | <: forms(1 - f2amt*0.8), ctrlforms(f2amt) :> _ 56 | <: *(0.2), tuners(1 - f2amt*0.5) :> _ 57 | <: *(1 - f2amt*0.5), ctrlend(f2amt) :> om; 58 | 59 | process = exc : body : *(ampatk); 60 | -------------------------------------------------------------------------------- /MusicSource/patches/vocal-overtones/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vocal overtones. 3 | * 4 | * This starts off with classic vocal synthesis: an exciter (sawtooth and noise) 5 | * is fed into a formant filter bank. Additional sweeping "overtone" filters are 6 | * added in parallel to the original bank, and fixed "tonic" filters are added 7 | * on the combined output to keep it tuned. 8 | * 9 | * Audio-rate code is written in Faust and can be found in the faust directory. 10 | * 11 | * This file contains envelopes, automation, and other control code. 12 | */ 13 | 14 | import { Graph, Seq, FaustNode } from '../../lib/tealib.js'; 15 | 16 | export const sampleRate = 44100; 17 | const graph = new Graph({ sampleRate }); 18 | 19 | // Create some nodes: Faust instrument and post-processing 20 | const fau = new FaustNode('faust/tuvan.dsp', { f1: 0, f2amt: 0 }); 21 | const fau2 = new FaustNode('faust/tuvan.dsp', { f1: 0, f2amt: 0.1 }); 22 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 23 | fau.connectWithGain(post).connect(graph.out); 24 | fau2.connectWithGain(post).connect(graph.out); 25 | 26 | // Parameters to control the patch: amp, number of voices 27 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 28 | const num = graph.addParam('num', { def: 1, min: 1, max: 2 }); 29 | // Fundamental frequencies 30 | graph.addParam('freq1', { def: '100*4/5' }).connect(fau.f1); 31 | graph.addParam('freq2', { def: '100' }).connect(fau2.f1); 32 | 33 | // At control rate, add movement to the voices 34 | graph.ctrl(tSec => { 35 | if (num.value > 1.5) { 36 | fau.f2amt.value = 0.2; 37 | fau2.muted = false; 38 | graph.getConnection(fau, post).gain.value = 0.8 - 0.2 * Math.cos(tSec * 0.5); 39 | graph.getConnection(fau2, post).gain.value = 0.75 + 0.25 * Math.cos(tSec * 0.5); 40 | } else { 41 | graph.getConnection(fau, post).gain.value = 1; 42 | fau.f2amt.value = 0.33; 43 | fau2.muted = true; 44 | } 45 | }); 46 | 47 | export const process = graph.makeProcessor(); 48 | -------------------------------------------------------------------------------- /MusicSource/patches/water-bell/faust/modal.dsp: -------------------------------------------------------------------------------- 1 | // Bell instrument in Faust using modal synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | lp1 = hslider("lp1", 400, 100, 10000, 0.0001); 8 | mode0 = hslider("mode0", 0, 0, 1, 0.0001); 9 | mode1 = hslider("mode1", 0, 0, 1, 0.0001); 10 | mode2 = hslider("mode2", 0, 0, 1, 0.0001); 11 | mode3 = hslider("mode3", 0, 0, 1, 0.0001); 12 | mode4 = hslider("mode4", 0, 0, 1, 0.0001); 13 | 14 | // Exciter: a filtered oscillator 15 | f2 = 1500 + 1000 * os.osc(0.05); 16 | exc = no.noise * 0.2 : fi.lowpass(1, lp1*3); 17 | 18 | // Some LFOs to add movement to the modes 19 | // (this creates interesting wailing sounds) 20 | lf = os.osc(0.7) * 0.5 + 0.5; 21 | lf2 = os.oscp(0.7, 3) * 0.5 + 0.5; 22 | lf3 = os.oscp(0.9, 0); 23 | lf4 = os.oscp(0.9, 3); 24 | 25 | // Modal body (bonang panerus modal analysis) 26 | barCore = exc <: 27 | (_ * mode0 : pm.modeFilter(freq*1.0, 39.92, 0.1) : _ * lf), 28 | (_ * mode0 : pm.modeFilter(freq*2.0*(1+0.01*lf3), 19.73, 0.12) : _ * lf2), 29 | (_ * mode1 : pm.modeFilter(freq*2.38*(1+0.03*lf4), 18.84, 0.05) : _ * lf), 30 | (_ * mode1 : pm.modeFilter(freq*3.01*(1+0.01*lf3), 15.4, 0.05) : _ * lf2), 31 | (_ * mode2 : pm.modeFilter(freq*3.66*(1+0.02*lf4), 5.34, 0.05) : _ * lf), 32 | (_ * mode2 : pm.modeFilter(freq*4.0*(1+0.01*lf3), 4.48, 0.1) : _ * lf2), 33 | (_ * mode3 : pm.modeFilter(freq*5.35*(1+0.04*lf3), 4.22, 0.02) : _ * lf), 34 | (_ * mode3 : pm.modeFilter(freq*5.96*(1+0.01*lf4), 4.39, 0.04) : _ * lf2), 35 | (_ * mode4 : pm.modeFilter(freq*8.16*(1+0.02*lf4), 4.6, 0.03) : _ * lf), 36 | (_ * mode4 : pm.modeFilter(freq*10.56*(1+0.01*lf3), 4.39, 0.03 : _ * lf)) 37 | :> _; 38 | 39 | process = barCore; -------------------------------------------------------------------------------- /MusicSource/patches/water-bell/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add a phaser, pitch shifter, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | 6 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 7 | phaser = pf.phaser2_mono(4, 0, 1000, 400, 1.5, 1500, 0.7, 1.5, 0.6, 1); 8 | 9 | raise(x) = x*0.8 + ef.transpose(0.5: ba.sec2samp, 0.2 : ba.sec2samp, 12, x)*0.3 :> _; 10 | 11 | process = _*preamp : raise : fi.bandstop(1, 450, 1500) : phaser <: _*0.3, _*0.3, rev_st :> co.limiter_1176_R4_stereo; 12 | -------------------------------------------------------------------------------- /MusicSource/patches/water-bell/main.js: -------------------------------------------------------------------------------- 1 | import { Graph, FaustNode, Seq, LinSmooth } from '../../lib/tealib.js'; 2 | 3 | export const sampleRate = 44100; 4 | const graph = new Graph({ sampleRate }); 5 | 6 | 7 | // Create some nodes: Faust instrument and post-processing 8 | const a0 = 0; 9 | const fau = new FaustNode('faust/modal.dsp', { freq: 1, mode0: a0, mode1: a0, mode2: a0, mode3: a0, mode4: a0 }); 10 | const post = new FaustNode('faust/post.dsp', { preamp: 1 }); 11 | fau.connect(post); 12 | post.connect(graph.out); 13 | 14 | // Parameters to control the patch: amp, fundamental frequency 15 | graph.addParam('preamp', { def: 1 }).connect(new LinSmooth(0.00001)).connect(post.preamp); 16 | graph.addParam('freq1', { def: '100*4' }).connect(fau.freq); 17 | // Performance parameters: strum the individual modes 18 | const interval = graph.addParam('interval', {min: -1}); 19 | const iniStrum = graph.addParam('inistrum'); 20 | 21 | // Get the Faust parameters for individual modes as an array 22 | const modes = []; 23 | for (let i=0; i<5; i++) modes.push(fau['mode'+i]); 24 | 25 | // At control rate, add decay to each mode 26 | graph.ctrl(tSec => { 27 | modes.forEach((m,i) => { m.value *= 0.99; }); 28 | }); 29 | 30 | // Utility to strum the modes 31 | const seq = new Seq(graph); 32 | const strum = async ({interval, amp}) => { 33 | const dir = Math.sign(interval) || 1; 34 | interval = Math.abs(interval); 35 | let i = dir > 0 ? 0 : modes.length - 1; 36 | while (modes[i] !== undefined) { 37 | modes[i].value = dir > 0 ? amp*(i+1)/(modes.length) : amp; 38 | if (i < fau.freq.value/150) modes[i].value *= 0.3; 39 | await seq.play(interval); 40 | i += dir; 41 | } 42 | }; 43 | 44 | // At control rate, play the initial strum then randomly trigger modes 45 | seq.schedule(async () => { 46 | if (iniStrum.value) { 47 | /* await */ strum({interval: 0.5, amp: 0.4}); 48 | await seq.play(4); 49 | } 50 | /* await */ strum({interval: interval.value*0.1, amp: 1}); 51 | graph.ctrl(t => { 52 | modes.forEach((m,i) => { 53 | if (Math.random() < 0.001) { 54 | m.value = Math.max(m.value, Math.random()*0.3); 55 | if (i < fau.freq.value/150) m.value *= 0.3; 56 | } 57 | }); 58 | }); 59 | }); 60 | 61 | export const process = graph.makeProcessor(); 62 | -------------------------------------------------------------------------------- /MusicSource/patches/wind-bells/faust/chime.dsp: -------------------------------------------------------------------------------- 1 | // Bell instrument in Faust using modal synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | 9 | // Exciter: a filtered oscillator 10 | exc = no.pink_noise * noiseAmt * 0.5; 11 | 12 | // Modal body 13 | body = exc <: 14 | pm.modeFilter(freq*1.0, 1.2, 0.07), 15 | pm.modeFilter(freq*4.95, 1.2, 0.05), 16 | pm.modeFilter(freq*7.9, 1.2, 0.05), 17 | pm.modeFilter(freq*11.9, 1.2, 0.1), 18 | pm.modeFilter(freq*15.7, 1.2, 0.1), 19 | pm.modeFilter(freq*20, 1.2, 0.1) 20 | :> _; 21 | 22 | process = body; -------------------------------------------------------------------------------- /MusicSource/patches/wind-bells/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add a phaser, delay, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | lp1 = hslider("lp1", 400, 100, 10000, 0.0001); 6 | hp1 = hslider("hp1", 500, 50, 10000, 0.0001); 7 | phaser = pf.phaser2_mono(4, 0, 1000, 400, 1.5, 1500, 0.5, 1, 0, 0); 8 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 5, 3, 44100); 9 | filters = fi.lowpass(1, lp1)*preamp : fi.highpass(5, hp1); 10 | loop1 = + ~ (@(1.283 : ba.sec2samp) * -0.6); 11 | loop2 = + ~ (@(0.937 : ba.sec2samp) * -0.5); 12 | delmix(dry, del1, del2) = dry*0.6+del1, dry*0.12+del2; 13 | del = _ <: _, loop1, loop2 : delmix; 14 | mixer(rev1, rev2, del1, del2) = del1*0.2+rev1, del2*0.2+rev2; 15 | 16 | process = filters : phaser : del <: rev_st,_,_ : mixer : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/wind-bells/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wind chimes based on modal synthesis. Audio-rate code is written in Faust and 3 | * can be found in the faust directory. This file contains sequencing, 4 | * envelopes, and other control code. 5 | */ 6 | 7 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 8 | import { mixFreqs, randomSeed } from '../../lib/math.js'; 9 | 10 | export const sampleRate = 44100; 11 | const graph = new Graph({ sampleRate }); 12 | 13 | // Post-processing Faust node 14 | const post = new FaustNode('faust/post.dsp', { preamp: 1, lp1: 600, hp1: 500 }); 15 | post.connect(graph.out); 16 | 17 | // Parameters to control the patch 18 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 19 | // Frequency centers for the generated pitches, frequency tightness 20 | const fParam1 = graph.addParam('freq1', { def: '100*6' }); 21 | const fParam2 = graph.addParam('freq2', { def: '100*8' }); 22 | const tightParam = graph.addParam('tightness', { max: 6.5 }); 23 | // Intensity parameters: lowpass and note density 24 | const lpParam1 = graph.addParam('lp1', { def: 350, min: 50, max: 2000 }); 25 | const density = graph.addParam('density', { def: 1.7, min: 0.1, max: 3 }); 26 | lpParam1.connect(post.lp1); 27 | 28 | // The bell synth is polyphonic. This creates one polyphonic voice 29 | const mkVoice = i => { 30 | const ret = new FaustNode('faust/chime.dsp', { freq: 500, noise: 0 }); 31 | ret.notePos = 100; 32 | ret.amp = 1; 33 | ret.dir = 1; 34 | ret.note = (freq, amp, dir) => { 35 | ret.freq.value = freq; 36 | ret.notePos = dir > 0 ? 0 : 0.6; 37 | ret.amp = amp; 38 | ret.dir = dir; 39 | }; 40 | return ret; 41 | }; 42 | 43 | // Use the Poly class to make an instrument with 8 managed voices 44 | const poly = new Poly(8, mkVoice, post); 45 | 46 | // At control rate, apply an envelope to all voices 47 | const env = (x) => { 48 | return Math.max(0, (1.2)/((x)*10+1)-0.2); 49 | } 50 | graph.ctrl((tSeconds, delta) => { 51 | poly.forEach(voice => { 52 | voice.notePos += delta*voice.dir; 53 | if (voice.notePos < 0) { 54 | voice.notePos = 100; 55 | voice.dir = 1; 56 | } 57 | voice.noise.value = env(voice.notePos) * voice.amp; 58 | }); 59 | }); 60 | 61 | const seq = new Seq(graph); 62 | const idxpow = 1; 63 | let freqs; 64 | 65 | // Generate a set of pitches for the chiming 66 | const setFreqs = () => { 67 | freqs = mixFreqs(fParam1.value, fParam2.value, 2+Math.floor(tightParam.value)); 68 | freqs = freqs.slice(0,8-Math.floor(tightParam.value)); 69 | post.hp1.value = Math.min(...freqs)*0.5; 70 | }; 71 | 72 | // At control rate, trigger bells randomly 73 | seq.schedule(async () => { 74 | let t = 0; 75 | let rand = randomSeed(1); 76 | let lastIdx = 0; 77 | while (true) { 78 | if (fParam1.changed() || fParam2.changed() || tightParam.changed()) setFreqs(); 79 | if (t === 100) { 80 | t = 0; 81 | rand = randomSeed(1); 82 | } 83 | let idx = Math.pow(rand(), idxpow); 84 | if (idx === lastIdx) idx = Math.pow(rand(), idxpow); 85 | if (idx === lastIdx) idx = Math.pow(rand(), idxpow); 86 | lastIdx = idx; 87 | const f = freqs[Math.floor(idx*freqs.length)]; 88 | poly.note(f, t ? 0.6 + 0.4*rand() : 1, Math.sign(rand()-0.05)); 89 | await seq.play(0.1*rand() + 0.4/density.value); 90 | t++; 91 | } 92 | }); 93 | 94 | export const process = graph.makeProcessor(); 95 | -------------------------------------------------------------------------------- /MusicSource/patches/wind-chimes/faust/chime.dsp: -------------------------------------------------------------------------------- 1 | // Chime instrument in Faust using modal synthesis 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 500, 50, 2000, 0.001); 7 | noiseAmt = hslider("noise", 0, 0, 1, 0.0001); 8 | 9 | // Exciter: a filtered oscillator 10 | exc = no.pink_noise * noiseAmt * 0.5; 11 | 12 | // Modal body 13 | body = exc <: 14 | pm.modeFilter(freq, 1.2, 0.1), 15 | pm.modeFilter(freq*2.778, 1.2, 0.1), 16 | pm.modeFilter(freq*5.18, 1.2, 0.1), 17 | pm.modeFilter(freq*8.163, 1.2, 0.1), 18 | pm.modeFilter(freq*11.66, 1.2, 0.1), 19 | pm.modeFilter(freq*15.638, 1.2, 0.1), 20 | pm.modeFilter(freq*20, 1.2, 0.1) 21 | :> _; 22 | 23 | process = body; -------------------------------------------------------------------------------- /MusicSource/patches/wind-chimes/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust: add delay, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | lp1 = hslider("lp1", 400, 100, 10000, 0.0001); 6 | hp1 = hslider("hp1", 500, 50, 10000, 0.0001); 7 | 8 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 5, 3, 44100); 9 | filters = fi.lowpass(1, lp1)*preamp : fi.highpass(5, hp1); 10 | loop1 = + ~ (@(1.283 : ba.sec2samp) * -0.6); 11 | loop2 = + ~ (@(0.937 : ba.sec2samp) * -0.5); 12 | delmix(dry, del1, del2) = dry*0.6+del1, dry*0.12+del2; 13 | del = _ <: _, loop1, loop2 : delmix; 14 | mixer(rev1, rev2, del1, del2) = del1*0.2+rev1, del2*0.2+rev2; 15 | 16 | process = filters : del <: rev_st,_,_ : mixer : co.limiter_1176_R4_stereo; -------------------------------------------------------------------------------- /MusicSource/patches/wind-chimes/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wind chimes based on modal synthesis. Audio-rate code is written in Faust and 3 | * can be found in the faust directory. This file contains sequencing, 4 | * envelopes, and other control code. 5 | */ 6 | 7 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 8 | import { mixFreqs, randomSeed } from '../../lib/math.js'; 9 | 10 | export const sampleRate = 44100; 11 | const graph = new Graph({ sampleRate }); 12 | 13 | // Post-processing Faust node 14 | const post = new FaustNode('faust/post.dsp', { preamp: 1, lp1: 600, hp1: 500 }); 15 | post.connect(graph.out); 16 | 17 | // Parameters to control the patch 18 | graph.addParam('preamp', { def: 1 }).connect(post.preamp); 19 | // Frequency centers for the generated pitches, frequency tightness 20 | const fParam1 = graph.addParam('freq1', { def: '100*6' }); 21 | const fParam2 = graph.addParam('freq2', { def: '100*8' }); 22 | const tightParam = graph.addParam('tightness', { max: 6.5 }); 23 | // Intensity parameters: lowpass and note density 24 | const lpParam1 = graph.addParam('lp1', { def: 350, min: 50, max: 2000 }); 25 | const density = graph.addParam('density', { def: 1, min: 0.1, max: 3 }); 26 | lpParam1.connect(post.lp1); 27 | 28 | // The chime synth is polyphonic. This creates one polyphonic voice 29 | const mkVoice = i => { 30 | const ret = new FaustNode('faust/chime.dsp', { freq: 500, noise: 0 }); 31 | ret.notePos = 100; 32 | ret.amp = 1; 33 | ret.dir = 1; 34 | ret.note = (freq, amp, dir) => { 35 | ret.freq.value = freq; 36 | ret.notePos = dir > 0 ? 0 : 0.6; 37 | ret.amp = amp; 38 | ret.dir = dir; 39 | }; 40 | return ret; 41 | }; 42 | 43 | // Use the Poly class to make an instrument with 8 managed voices 44 | const poly = new Poly(8, mkVoice, post); 45 | 46 | // At control rate, apply an envelope to all voices 47 | const env = (x) => { 48 | return Math.max(0, (1.2)/((x)*10+1)-0.2); 49 | } 50 | graph.ctrl((tSeconds, delta) => { 51 | poly.forEach(voice => { 52 | voice.notePos += delta*voice.dir; 53 | if (voice.notePos < 0) { 54 | voice.notePos = 100; 55 | voice.dir = 1; 56 | } 57 | voice.noise.value = env(voice.notePos) * voice.amp; 58 | }); 59 | }); 60 | 61 | const seq = new Seq(graph); 62 | const idxpow = 1; 63 | let freqs; 64 | 65 | // Generate a set of pitches for the chiming 66 | const setFreqs = () => { 67 | freqs = mixFreqs(fParam1.value, fParam2.value, 2+Math.floor(tightParam.value)); 68 | freqs = freqs.slice(0,8-Math.floor(tightParam.value)); 69 | post.hp1.value = Math.min(...freqs)*0.5; 70 | }; 71 | 72 | // At control rate, trigger chimes randomly 73 | seq.schedule(async () => { 74 | let t = 0; 75 | let rand = randomSeed(1); 76 | let lastIdx = 0; 77 | while (true) { 78 | if (fParam1.changed() || fParam2.changed() || tightParam.changed()) setFreqs(); 79 | if (t === 100) { 80 | t = 0; 81 | rand = randomSeed(1); 82 | } 83 | let idx = Math.pow(rand(), idxpow); 84 | if (idx === lastIdx) idx = Math.pow(rand(), idxpow); 85 | if (idx === lastIdx) idx = Math.pow(rand(), idxpow); 86 | lastIdx = idx; 87 | const f = freqs[Math.floor(idx*freqs.length)]; 88 | poly.note(f, t ? rand() : 1, Math.sign(rand()-0.1)); 89 | await seq.play((0.3+0.2*rand())/density.value); 90 | t++; 91 | } 92 | }); 93 | 94 | export const process = graph.makeProcessor(); 95 | -------------------------------------------------------------------------------- /MusicSource/patches/wind-theme/faust/bass.dsp: -------------------------------------------------------------------------------- 1 | // Contrabass instrument in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 7 | freq = hslider("freq", 80, 30, 200, 0.001); 8 | lp1 = hslider("lp1", 3300, 100, 10000, 0.0001); 9 | lp2 = hslider("lp2", 580, 100, 1000, 0.0001); 10 | noise1amt = hslider("noise1", 0, 0, 1, 0.0001); 11 | 12 | // Convert frequency to delay line length in samples 13 | f2samp(f) = (f : ma.inv : ba.sec2samp) - 1; 14 | 15 | // Exciter: filtered oscillator 16 | noise1 = no.noise * noise1amt * 0.5 : fi.lowpass(1, lp1) : fi.lowpass(2, lp2); 17 | exc = noise1 : fi.highpass(2, 80); 18 | 19 | // Resonator with artifacts 20 | reso = loop(vibrato-alias) : loop(vibrato+alias) with { 21 | vibrato = freq * (1 + os.osc(4) * 0.008); 22 | alias = (3 / ma.SR) * freq * freq; 23 | loop(f) = + ~ (de.fdelay2(9000, f2samp(f)) * 0.8); 24 | }; 25 | 26 | // Post-processing: reverb and compression 27 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 10, 20, 44100); 28 | strin = exc*preamp : reso <: rev_st;// : co.limiter_1176_R4_stereo; 29 | process = hgroup("strin", strin); -------------------------------------------------------------------------------- /MusicSource/patches/wind-theme/faust/post.dsp: -------------------------------------------------------------------------------- 1 | // Post-processor in Faust with delay, reverb and compression 2 | 3 | import("stdfaust.lib"); 4 | preamp = hslider("preamp", 1, 0, 1, 0.0001); 5 | lpf = hslider("lpf", 500, 20, 10000, 0.001); 6 | 7 | lp = fi.lowpass(2, lpf); 8 | hp = fi.highpass(3, 200); 9 | 10 | // mono -> stero ping pong delay 11 | loopy_mst(dry) = dry*0.7 + l1*0.2 + l2*0.05, dry*0.7 + l1*0.1 + l2*0.2 with { 12 | l1 = dry : + ~ (de.fdelay2(9000, 0.001+0.0003*os.oscp(0.1, 2) : ba.sec2samp) * 0.6); 13 | l2 = dry : + ~ (de.fdelay2(9000, 0.001+0.0003*os.oscp(0.1, 0) : ba.sec2samp) * 0.6); 14 | }; 15 | rev_st = re.zita_rev1_stereo(0, 200, 6000, 2.4, 2.7, 44100); 16 | del_st = + ~ @(1.57 : ba.sec2samp) * 0.8, + ~ @(1.7 : ba.sec2samp) * 0.8; 17 | 18 | process(x) = x*preamp : hp : lp : loopy_mst : del_st : rev_st : _*0.5, _*0.5 : co.limiter_1176_R4_stereo : _*2, _*2; -------------------------------------------------------------------------------- /MusicSource/patches/wind-theme/faust/reed.dsp: -------------------------------------------------------------------------------- 1 | // Reed-like waveguide instrument in Faust 2 | 3 | import("stdfaust.lib"); 4 | 5 | // Use "sliders" to add parameters to control the instrument 6 | freq = hslider("freq", 80, 30, 2000, 0.001); 7 | lp1 = hslider("lp1", 2000, 100, 15000, 0.0001); 8 | noise1amt = hslider("noise", 0, 0, 1, 0.0001); 9 | 10 | // Exciter: filtered oscillator 11 | noise1 = no.pink_noise*noise1amt : fi.lowpass(1, lp1); 12 | exc = noise1 : fi.highpass(2, freq*1.2); 13 | 14 | // Waveguide filters 15 | wgbottom = 10 + freq * 0.1; 16 | wgtop = 140 + freq*1.75; 17 | 18 | // Convert frequency to delay line length in samples 19 | wgf2samp(f) = 1 / (f + ((0.22*f*f)/(wgtop*2+wgbottom*2)) ) : ba.sec2samp - 1; 20 | 21 | // Waveguide loop 22 | reso = loop(vibrato-alias) : loop(vibrato+alias) : loop(vibrato) with { 23 | vibrato = freq * (1 + os.osc(5) * 0.006); 24 | alias = 0.013 * freq; 25 | wgf = fi.lowpass(1, wgtop) : fi.highpass(1, wgbottom); 26 | wg(f) = de.fdelay2(9000, wgf2samp(f)) : wgf; 27 | loop(f) = + ~ wg(f); 28 | }; 29 | 30 | process = exc : reso : fi.notchw(freq*0.1, freq) : fi.notchw(freq*0.02, freq*2); 31 | -------------------------------------------------------------------------------- /MusicSource/patches/wind-theme/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Reed-like polyphonic patch playing a slow and simple progression. The reed 3 | * instrument is a simple waveguide model. A lot of delay is applied so it ends 4 | * up sounding mostly like a drone. Similar to the "ocean-theme" patch. 5 | * 6 | * Audio-rate code is written in Faust and can be found in the faust directory. 7 | * 8 | * This file contains sequencing, envelopes, and other control code. 9 | */ 10 | 11 | import { Graph, FaustNode, Seq, Poly } from '../../lib/tealib.js'; 12 | import { mixFreqs } from '../../lib/math.js'; 13 | 14 | export const sampleRate = 44100; 15 | const graph = new Graph({ sampleRate }); 16 | 17 | // Create some nodes: Faust bass, and post-processing for the strings 18 | const post = new FaustNode('faust/post.dsp', { preamp: 1, lpf: 16000 }); 19 | const bass = new FaustNode('faust/bass.dsp', { preamp: 1, freq: 0, noise1: 0, lp1: 3300, lp2: 580 }); 20 | post.connect(graph.out); 21 | bass.connect(graph.out); 22 | 23 | //Parameters to control the patch 24 | const preamp = graph.addParam('preamp', { def: 1 }); 25 | preamp.connect(post.preamp); 26 | preamp.connect(bass.preamp); 27 | // Frequency centers for the generative progression 28 | const fParam1 = graph.addParam('freq1', { def: '100*8' }); 29 | const fParam2 = graph.addParam('freq2', { def: '100*8*4/5' }); 30 | // Timbral parameters: lowpasses, bass amount 31 | const lp1Param = graph.addParam('lp1', { def: 2000, min: 100, max: 15000 }); 32 | const bassiness = graph.addParam('bassiness'); 33 | // Parameter to pause the progression 34 | const freeze = graph.addParam('freeze'); 35 | 36 | // The strings are polyphonic. This creates one polyphonic voice 37 | const mkVoice = i => { 38 | const ret = new FaustNode('faust/reed.dsp', { freq: 500, noise: 0, lp1: 2000 }); 39 | lp1Param.connect(ret.lp1); 40 | ret.notePos = 100; 41 | ret.amp = 1; 42 | ret.note = (freq, amp) => { 43 | ret.freq.value = freq/2; 44 | ret.notePos = 0; 45 | ret.amp = amp; 46 | }; 47 | return ret; 48 | }; 49 | 50 | // Use the Poly class to make an instrument with 3 managed voices 51 | const poly = new Poly(3, mkVoice, post); 52 | 53 | // At control rate, apply an envelope to all voices 54 | const atk = 4, rel = 3; 55 | const env = (x) => { 56 | if (x < atk) return 1-Math.cos(Math.PI*x/atk); 57 | if (x < atk+rel) return 1+Math.cos(Math.PI*(x-atk)/rel); 58 | return 0; 59 | }; 60 | let bassTgt = 0; 61 | graph.ctrl((tSec, delta) => { 62 | post.lpf.value = 16000*(1-.8/(tSec*tSec*0.002+1)); 63 | bass.noise1.value += (bassTgt - bass.noise1.value)*0.001; 64 | poly.forEach(voice => { 65 | voice.notePos += delta; 66 | voice.noise.value = env(voice.notePos) * voice.amp; 67 | }); 68 | }); 69 | 70 | const seq = new Seq(graph); 71 | 72 | // Generate a set of pitches for the progression 73 | const scaleLen = 6; 74 | let freqs; 75 | const setFreqs = () => { 76 | freqs = mixFreqs(fParam1.value, fParam2.value, 0.5); 77 | freqs = freqs.filter(f => f>567 && f<1380).slice(0,scaleLen).sort((a, b) => a - b); 78 | }; 79 | const noteGen = function * () { 80 | let i = 0; 81 | while (true) { 82 | yield i % scaleLen; 83 | yield (i+4) % scaleLen; 84 | i++; 85 | } 86 | }; 87 | 88 | // Run the progression according to the parameters and generated pitches 89 | seq.schedule(async () => { 90 | const gen = noteGen(); 91 | while (true) { 92 | if (fParam1.changed() || fParam2.changed()) setFreqs(); 93 | const i = gen.next().value; 94 | poly.note(freqs[i], 0.7); 95 | if (i < scaleLen/2) { 96 | bass.freq.value = freqs[i]; 97 | bass.noise1.value = 0; 98 | bassTgt = bassiness.value*2; 99 | while (bass.freq.value > 110) bass.freq.value /= 2; 100 | } 101 | while (i === 0 && freeze.value) await seq.play(1); 102 | await seq.play(5); 103 | } 104 | }); 105 | 106 | export const process = graph.makeProcessor(); 107 | -------------------------------------------------------------------------------- /MusicSource/tracks/01. diffuse_process.tmac: -------------------------------------------------------------------------------- 1 | # This track reflects the opening of the interactive version of 2 | # ambient.garden. The same patches are used right away, then the track 3 | # elaborates in a different direction. 4 | # Recorded using the experimental macro recording feature. 5 | 6 | 00:00 start resonant-drone. amp: 1, preamp: 1, impact: 1, ofs: 0.06, freq1: 100*1, freq2: 100*9/4, freq3: 100*3/4 7 | 00:04 start contrabass. amp: 1, preamp: 1, freq1: 100*3/4, flatten: 0, lp1: 10000, texamp: 0.1, texvar: 0 8 | 9 | # modulation to 4/5 10 | 11 | 00:20 start resonant-drone #2. amp: 1, preamp: 1, impact: 0, ofs: 0.09, freq1: 100*1*4/5, freq2: 100*9/4*4/5, freq3: 100*3/4*4/5 12 | 00:20 tweak resonant-drone. preamp: 0=0.9, 1=0.7, 2=0.5, 3=0.4, 4=0.3, 5=0 13 | 00:20 start sparse-soprano. amp: 1, preamp: 0.3, freq1: 100*4*8/9 14 | 00:25 tweak contrabass. texamp: 0.5; texvar: 1; freq1: 100*3/4*4/5 15 | 00:27 tweak sparse-soprano. preamp: 0.07 16 | 00:30 stop resonant-drone. 17 | 00:35 tweak resonant-drone #2. preamp: 0=0.9, 1=0.8, 2=0.7, 3=0.55, 4=0.5, 5=0.4 18 | 00:35 tweak sparse-soprano. freq1: 100*9/4*2/3 19 | 00:35 tweak sparse-soprano. preamp: 0=0.15, 2=0.3, 4=0.5, 6=0.7 20 | 00:40 start vibraphones. amp: 1, preamp: 0.4, impact: 0, freq1: 100*3/4*3, freq2: 100*3/4*2/3*3 21 | 00:40 start wind-bells. amp: 1, preamp: 1, freq1: 100*3/4*3*8, freq2: 100*3/4*2/3*3*8, tightness: 1.1, lp1: 100, density: 1 22 | 00:42 tweak contrabass. texamp: 0.15 23 | 00:42 tweak vibraphones. preamp: 0=0.6, 3=0.8 24 | 00:42 tweak wind-bells. lp1: 0=150, 4=200, 10=250 25 | 00:45 tweak contrabass. texamp: 0.25; texvar: 0; preamp: 0.3; freq1: 100*3/4 26 | 00:45 tweak resonant-drone #2. preamp: 0=0.25, 1=0.15, 2=0 27 | 28 | ##### modulation back to 1 29 | 00:52 start contrabass #2. amp: 1, preamp: 0, freq1: 100*3/4, flatten: 0, lp1: 10000, texamp: 0.15, texvar: 0 30 | 00:55 tweak contrabass #2. preamp: 0=0.3, 0.5=0.5, 1=0.75, 1.5=1 31 | 00:55 tweak sparse-soprano. preamp: 0=0.5, 4=0.33, 10=0.25, 15=0.1, 20=0 32 | 00:55 start resonant-drone. amp: 1, preamp: 1, impact: 0.2, ofs: 0.06, freq1: 100*1, freq2: 100*9/4, freq3: 100*3/4 33 | 00:55 tweak vibraphones. preamp: 0=0.6, 3=0.4, 6=0.2, 9=0.1, 12=0 34 | 01:00 stop resonant-drone #2. 35 | 01:00 tweak contrabass. preamp: 0 36 | 01:00 tweak wind-bells. density: 0=1.25, 8=1.5 37 | 01:10 stop contrabass. 38 | 01:20 tweak contrabass #2. preamp: 0=0.8, 8=0.6, 15=0.4, 18=0.2 39 | 01:20 tweak wind-bells. freq1: 100*3/4*4*8; lp1: 400 40 | 01:20 stop vibraphones. 41 | 01:25 tweak resonant-drone. preamp: 0=0.8, 7=0.6, 14=0.4, 18=0.2 42 | 01:28 stop sparse-soprano. 43 | 01:30 tweak wind-bells. freq2: 100*3/4*2/3*4*8; lp1: 550 44 | 01:31 start sparse-soprano. amp: 1, preamp: 0.5, freq1: 100*3/4*9 45 | 01:40 tweak wind-bells. freq2: 100*3/4*2/3*4*8; lp1: 650 46 | 01:40 tweak contrabass #2. preamp: 0.4 47 | 01:45 tweak resonant-drone. preamp: 0=0.4, 3=0.6, 6=0.8 48 | 01:46 tweak sparse-soprano. preamp: 0=0.3, 4=0.15, 10=0.07, 18=0 49 | 01:46 tweak wind-bells. lp1: 450; density: 1.75 50 | 01:50 tweak contrabass #2. preamp: 0.6 51 | 52 | #### melodic 53 | 54 | 01:50 tweak wind-bells. freq1: 100*3/4*8; freq2: 100*9/4*8 55 | 01:55 tweak wind-bells. lp1: 350; density: 2 56 | 02:00 start melodic-soprano. amp: 0.75, preamp: 1, freq1: 100*6/2, freq2: 100*9/2, minFreq: 289, maxFreq: 616.4, complexity: 2, slowness: 0.7 57 | 58 | ##### modulation to 4/5 59 | 60 | 02:19 start resonant-drone #2. amp: 1, preamp: 1, impact: 0, ofs: 0.09, freq1: 100*1*4/5, freq2: 100*9/4*4/5, freq3: 100*3/4*4/5 61 | 02:19 tweak contrabass #2. freq1: 100*3/4*4/5 62 | 02:19 tweak resonant-drone. preamp: 0=0.9, 1=0.7, 2=0.6, 3=0.4, 5=0.3, 6=0.2, 7=0.1, 8=0 63 | 02:20 stop sparse-soprano. 64 | 02:26 tweak melodic-soprano. freq1: 100*6/2*4/5; freq2: 100*9/2*4/5 65 | 02:26 tweak contrabass #2. preamp: 0.9 66 | 02:38 tweak melodic-soprano. slowness: 0=0.6, 4=0.5 67 | 02:40 stop resonant-drone. 68 | 02:50 tweak contrabass #2. preamp: 0=0.7, 5=0.5, 8=0.4, 11=0.6, 12=0.8, 13=1 69 | 02:56 tweak resonant-drone #2. preamp: 0=0.9, 1=0.8, 2=0.7, 3=0.6, 4=0.5 70 | 71 | ##### modulation back to 1 72 | 73 | 03:01 start resonant-drone. amp: 1, preamp: 1, impact: 1, ofs: 0.06, freq1: 100*1, freq2: 100*9/4, freq3: 100*3/4 74 | 03:01 tweak contrabass #2. freq1: 100*3/4 75 | 03:01 tweak wind-bells. freq1: 100*3/4*8; freq2: 100*9/4*8 76 | 03:03 tweak melodic-soprano. freq1: 100*6/2; freq2: 100*9/2 77 | 03:03 tweak resonant-drone #2. preamp: 0=0.4, 2=0.3, 4=0.2, 6=0.25 78 | 03:08 tweak contrabass #2. preamp: 0=0.7, 20=0.4, 30=0.2 79 | 03:12 tweak resonant-drone. preamp: 0=0.8, 10=0.6, 20=0.4, 30=0.2 80 | 03:12 tweak resonant-drone #2. preamp: 0 81 | 03:18 start sparse-soprano. amp: 1, preamp: 0.6, freq1: 100*3/4*4 82 | 03:25 stop resonant-drone #2. 83 | 03:30 tweak sparse-soprano. preamp: 0 84 | 03:34 start sparse-soprano #2. amp: 1, preamp: 0.6, freq1: 100*3/4*5 85 | 03:40 stop sparse-soprano. 86 | 03:40 tweak wind-bells. density: 1.5; preamp: 0.8 87 | 03:46 tweak sparse-soprano #2. freq1: 100*3/4*6 88 | 03:46 tweak contrabass #2. preamp: 0=0.4, 10=0.6 89 | 03:50 tweak resonant-drone. preamp: 0=0.3, 10=0.5, 15=0.7 90 | 03:55 tweak sparse-soprano #2. preamp: 0 91 | 03:58 start sparse-soprano. amp: 1, preamp: 0.5, freq1: 100*3/4*7 92 | 04:05 stop sparse-soprano #2. 93 | 04:05 tweak wind-bells. density: 0=1.25, 5=1, 10=0.8 94 | 04:11 tweak sparse-soprano. preamp: 0.3 95 | 96 | # ending 97 | 98 | 04:22 tweak melodic-soprano. preamp: 0 99 | 04:22 tweak sparse-soprano. preamp: 0.2 100 | 04:22 tweak wind-bells. density: 0=0.66, 5=0.44, 10=0.22, 15=0.1 101 | 04:25 tweak sparse-soprano. preamp: 0=0.18, 0.5=0.16, 1=0.15, 1.5=0.13, 2=0.1, 2.5=0.08, 3=0.03 102 | 04:30 tweak contrabass #2. preamp: 0=0.56, 1=0.52, 2=0.43, 3=0.38, 4=0.29 103 | 04:35 tweak resonant-drone. preamp: 0=0.66, 0.5=0.6, 1=0.54, 1.5=0.48, 2=0.44, 2.5=0.4, 3=0.35, 3.5=0.29 104 | 04:40 tweak sparse-soprano. preamp: 0 105 | 04:40 tweak wind-bells. preamp: 0=0.6, 1=0.4, 2=0.2, 3=0 106 | 04:41 tweak contrabass #2. preamp: 0=0.24, 4=0.19, 7=0.11, 10=0.07, 15=0.03, 20=0 107 | 04:46 tweak resonant-drone. preamp: 0=0.28, 3=0.14, 5=0.08, 7=0.03, 10=0.01, 15=0 108 | 109 | 05:07 stop melodic-soprano. 110 | 05:07 stop sparse-soprano. 111 | 05:07 stop contrabass #2. 112 | 05:07 stop resonant-drone. 113 | 05:07 stop wind-bells. -------------------------------------------------------------------------------- /MusicSource/tracks/02. reflecting_strings.tmac: -------------------------------------------------------------------------------- 1 | # This track showcases the moon-strings patch, and harmonies produced by the 2 | # frequency mixing algorithm. I haven't added moon-strings to ambient garden 3 | # yet but might at some point. 4 | # Recorded using the experimental macro recording feature. 5 | 6 | 00:00 start moon-strings. amp: 1, preamp: 1, freq1: 100*9/4, freq2: 100*8/4, lp: 160, fb: 0.8, density: 0.27, flatness: 1 7 | 00:15 start sine-drone. amp: 1, preamp: 0.3, freq1: 100*9/4, freq2: 100*8/4 8 | 00:15 tweak moon-strings. lp: 0=200, 5=300 9 | 00:15 tweak moon-strings. freq1: 100*9/2 10 | 00:22 tweak moon-strings. flatness: 0=0.8, 5=0.6, 10=0.4, 15=0.3, 20=0 11 | 00:25 tweak moon-strings. freq2: 100*8/2 12 | 00:30 tweak moon-strings. density: 0=0.3, 4=0.33, 8=0.37, 10=0.4 13 | 00:36 tweak moon-strings. lp: 400; freq1: 100*9 14 | 00:42 tweak moon-strings. density: 0=0.5, 4=0.6, 8=0.65, 10=0.7 15 | 01:00 start vibraphones. amp: 1, preamp: 1, impact: 1, freq1: 100*12, freq2: 100*3 16 | 01:00 tweak moon-strings. lp: 0=410, 2=450, 4=500, 6=550, 10=600, 13=700, 16=800 17 | 01:01 tweak vibraphones. preamp: 0=0.8, 2=0.6, 4=0.4, 6=0.2, 10=0 18 | 01:05 tweak sine-drone. preamp: 0=0.35, 10=0.4 19 | 01:10 tweak moon-strings. density: 0.75; freq2: 100*8 20 | 01:20 stop vibraphones. 21 | 01:27 tweak moon-strings. density: 0.8; fb: 0.65 22 | 01:53 tweak sine-drone. freq2: 100*8/2 23 | 01:58 tweak moon-strings. density: 0=0.85, 5=0.9, 10=1 24 | 02:02 tweak moon-strings. fb: 0.2 25 | 02:08 tweak sine-drone. freq1: 100*9/2 26 | 02:08 start ocean-theme. amp: 1, preamp: 0.3, freq1: 100*9, freq2: 100*8, lp1: 1545, lp2: 500, bassiness: 0, freeze: 0 27 | 02:10 tweak sine-drone. preamp: 0=0.3, 5=0.2, 12=0.1, 20=0.05 28 | 02:19 start vibraphones. amp: 1, preamp: 3, impact: 1, freq1: 100*8, freq2: 100*12 29 | 02:20 tweak moon-strings. lp: 600 30 | 02:21 tweak vibraphones. preamp: 0=2.5, 2=1.7, 4=1, 6=0.4, 10=0 31 | 02:30 tweak ocean-theme. lp2: 0=800, 10=1500 32 | 33 | # 1st chord change to 9,10 34 | 35 | # 02:35 tweak sine-drone. preamp: 0=0.3, 1=0.2, 2=0.1 36 | 02:35 tweak moon-strings. lp: 200 37 | 02:35 stop vibraphones. 38 | 02:38 start vibraphones. amp: 1, preamp: 4, impact: 1, freq1: 100*10, freq2: 100*15 39 | 02:38 tweak sine-drone. freq2: 100*10/2 40 | 02:38 tweak moon-strings. freq2: 100*10; lp: 600 41 | 02:39 tweak sine-drone. preamp: 0=0.2, 1=0.3, 2=0.4 42 | 02:40 tweak vibraphones. preamp: 0=2.5, 2=1.7, 4=1, 6=0.4, 10=0 43 | 02:41 tweak moon-strings. lp: 800 44 | 02:49 tweak ocean-theme. preamp: 0=0.37, 5=0.42, 10=0.5 45 | 02:58 start vibraphones #2. amp: 1, preamp: 3, impact: 1, freq1: 100*8*5/6, freq2: 100*4*5/6 46 | 03:01 tweak ocean-theme. preamp: 0=0.33, 5=0.22 47 | 03:01 tweak vibraphones #2. preamp: 0=2.5, 2=1.7, 4=1, 6=0.4, 10=0 48 | 03:05 tweak sine-drone. preamp: 0=0.3, 5=0.2, 10=0.1 49 | 03:09 tweak moon-strings. lp: 0=600, 4=400 50 | 03:09 tweak ocean-theme. lp2: 1000 51 | 03:09 stop vibraphones. 52 | 03:15 tweak sine-drone. preamp: 0=0.2, 3=0.4, 5=0.6 53 | 54 | # 2nd chord change to 8,10 55 | 56 | 03:18 start vibraphones. amp: 1, preamp: 2, impact: 1, freq1: 100*4, freq2: 100*10 57 | 03:18 tweak sine-drone. freq2: 100*8/2 58 | 03:18 tweak moon-strings. freq1: 100*10; freq2: 100*8; lp: 700 59 | 03:19 stop vibraphones #2. 60 | 03:22 tweak vibraphones. preamp: 0=1.7, 2=1.3, 4=1, 6=0.4, 10=0 61 | 03:22 tweak ocean-theme. lp2: 4800; preamp: 0.33 62 | 03:29 tweak moon-strings. fb: 0=0.6, 12=0.3 63 | 03:39 stop vibraphones. 64 | 03:49 tweak ocean-theme. lp2: 0=3000, 3=2000, 8=1000 65 | 03:49 tweak ocean-theme. preamp: 0.12 66 | 67 | # chord change back to 8,9 68 | 69 | 03:58 tweak moon-strings. freq1: 100*9/2; freq2: 100*8/4 70 | 04:02 tweak moon-strings. freq1: 100*9 71 | 04:03 tweak sine-drone. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.1 72 | 04:05 tweak moon-strings. freq2: 100*8/2 73 | 04:08 tweak moon-strings. freq2: 100*8 74 | 04:11 tweak moon-strings. freq1: 100*9*2 75 | 04:14 tweak moon-strings. freq2: 100*8*2 76 | 04:22 tweak moon-strings. freq2: 100*8 77 | 04:25 tweak moon-strings. freq1: 100*9; fb: 0.33 78 | 04:25 tweak sine-drone. preamp: 0=0.2, 8=0.3, 18=0.2, 23=0.1, 33=0.05, 38=0 79 | 04:28 tweak moon-strings. freq2: 100*8/2; fb: 0.44 80 | 04:28 tweak ocean-theme. preamp: 0 81 | 04:31 tweak moon-strings. freq2: 100*8/4; fb: 0.7 82 | 04:34 tweak moon-strings. freq1: 100*9/2; fb: 0.9 83 | 04:37 tweak moon-strings. density: 0 84 | 85 | 05:10 stop moon-strings. 86 | 05:10 stop sine-drone. 87 | 05:10 stop ocean-theme. 88 | -------------------------------------------------------------------------------- /MusicSource/tracks/03. fundamental_breath.tmac: -------------------------------------------------------------------------------- 1 | # This track showcases the harmonic-series patch. 2 | # Recorded using the experimental macro recording feature. 3 | 4 | 00:00 start harmonic-series. amp: 1, preamp: 0.35, freq1: 100 5 | 00:10 tweak harmonic-series. preamp: 0.4 6 | 00:24 start contrabass. amp: 1, preamp: 0.26, freq1: 100*1/2, flatten: 1, lp1: 1338, texamp: 0, texvar: 0 7 | 00:33 tweak harmonic-series. preamp: 0.45 8 | 00:35 tweak contrabass. preamp: 0=0.3, 4=0.4, 8=0.5 9 | 00:39 tweak contrabass. lp1: 2436 10 | 00:49 tweak harmonic-series. preamp: 0.34 11 | 00:51 tweak contrabass. texamp: 0=0.03, 0.5=0.14, 1=0.23, 1.5=0.31, 2=0.4 12 | 00:57 start rain-stick. amp: 1, preamp: 0.11, pitch: 0.08 13 | 01:21 tweak rain-stick. preamp: 0=0.2, 0.5=0.26 14 | 01:22 tweak contrabass. texamp: 0=0.68, 0.5=0.64, 1=0.57 15 | 01:28 tweak rain-stick. preamp: 0=0.3, 0.5=0.34 16 | 01:31 start ocean-theme. amp: 1, preamp: 0.13, freq1: 100*6, freq2: 100*8, lp1: 2380, lp2: 1471, bassiness: 0, freeze: 0 17 | 01:35 tweak harmonic-series. preamp: 0=0.2, 0.5=0.2, 1=0.21, 2=0.21 18 | 01:43 tweak contrabass. preamp: 0=0.4, 10=0.3, 18=0.2, 26=0.15 19 | 01:43 tweak contrabass. texamp: 0.49 20 | 01:45 tweak rain-stick. pitch: 0.16 21 | 01:46 tweak ocean-theme. preamp: 0=0.15, 0.5=0.2, 1=0.24, 1.5=0.27 22 | 01:58 tweak ocean-theme. lp2: 0=1381, 0.5=1635, 1=1873, 1.5=2290 23 | # 02:05 tweak harmonic-series. preamp: 0 24 | 02:05 tweak rain-stick. pitch: 0=0.18, 1=0.22, 2=0.24, 3=0.26, 4=0.29, 5=0.31, 6=0.31, 7=0.34, 8=0.38 25 | 02:15 tweak contrabass. freq1: 100*4/3/3 26 | 02:15 tweak contrabass. preamp: 0=0.2, 10=0.3, 18=0.4, 40=0.5 27 | 02:20 tweak harmonic-series. preamp: 0.2 28 | 02:22 tweak rain-stick. pitch: 0.2 29 | 02:23 tweak rain-stick. preamp: 0=0.43, 0.5=0.48, 1=0.59 30 | 02:28 tweak contrabass. texamp: 0=0.54, 0.5=0.56, 1=0.59, 1.5=0.61, 2=0.63, 2.5=0.64 31 | 02:30 tweak harmonic-series. preamp: 0.1 32 | 02:35 tweak ocean-theme. preamp: 0=0.29, 0.5=0.31, 1=0.32, 1.5=0.33, 2=0.34, 2.5=0.36, 3=0.4, 3.5=0.44 33 | 02:43 tweak ocean-theme. lp2: 0=2216, 0.5=2290, 1.5=2454, 2=2618 34 | 02:51 tweak ocean-theme. lp2: 1963 35 | 02:52 tweak ocean-theme. lp1: 0=2618, 0.5=2961, 1=3453 36 | 02:58 tweak contrabass. preamp: 0.2; texvar: 0.34; texamp: 0.85; lp1: 2268; flatten: 0.83 37 | 03:00 tweak contrabass. freq1: 100*1/2; preamp: 0.5 38 | 03:05 tweak contrabass. preamp: 0=0.4, 10=0.3, 20=0.2, 30=0.1 39 | 03:08 tweak rain-stick. preamp: 0=0.64, 0.5=0.65, 1=0.69, 1.5=0.74, 2=0.8, 2.5=0.84 40 | 03:15 tweak ocean-theme. preamp: 0=0.42, 0.5=0.45, 1=0.49, 1.5=0.51, 2=0.54, 2.5=0.57 41 | 03:19 tweak ocean-theme. lp2: 2380 42 | 03:20 tweak ocean-theme. bassiness: 0=0.1, 6=0.25, 12=0.4 43 | 03:22 tweak rain-stick. preamp: 0=0.88, 0.5=0.9, 1=1 44 | 03:25 tweak rain-stick. pitch: 0=0.21, 0.5=0.28, 1=0.31, 1.5=0.35 45 | # 03:28 tweak harmonic-series. preamp: 0=0.18, 0.5=0.15, 1=0.14, 1.5=0.13, 2=0.13, 2.5=0.12, 3=0.11 46 | 03:37 tweak ocean-theme. preamp: 0=0.56, 0.5=0.6, 1=0.6, 1.5=0.65 47 | 03:48 tweak rain-stick. pitch: 0=0.33, 0.5=0.3 48 | 03:52 tweak ocean-theme. preamp: 0=0.65, 0.5=0.71 49 | 03:54 tweak ocean-theme. lp2: 0=2126, 0.5=1873 50 | 03:56 tweak ocean-theme. preamp: 0=0.73, 0.5=0.78, 1=0.8, 1.5=0.8, 2=0.85 51 | 04:02 tweak contrabass. flatten: 0=0.79, 0.5=0.75, 1=0.7 52 | 04:04 tweak contrabass. texamp: 0=0.86, 0.5=0.9, 1=0.94, 2=0.98 53 | 04:08 tweak contrabass. preamp: 0=0.4, 0.5=0.38, 1=0.36, 1.5=0.35, 2=0.31 54 | 04:16 tweak harmonic-series. preamp: 0=0.11, 1=0.08, 2=0.05, 3=0.03, 4=0 55 | 04:23 tweak ocean-theme. preamp: 0=0.86, 0.5=0.94 56 | 04:25 tweak ocean-theme. lp1: 0=3289, 0.5=2961, 1=2618, 1.5=2052 57 | 04:25 tweak contrabass. preamp: 0=0.2, 10=0.3, 20=0.4 58 | 04:30 tweak ocean-theme. lp2: 0=2380, 0.5=1963, 1=1218, 1.5=725.8 59 | 04:37 tweak ocean-theme. bassiness: 0=0.3, 6=0.2, 12=0.1 60 | 04:40 tweak rain-stick. pitch: 0.11 61 | 04:40 tweak ocean-theme. preamp: 0=0.8, 2=0.6, 5=0.5, 10=0.4, 15=0.3, 30=0.2, 40=0.1 62 | 04:41 tweak rain-stick. preamp: 0.79 63 | 04:50 tweak rain-stick. pitch: 0=0.08, 0.5=0.05, 1=0 64 | 04:52 tweak ocean-theme. lp1: 561.9 65 | 04:54 tweak ocean-theme. lp2: 0=472.5, 0.5=144.7, 1=100 66 | 04:56 tweak ocean-theme. bassiness: 0 67 | 05:00 tweak contrabass. texamp: 0=0.9, 2=0.7, 4=0.6, 6=0.5 68 | 05:00 tweak rain-stick. preamp: 0=0.5, 2=0.4, 4=0.3, 8=0.2, 10=0.1, 13=0 69 | 05:04 tweak contrabass. preamp: 0=0.3, 8=0.2, 16=0.1, 25=0.05, 30=0 70 | 05:20 tweak rain-stick. amp: 0=0.9, 1=0.8, 3=0.7, 5=0.5, 6=0.4, 8=0.3, 10=0.2, 12=0.1, 14=0 71 | 05:33 tweak ocean-theme. preamp: 0=0.08, 3=0.04, 6=0.02, 9=0 72 | 05:58 stop harmonic-series. 73 | 05:58 stop rain-stick. 74 | 05:58 stop contrabass. 75 | 05:58 stop ocean-theme. -------------------------------------------------------------------------------- /MusicSource/tracks/04. voiceless_drift.tmac: -------------------------------------------------------------------------------- 1 | # This track showcases the bottle patches. 2 | # Recorded using the experimental macro recording feature. 3 | 4 | 00:00 start contrabass. amp: 0.6, preamp: 0.5, freq1: 100*1/2, flatten: 1, lp1: 10000, texamp: 1, texvar: 0.3 5 | 00:00 start sine-drone. amp: 0.8, preamp: 0.3, freq1: 100*4, freq2: 100*4*6/5 6 | 00:00 start sparse-bottle. amp: 0.8, preamp: 0.4, freq1: 100*4*6/5, freq2: 100*4*6/5, breath: 0.5, modes: 2.2 7 | 00:03 tweak sparse-bottle. preamp: 0=0.2, 3=0.15 8 | 9 | 00:12 tweak sparse-bottle. preamp: 0.4; freq1: 100*4; freq2: 100*4 10 | 00:13 tweak sine-drone. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.1, 22=0.2, 27=0.3, 35=0.4, 40=0.5 11 | 00:15 tweak sparse-bottle. preamp: 0=0.2, 3=0.15 12 | 00:23 tweak contrabass. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.2, 22=0.3, 27=0.4, 35=0.5, 40=0.6 13 | 14 | 00:30 tweak sparse-bottle. preamp: 0.4; freq1: 100*4*6/5; freq2: 100*4*6/5 15 | 00:33 tweak sparse-bottle. preamp: 0=0.2, 3=0.15 16 | 00:33 start sparse-bottle #2. amp: 0.8, preamp: 0.35, freq1: 100*8, freq2: 100*8*6/5, breath: 0.5, modes: 2.2 17 | 00:36 start melodic-bottle. amp: 0.6, preamp: 0.6, freq1: 100*4, freq2: 100*4*6/5, breath: 0.9, modes: 0.5 18 | 19 | 00:40 tweak melodic-bottle. modes: 1.5 20 | 00:45 tweak sparse-bottle #2. preamp: 0.2 21 | 22 | 00:50 tweak melodic-bottle. breath: 0.8; preamp: 0.7 23 | 00:55 tweak sparse-bottle #2. preamp: 0.1 24 | 25 | 01:00 tweak melodic-bottle. modes: 2.5; preamp: 0.8; trem: 0.2; bounce: 0.2 26 | 01:00 tweak sparse-bottle #2. preamp: 0 27 | 01:00 tweak sine-drone. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.1, 22=0.2, 27=0.3, 35=0.4, 40=0.5 28 | 01:10 tweak contrabass. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.2, 22=0.3, 27=0.4, 35=0.5, 40=0.6 29 | 01:10 stop sparse-bottle #2. 30 | 01:10 start sparse-soprano. amp: 1, preamp: 0.3, freq1: 100*4 31 | 01:10 tweak melodic-bottle. breath: 0.7; trem: 0.3; bounce: 0.3 32 | 01:20 tweak sparse-bottle. preamp: 0.4; freq1: 100*4; freq2: 100*4 33 | 01:25 tweak melodic-bottle. preamp: 1; modes: 3.5; breath: 0.9; freq1: 100*4*2; freq2: 100*4*6/5*2; bounce: 0.7 34 | 01:25 tweak sparse-soprano. preamp: 0 35 | 36 | 01:36 stop sparse-soprano. 37 | 38 | 01:39 tweak contrabass. freq1: 100*4/5 39 | 01:39 tweak sine-drone. freq1: 100*4*4/5 40 | 01:39 tweak melodic-bottle. freq1: 100*4*4/5*2 41 | # amp change 42 | 01:39 start sparse-soprano. amp: 1, preamp: 0.3, freq1: 100*8*4/5 43 | 01:46 tweak sparse-soprano. freq1: 100*4*4/5 44 | 01:54 tweak sparse-soprano. preamp: 0 45 | 46 | 01:55 tweak melodic-bottle. preamp: 0.9; modes: 2.5; breath: 0.8; freq1: 100*4*4/5; freq2: 100*4*6/5; trem: 0.5 47 | 48 | 02:07 tweak melodic-bottle. preamp: 1; modes: 1.5; breath: 0.7 49 | 02:15 stop sparse-soprano. 50 | 02:15 tweak melodic-bottle. trem: 0.7 51 | 02:16 tweak contrabass. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.2, 22=0.3, 27=0.4, 35=0.5, 40=0.6 52 | 02:19 tweak melodic-bottle. amp: 1; preamp: 0.8; modes: 3.5; breath: 0.9; freq1: 100*4*2; freq2: 100*4*6/5*2 53 | 02:19 tweak contrabass. freq1: 100*1/2 54 | 02:19 tweak sine-drone. freq1: 100*4 55 | 02:19 start sparse-soprano. amp: 1, preamp: 0.3, freq1: 100*4 56 | # peak switch 57 | 02:26 tweak melodic-bottle. preamp: 0.9; modes: 2.5; breath: 0.8; freq1: 100*4; freq2: 100*4*6/5 58 | 02:26 tweak sparse-soprano. freq1: 100*2 59 | 02:26 tweak sine-drone. preamp: 0=0.4, 5=0.3, 10=0.2, 15=0.1 60 | 61 | 62 | 02:31 start sparse-bottle #3. amp: 0.8, preamp: 0.5, freq1: 100*2*8, freq2: 100*2*10, breath: 1, modes: 2.54 63 | 02:33 tweak sparse-soprano. preamp: 0 64 | 02:45 tweak sparse-bottle #3. preamp: 1 65 | 66 | 02:46 stop sparse-soprano. 67 | 68 | 02:49 tweak melodic-bottle. preamp: 1; modes: 1.5; breath: 0.8; trem: 0.4 69 | # peak switch / amp change 2 70 | 03:01 tweak sparse-bottle. preamp: 0=0.3, 2=0.3, 5=0.2, 8=0.1, 12=0 71 | 03:05 tweak sine-drone. preamp: 0=0.2, 10=0.3, 15=0.4, 20=0.5 72 | 03:11 tweak melodic-bottle. preamp: 0=0.9, 2=0.75, 4=0.6, 6=0.45, 8=0.3, 10=0.2 73 | 03:11 start sparse-bottle #2. amp: 0.8, preamp: 0.4, freq1: 100*4*6/5*2, freq2: 100*4*6/5*2, breath: 0.5, modes: 2.2 74 | 03:15 tweak melodic-bottle. trem: 0.3; bounce: 0.3 75 | 03:15 tweak sparse-bottle #3. preamp: 0=0.8, 3=0.6, 6=0.42, 10=0.25, 15=0.1, 20=0 76 | 03:16 tweak contrabass. preamp: 0=0.48, 2=0.43, 4=0.26, 6=0.15, 10=0 77 | 03:23 tweak melodic-bottle. hilen: 4; bounce: 1 78 | 03:26 tweak sparse-bottle #2. freq1: 100*4*6/5; freq2: 100*4*6/5 79 | 03:31 stop sparse-bottle. 80 | 03:33 tweak sine-drone. preamp: 0=0.55, 2=0.45, 4=0.36, 7=0.3, 11=0.14, 15=0.1, 20=0.07, 25=0.03, 30=0 81 | 03:36 tweak sparse-bottle #2. freq1: 100*2*6/5; freq2: 100*2*6/5 82 | 03:36 tweak sparse-bottle #2. preamp: 0=0.35, 2=0.31, 4=0.23, 7=0.16, 10=0.13, 13=0.09, 15=0 83 | 04:07 stop sparse-bottle #2. 84 | 04:07 stop sine-drone. 85 | 04:07 stop contrabass. 86 | 04:07 stop melodic-bottle. 87 | 04:07 stop sparse-bottle #3. 88 | -------------------------------------------------------------------------------- /MusicSource/tracks/05. distant_timescales.tmac: -------------------------------------------------------------------------------- 1 | # This track showcases the vocal-overtones patch. 2 | # Recorded using the experimental macro recording feature. 3 | 4 | 00:00 start sparse-bottle. amp: 1, preamp: 1, freq1: 100*2/3, freq2: 100*4/3, breath: 0, modes: 4 5 | 00:05 start vocal-overtones #4. amp: 1, preamp: 0.7, num: 2, freq1: 100/3, freq2: 100*2/3 6 | 00:07 tweak vocal-overtones #4. preamp: 0=0.8, 2=0.9, 4=1 7 | 8 | 00:10 tweak sparse-bottle. preamp: 0=0.8, 3=0.66, 6=0.5, 9=0.3, 12=0.2, 15=0.1, 18=0 9 | 00:10 start vocal-overtones. amp: 1, preamp: 1, num: 1, freq1: 100 10 | 00:13 start vocal-overtones #2. amp: 1, preamp: 1, num: 1, freq1: 100/2*4/3 11 | 00:13 tweak vocal-overtones #4. preamp: 0=0.8, 3=0.6, 6=0.4, 9=0.3, 12=0.2, 15=0.1, 18=0 12 | 00:16 start vocal-overtones #3. amp: 1, preamp: 0.05, num: 1, freq1: 100*4/3 13 | 00:16 tweak vocal-overtones. preamp: 0=0.9, 2=0.8, 4=0.6, 6=0.4, 8=0.3, 10=0.2 14 | 00:17 start moon-strings. amp: 1, preamp: 0.05, freq1: 100*2, freq2: 100*2*4/3, lp: 434.2, fb: 0.9, density: 0.69, flatness: 1 15 | 00:16 tweak vocal-overtones #3. preamp: 0=0.1, 2=0.2, 4=0.3, 6=0.4, 8=0.5, 10=0.6, 13=0.8, 15=1 16 | 17 | 00:20 tweak moon-strings. preamp: 0=0.07, 4=0.1, 7=0.15, 10=0.18, 14=0.2, 18=0.25, 20=0.3 18 | 00:30 tweak vocal-overtones. preamp: 0=0.15, 4=0.1, 8=0.05, 10=0 19 | 00:45 stop sparse-bottle. 20 | 00:45 stop vocal-overtones #4. 21 | 00:45 tweak moon-strings. preamp: 0=0.2, 5=0.15, 10=0.2, 15=0.1, 22=0.15, 30=0.2, 40=0.25, 50=0.3, 55=0.4 22 | 00:50 stop vocal-overtones. 23 | 00:50 start moon-strings #2. amp: 1, preamp: 0.07, freq1: 100*8*2, freq2: 100*8*2*4/3, lp: 350, fb: 0, delfb: 1, density: 0.61, flatness: 1 24 | 00:53 tweak moon-strings #2. preamp: 0=0.1, 2=0.13, 4=0.17, 6=0.22, 8=0.25 25 | 01:00 tweak moon-strings #2. lp: 0=400, 2=450, 4=500, 6=550, 8=600 26 | 01:00 tweak vocal-overtones #2. preamp: 0=0.9, 2=0.66, 4=0.44, 6=0.22, 8=0.1, 10=0 27 | 01:05 tweak vocal-overtones #3. preamp: 0=0.9, 2=0.8, 4=0.7, 6=0.6, 8=0.5 28 | 01:10 tweak moon-strings #2. preamp: 0=0.2, 2=0.15, 4=0.1 29 | 01:20 stop vocal-overtones #2. 30 | 01:20 tweak moon-strings #2. lp: 1800; fb: 0 31 | 01:30 tweak moon-strings #2. preamp: 0=0.07, 2=0.05, 4=0.02 32 | 01:30 tweak vocal-overtones #3. preamp: 0=0.9, 2=0.66, 4=0.44, 6=0.22, 8=0.1, 10=0 33 | 01:35 tweak moon-strings. preamp: 0=0.35, 3=0.27, 13=0.35, 16=0.4 34 | 01:35 start soprano-drone. amp: 1, preamp: 0.1, freq1: 100*4/3*8/3, freq2: 100*8/3, slowness: 0, inertia: 0, hicut: 9 35 | 01:35 start contrabass. amp: 1, preamp: 0.2, freq1: 100/4*4/3*4/3, flatten: 0.86, lp1: 3300, texamp: 0, texvar: 0 36 | 01:37 tweak contrabass. preamp: 0=0.3, 2=0.4, 3=0.5, 6=0.4, 9=0.3, 12=0.2, 14=0.1, 16=0.05, 20=0 37 | 38 | # chord change 1 39 | 40 | 01:40 tweak soprano-drone. preamp: 0=0.3, 4=0.5, 8=0.7, 13=1, 16=0.8, 22=0.6, 30=0.4, 40=0.2 41 | 01:41 start vocal-overtones. amp: 1, preamp: 1, num: 2, freq1: 100*4/3, freq2: 100/2*4/3 42 | 01:44 start vocal-overtones #2. amp: 0.7, preamp: 1, num: 2, freq1: 100*4/3*4/3, freq2: 100/2*4/3*4/3 43 | 44 | # slower sine recede, recede moon1 rather ok 45 | 46 | 01:45 tweak moon-strings. freq2: 100*2*4/3*4/3 47 | 48 | 01:48 tweak moon-strings. preamp: 0=0.3, 7=0.2, 12=0.1, 20=0.05 49 | 01:50 stop vocal-overtones #3. 50 | 02:00 tweak vocal-overtones. preamp: 0=0.9, 2=0.66, 4=0.44, 6=0.22, 8=0.1, 10=0 51 | 02:00 tweak moon-strings #2. freq2: 100*8*2*4/3*4/3 52 | 02:02 tweak moon-strings #2. preamp: 0=0.05, 2=0.07, 4=0.1 53 | 54 | 02:10 stop contrabass. 55 | 02:10 tweak moon-strings #2. preamp: 0=0.09, 2=0.066, 4=0.044, 6=0.022, 8=0.01, 10=0 56 | 02:15 tweak moon-strings. preamp: 0=0.1, 5=0.2, 10=0.4 57 | 02:20 stop vocal-overtones. 58 | 02:20 tweak moon-strings #2. delfb: 0.5 59 | 02:23 tweak vocal-overtones #2. preamp: 0=0.9, 2=0.66, 3=0.44, 5=0.22, 6=0.1, 7=0 60 | 61 | # chord change 2 62 | 63 | 02:25 start vocal-overtones. amp: 1, preamp: 1, num: 1, freq1: 100*2, freq2: 100 64 | 02:27 tweak soprano-drone. preamp: 0 65 | 02:28 tweak soprano-drone. freq1: 100*4 66 | 02:28 start vocal-overtones #3. amp: 1, preamp: 1, num: 1, freq1: 100*2*4/3, freq2: 100 67 | 02:28 stop moon-strings #2. 68 | 02:29 tweak soprano-drone. preamp: 0=0.2, 1=0.35, 2=0.5, 3=0.66, 4=0.8, 5=1 69 | 02:31 tweak moon-strings. freq2: 100*2*4/3 70 | 02:31 start contrabass. amp: 0.7, preamp: 1, freq1: 100*1/2*4/3, flatten: 0.89, lp1: 2050, texamp: 0, texvar: 0 71 | 02:31 tweak vocal-overtones. preamp: 0=0.9, 7=0.7, 14=0.5, 20=0.4, 28=0.3, 33=0.4, 37=0.6, 43=0.8 72 | 02:33 stop vocal-overtones #2. 73 | 02:33 tweak vocal-overtones #3. preamp: 0=0.9, 4=0.7, 8=0.5, 10=0.3, 14=0.2, 18=0.1, 22=0.05, 26=0 74 | 02:35 tweak moon-strings. preamp: 0=0.3, 7=0.2, 12=0.1, 20=0.05 75 | 02:35 start vocal-overtones #2. amp: 1, preamp: 0.1, num: 1, freq1: 100*4/3, freq2: 100 76 | 02:37 tweak vocal-overtones #2. preamp: 0=0.2, 4=0.4, 9=0.6, 12=0.8, 16=1 77 | 02:38 tweak soprano-drone. preamp: 0=0.8, 10=0.6, 16=0.4, 25=0.2 78 | 79 | 02:50 start moon-strings #2. amp: 1, preamp: 0.02, freq1: 100*8*2*4/3, freq2: 100*8*2*4/3*4/3, lp: 1800, fb: 0, delfb: 0.8, density: 1, flatness: 1 80 | 02:50 tweak contrabass. preamp: 0=0.9, 2=0.8, 4=0.7, 6=0.6, 8=0.5 81 | 02:54 tweak moon-strings #2. preamp: 0=0.04, 5=0.06 82 | 83 | 03:05 tweak vocal-overtones #2. preamp: 0=0.9, 4=0.7, 8=0.5, 10=0.3, 14=0.2, 18=0.1, 22=0.05, 26=0 84 | 03:10 tweak moon-strings #2. freq1: 100*8*4/3 85 | 03:10 tweak soprano-drone. preamp: 0=0.4, 5=0.6, 10=0.8, 14=0.6, 18=0.4, 22=0.2, 26=0.1, 29=0.05, 32=0 86 | 03:15 tweak moon-strings #2. freq2: 100*8*4/3*4/3 87 | 03:15 stop vocal-overtones #3. 88 | 03:15 tweak vocal-overtones. preamp: 0=0.7, 4=0.5, 7=0.4, 10=0.3, 14=0.4, 17=0.6, 19=0.8 89 | 90 | 03:20 tweak vocal-overtones. num: 2 91 | 03:25 tweak moon-strings #2. preamp: 0=0.08, 8=0.1, 12=0.12, 16=0.15 92 | 93 | 03:30 tweak moon-strings #2. fb: 0=0.02, 4=0.07, 8=0.16, 12=0.23, 16=0.36, 20=0.46, 24=0.51 94 | 03:30 tweak moon-strings #2. freq1: 100*8*4/3 95 | 03:35 tweak moon-strings #2. freq2: 100*8*4/3*4/3 96 | 97 | 03:36 tweak vocal-overtones. preamp: 0=0.6, 3=0.5, 6=0.4, 9=0.3, 14=0.2, 18=0.1, 22=0.05, 26=0 98 | 03:40 tweak moon-strings #2. lp: 0=1300, 2=1100, 4=950, 8=850, 12=650 99 | 03:40 tweak moon-strings #2. preamp: 0=0.17, 8=0.2, 12=0.23, 16=0.25 100 | 101 | 03:50 tweak moon-strings #2. freq1: 100*4*4/3 102 | 03:55 tweak moon-strings #2. freq2: 100*4*4/3*4/3 103 | 104 | 03:58 tweak moon-strings #2. preamp: 0=0.22, 2=0.2, 4=0.15, 6=0.1, 8=0.05, 10=0.025, 12=0.01, 15=0 105 | 04:00 stop soprano-drone. 106 | 04:00 tweak moon-strings #2. fb: 0=0.55, 2=0.65 107 | 04:05 tweak moon-strings. preamp: 0=0.4, 2=0.3, 4=0.2, 6=0.1, 8=0.05, 11=0 108 | 04:10 tweak contrabass. preamp: 0=0.5, 2=0.4, 4=0.3, 6=0.2, 8=0.1, 10=0.05, 12=0.025, 14=0 109 | 110 | 04:37 stop contrabass. 111 | 04:37 stop moon-strings. 112 | 04:37 stop moon-strings #2. 113 | 04:37 stop vocal-overtones #2. 114 | 04:37 stop vocal-overtones. 115 | -------------------------------------------------------------------------------- /MusicSource/tracks/06. occultation.tmac: -------------------------------------------------------------------------------- 1 | # This track showcases the soprano-drone patch, and harmonies produced by the 2 | # frequency mixing algorithm. Note soprano-drone is not yet in ambient.garden. 3 | # Recorded using the experimental macro recording feature. 4 | 5 | 00:00 start contrabass. amp: 0.45, preamp: 0.3, freq1: 100*2/3*2/3, flatten: 0.8, lp1: 800, texamp: 0, texvar: 0 6 | 00:00 start water-bell. amp: 0.3, preamp: 0, freq1: 100*8/3, interval: 0, inistrum: 0 7 | 00:00 start resonant-drone. amp: 0.3, preamp: 1, inertia: 0, impact: 0, ofs: 0.09, lp1: 964.2, freq1: 100*1*2/3, freq2: 100*4/3*2/3, freq3: 100*4/3*4/3*2/3 8 | 00:03 tweak contrabass. preamp: 0=0.5, 2=0.7, 4=1 9 | 00:03 start soprano-drone. amp: 1.5, preamp: 1, freq1: 100*4, freq2: 100*8/3, slowness: 0, inertia: 20, hicut: 0 10 | 00:05 tweak water-bell. preamp: 0=0.2, 5=0.4, 10=0.6, 15=0.8, 20=1 11 | 00:08 tweak soprano-drone. hicut: 9 12 | 00:25 tweak contrabass. texamp: 0=0.05, 3=0.1, 6=0.2, 9=0.3, 12=0.4 13 | 00:40 tweak soprano-drone. preamp: 0=0.8, 3=0.6, 6=0.4, 9=0.3, 12=0.2, 15=0.1, 18=0 14 | 15 | 00:40 start soprano-drone #2. amp: 1.5, preamp: 1, freq1: 100*4, freq2: 100*64/3/3, slowness: 0, inertia: 20, hicut: 0 16 | 00:45 tweak contrabass. preamp: 0=0.8, 4=0.6, 8=0.4, 12=0.2 17 | 00:50 tweak soprano-drone #2. hicut: 9 18 | 19 | 01:05 stop soprano-drone. 20 | 01:10 tweak contrabass. preamp: 0=0.4, 6=0.6, 12=0.8, 18=1 21 | 01:25 tweak soprano-drone #2. preamp: 0=0.8, 3=0.6, 6=0.4, 9=0.3, 12=0.2, 15=0.1, 18=0 22 | 01:25 start soprano-drone. amp: 1.5, preamp: 1, freq1: 100*4, freq2: 100*8*3/5, slowness: 0, inertia: 20, hicut: 0 23 | 01:35 tweak soprano-drone. hicut: 9 24 | 25 | 01:50 tweak contrabass. freq1: 100/2 26 | 01:50 tweak resonant-drone. freq1: 100*3/2; freq2: 100; freq3: 100*4/3 27 | 01:52 stop soprano-drone #2. 28 | 01:55 start soprano-drone #2. amp: 1.5, preamp: 1, freq1: 100*16/3, freq2: 100*64/3/5, slowness: 0, inertia: 20, hicut: 0 29 | 02:00 tweak soprano-drone. preamp: 0=0.8, 3=0.6, 6=0.4, 9=0.3, 12=0.2, 15=0.1, 18=0 30 | 02:05 tweak soprano-drone #2. hicut: 9 31 | 02:30 stop soprano-drone. 32 | 02:35 tweak soprano-drone #2. preamp: 0=0.8, 3=0.6, 6=0.4, 9=0.3, 12=0.2, 15=0.1, 18=0 33 | 02:35 start soprano-drone. amp: 1.5, preamp: 1, freq1: 100*4, freq2: 100/2*3*3, slowness: 0, inertia: 20, hicut: 0 34 | 02:45 tweak soprano-drone. hicut: 9 35 | 03:05 stop soprano-drone #2. 36 | 03:15 tweak soprano-drone. preamp: 0=0.8, 3=0.6, 6=0.4, 9=0.3, 12=0.2, 15=0.1, 18=0 37 | 03:20 tweak water-bell. preamp: 0=0.7, 4=0.5, 8=0.25, 12=0.1, 16=0 38 | 03:30 tweak contrabass. preamp: 0=0.7, 4=0.5, 8=0.25, 12=0.1, 16=0 39 | 03:40 tweak resonant-drone. preamp: 0=0.7, 4=0.5, 8=0.25, 12=0.1, 16=0 40 | 03:45 stop soprano-drone. 41 | 42 | 04:00 stop water-bell. 43 | 04:00 stop contrabass. 44 | 04:00 stop resonant-drone. -------------------------------------------------------------------------------- /MusicSource/tracks/07. resonant_cycle.tmac: -------------------------------------------------------------------------------- 1 | # This track showcases the water-bell patch, found in the secret area of 2 | # ambient garden. 3 | # Recorded using the experimental macro recording feature. 4 | 5 | 00:00 start water-bell. preamp: 0.6, freq1: 100*2, interval: 0.58, inistrum: 0 6 | 00:04 start contrabass. preamp: 1, freq1: 100*6, flatten: 0, lp1: 500, texamp: 0.2, texvar: 1 7 | 00:20 tweak contrabass. lp1: 2000 8 | 00:23 start sparse-soprano. amp: 1, preamp: 0.24, freq1: 100*6 9 | 00:30 tweak sparse-soprano. preamp: 0 10 | 11 | ### 1 12 | 00:30 start water-bell #2. amp: 1, preamp: 0.4, freq1: 100, interval: 0.3, inistrum: 0 13 | 00:30 start resonant-drone. preamp: 1, inertia: 0, impact: 1, ofs: 0.11, freq1: 100*1/2, freq2: 100*3/4, freq3: 100*6/4 14 | 00:31 start resonant-drone #2. preamp: 1, inertia: 40, impact: 1, ofs: 0.11, freq1: 100*1/2*2/3, freq2: 100*3/4*2/3, freq3: 100*6/4*2/3 15 | 16 | 00:32 tweak contrabass. lp1: 800 17 | 00:32 tweak water-bell. preamp: 0.3 18 | 00:35 tweak resonant-drone #2. freq1: 100*1/2 19 | 00:35 tweak resonant-drone. preamp: 0.3 20 | 00:35 tweak water-bell #2. preamp: 0=0.8, 2=0.5, 4=0.2 21 | 00:40 stop sparse-soprano. 22 | 00:45 tweak resonant-drone #2. preamp: 0.8 23 | 00:45 tweak water-bell #2. preamp: 0 24 | 25 | 00:52 stop water-bell #2. 26 | 00:52 tweak water-bell. preamp: 0 27 | 00:52 tweak contrabass. lp1: 2000 28 | 00:52 start sparse-soprano. amp: 1, preamp: 0.4, freq1: 100*6 29 | 01:00 tweak resonant-drone. preamp: 0 30 | 01:00 tweak sparse-soprano. preamp: 0 31 | 01:00 tweak contrabass. lp1: 800 32 | 33 | ### 2 34 | 01:00 start water-bell #2. amp: 1, preamp: 0.6, freq1: 100*2, interval: 0.8 35 | 01:01 start water-bell #3. amp: 1, preamp: 0.4, freq1: 100*4, interval: 0.8 36 | 01:02 start wind-chimes. amp: 1, preamp: 1, freq1: 100*16, freq2: 100*16*2/3, tightness: 4.71, lp1: 315.2, density: 1.7 37 | 38 | 01:10 stop resonant-drone. 39 | 01:10 stop sparse-soprano. 40 | 01:10 stop water-bell. 41 | 01:10 tweak resonant-drone #2. inertia: 28; freq2: 100*3/4 42 | 01:15 tweak wind-chimes. tightness: 2.5 43 | 01:15 tweak water-bell #2. preamp: 0 44 | 01:20 tweak wind-chimes. tightness: 1; lp1: 1000; density: 1 45 | 01:30 tweak wind-chimes. tightness: 2.5 46 | 01:40 tweak water-bell #3. preamp: 0 47 | 01:42 tweak wind-chimes. tightness: 4.71; lp1: 315.2; density: 1.7 48 | 01:42 tweak contrabass. lp1: 2000 49 | 01:42 start sparse-soprano. amp: 1, preamp: 0.6, freq1: 100*6 50 | 51 | 01:50 tweak sparse-soprano. preamp: 0 52 | 01:50 tweak contrabass. lp1: 800 53 | 01:51 stop water-bell #2. 54 | 01:51 stop water-bell #3. 55 | 56 | ### 3 57 | 01:52 start resonant-drone. amp: 1, preamp: 1, inertia: 40, impact: 1, ofs: 0.04, freq1: 100*1/2*2/3*4/5, freq2: 100*3/4*2/3*4/5, freq3: 100*6/4*2/3*4/5 58 | 01:52 start contrabass #2. amp: 1, preamp: 1, freq1: 100*1/2*4/5, flatten: 0.5, lp1: 10000, texamp: 0, texvar: 0 59 | 01:52 start water-bell. amp: 1, preamp: 0.9, freq1: 100*2*4/5, interval: 0.6 60 | 01:52 start water-bell #2. amp: 1, preamp: 0.4, freq1: 100*2, interval: 0.3 61 | 01:52 tweak resonant-drone #2. preamp: 0 62 | 01:58 tweak water-bell #2. preamp: 0.3 63 | 02:00 tweak resonant-drone. preamp: 0=0.7, 4=0.6, 7=0.5, 11=0.3, 15=0.2 64 | 02:00 tweak wind-chimes. freq2: 100*16*4/5 65 | 02:00 stop sparse-soprano. 66 | 02:00 tweak water-bell. preamp: 0=0.7, 2=0.5 67 | 02:05 tweak water-bell #2. preamp: 0.15 68 | 02:05 stop resonant-drone #2. 69 | 02:05 start melodic-soprano. preamp: 0.6, freq1: 100*16*4/5, freq2: 100*16, minFreq: 350, maxFreq: 1000, complexity: 0, skip: 4, slowness: 0.3 70 | 02:05 tweak contrabass #2. preamp: 0=0.8, 2=0.5, 4=0.2, 6=0 71 | 02:07 tweak melodic-soprano. slowness: 1 72 | 02:14 tweak water-bell #2. preamp: 0 73 | 02:14 tweak contrabass. lp1: 2000 74 | 02:17 stop contrabass #2. 75 | 02:17 tweak melodic-soprano. preamp: 0 76 | 02:17 start sparse-soprano. amp: 1, preamp: 0.8, freq1: 100*6*4/5 77 | 02:25 tweak resonant-drone. preamp: 0 78 | 02:25 tweak sparse-soprano. preamp: 0 79 | 02:25 tweak water-bell. preamp: 0 80 | 02:25 tweak contrabass. lp1: 800 81 | 02:26 stop melodic-soprano. 82 | 02:26 stop water-bell #2. 83 | 84 | 02:27 start resonant-drone #2. amp: 1, preamp: 1, inertia: 40, impact: 1, ofs: 0.04, lp1: 15000, freq1: 100/5, freq2: 100/2*3/5, freq3: 100*3/5 85 | 02:27 start water-bell #3. amp: 1, preamp: 1, freq1: 100*2*3/5, interval: 0.8 86 | 02:28 start water-bell #2. amp: 1, preamp: 0.8, freq1: 100*4*4/5, interval: 0.8 87 | 02:28 tweak contrabass. freq1: 100*3/5; preamp: 0.4 88 | 89 | 02:29 tweak resonant-drone #2. freq1: 100*2/5 90 | 02:35 stop resonant-drone. 91 | 02:35 stop sparse-soprano. 92 | 02:35 stop water-bell. 93 | 02:35 tweak water-bell #2. preamp: 0=0.7, 2=0.5, 4=0.2, 6=0.1, 8=0 94 | 02:35 tweak water-bell #3. freq1: 100*4*3/5 95 | 96 | 02:50 stop water-bell #2. 97 | 98 | 02:50 start melodic-soprano. amp: 1, preamp: 0.7, freq1: 100*8*3/5, freq2: 100*8*4/5, minFreq: 436.5, maxFreq: 843.6, complexity: 0.31, skip: 5.1, slowness: 0.7 99 | 02:57 tweak melodic-soprano. slowness: 1 100 | 03:03 tweak melodic-soprano. preamp: 0 101 | 102 | 03:03 start sparse-soprano. amp: 1, preamp: 0.7, freq1: 100*8 103 | 03:04 tweak contrabass. preamp: 0=0.3, 4=0.2, 7=0.1, 10=0 104 | 03:08 tweak water-bell #3. preamp: 0=0.7, 2=0.5, 4=0.2, 6=0.1, 8=0 105 | 106 | 03:11 tweak sparse-soprano. preamp: 0 107 | 03:11 stop melodic-soprano. 108 | 109 | ### 3 repeat 110 | 03:15 start resonant-drone. amp: 1, preamp: 1, inertia: 40, impact: 1, ofs: 0.04, freq1: 100*1/2*2/3*4/5, freq2: 100*3/4*2/3*4/5, freq3: 100*6/4*2/3*4/5 111 | 03:15 start contrabass #2. amp: 1, preamp: 1, freq1: 100*1/2*4/5, flatten: 0.5, lp1: 10000, texamp: 0, texvar: 0 112 | 03:15 start water-bell. amp: 1, preamp: 0.9, freq1: 100*2*4/5, interval: 0.6 113 | 03:15 start water-bell #2. amp: 1, preamp: 0.4, freq1: 100*2, interval: 0.3 114 | 03:15 tweak resonant-drone #2. preamp: 0 115 | 03:18 tweak contrabass #2. preamp: 0=0.8, 5=0.7, 10=0.65, 15=0.45, 20=0.34, 25=0.2, 30=0 116 | 03:21 tweak water-bell #2. preamp: 0 117 | 03:23 tweak wind-chimes. freq2: 100*16*4/5 118 | 03:23 stop sparse-soprano. 119 | 03:23 tweak water-bell. preamp: 0=0.7, 2=0.5 120 | 03:25 stop contrabass. 121 | 03:28 stop water-bell #2. 122 | 03:28 stop water-bell #3. 123 | 03:28 stop resonant-drone #2. 124 | 125 | # ending 126 | 127 | 03:32 start water-bell #2. amp: 1, preamp: 1, freq1: 100*4*3/5, interval: 0.6 128 | 03:33 tweak resonant-drone. lp1: 0=4000, 5=1000, 10=600, 15=300, 20=200, 25=100 129 | 03:38 tweak wind-chimes. freq1: 100*64; freq2: 100*64*4/5 130 | 03:38 tweak water-bell. preamp: 0=0.8, 2=0.7, 3=0.65, 4=0.45, 5=0.34, 6=0.2, 7=0 131 | 03:40 tweak wind-chimes. density: 0.7 132 | 03:50 tweak water-bell #2. preamp: 0=0.8, 2=0.6, 4=0.4, 6=0.2, 8=0.1, 10=0.05, 12=0 133 | 03:48 tweak wind-chimes. freq1: 100*32; freq2: 100*32*4/5 134 | 03:55 stop water-bell. 135 | 03:55 tweak wind-chimes. freq1: 100*16; freq2: 100*16*4/5 136 | 03:55 tweak wind-chimes. density: 0 137 | 03:58 tweak resonant-drone. preamp: 0=0.7, 2=0.4, 4=0.2, 8=0.1, 10=0.05, 12=0 138 | 04:18 stop contrabass #2. 139 | 04:18 stop water-bell #2. 140 | 04:18 stop resonant-drone. 141 | 04:18 stop wind-chimes. 142 | 143 | 144 | -------------------------------------------------------------------------------- /MusicSource/tracks/08. passing_tides.tmac: -------------------------------------------------------------------------------- 1 | # This track doesn't have much in common with ambient.garden but makes a good 2 | # conclusion. Inspired by Cubus - https://www.ilovecubus.co.uk/ 3 | # Recorded using the experimental macro recording feature. 4 | 5 | 00:00 start resonant-drone. amp: 1, preamp: 0.4, inertia: 0, impact: 0, ofs: 0.09, freq1: 100*8/5, lp1: 800, freq2: 100*16/5, freq3: 100*4*3/5 6 | 00:00 start wind-theme. amp: 1, preamp: 0.3, freq1: 100*64*3/5/5, freq2: 100*32/5, freeze: 1, lp1: 2000 7 | 00:04 tweak wind-theme. freeze: 0 8 | 00:25 tweak wind-theme. preamp: 0.4; bassiness: 0.15; lp1: 2500 9 | 00:35 tweak wind-theme. preamp: 0.5 10 | 00:35 tweak resonant-drone. preamp: 0.3 11 | 00:40 tweak wind-theme. lp1: 3000 12 | 00:55 tweak wind-theme. preamp: 0.6; bassiness: 0.5 13 | 14 | 01:15 start vocal-overtones. amp: 1, preamp: 0.1, num: 1, freq1: 100*4/5, freq2: 100*2*3/5 15 | 01:15 tweak resonant-drone. preamp: 0.15 16 | 01:25 tweak resonant-drone. preamp: 0 17 | 01:25 tweak wind-theme. lp1: 3500; preamp: 0.5 18 | 01:25 tweak vocal-overtones. preamp: 0=0.2, 5=0.3 19 | 01:34 stop resonant-drone. 20 | 01:34 start rain-stick. preamp: 0.2 21 | 01:40 tweak vocal-overtones. preamp: 0.4 22 | 01:45 tweak wind-theme. lp1: 4000 23 | 01:50 tweak vocal-overtones. preamp: 0.5 24 | 02:00 tweak vocal-overtones. preamp: 0.7 25 | 26 | 02:05 tweak vocal-overtones. preamp: 0.8 27 | 02:05 tweak wind-theme. lp1: 15000 28 | 02:05 tweak rain-stick. pitch: 0.7 29 | 02:06 tweak vocal-overtones. num: 2 30 | 02:34 tweak rain-stick. preamp: 0.4; pitch: 0.8 31 | 02:45 tweak vocal-overtones. preamp: 0.7 32 | 02:50 start vibraphones. amp: 1, preamp: 1, impact: 0, freq1: 100*32/5, freq2: 100*16*3/5 33 | 03:05 tweak vocal-overtones. preamp: 0.6 34 | 03:05 tweak wind-theme. preamp: 0.5 35 | 03:05 tweak rain-stick. pitch: 0.6 36 | 03:15 start vibraphones #2. amp: 1, preamp: 0.5, impact: 0, freq1: 100*8*3/5, freq2: 100*4*3*3/5 37 | 03:15 tweak vibraphones. preamp: 0.6 38 | 03:25 tweak vocal-overtones. preamp: 0.5 39 | 03:25 tweak wind-theme. preamp: 0.4 40 | 03:35 tweak rain-stick. pitch: 0.45 41 | 03:35 tweak vibraphones #2. preamp: 0.4 42 | 03:35 tweak vocal-overtones. preamp: 0.3 43 | 03:45 tweak vocal-overtones. preamp: 0.2 44 | 03:55 tweak vocal-overtones. preamp: 0.1 45 | 03:55 tweak wind-theme. bassiness: 0.75; preamp: 0.3; freeze: 1 46 | 04:05 tweak rain-stick. preamp: 0.2; pitch: 0.4 47 | 04:05 start contrabass. freq1: 100*8/5, preamp: 0.3, lp1: 10000 48 | 04:05 tweak vocal-overtones. preamp: 0 49 | 04:05 tweak vibraphones. preamp: 0.4 50 | 04:10 tweak vibraphones #2. freq2: 100*2*3*3/5 51 | 04:10 tweak vibraphones. preamp: 0=0.3, 5=0.2 52 | 04:15 tweak rain-stick. preamp: 0; amp: 0.9 53 | 54 | 04:20 tweak contrabass. lp1: 0=2000, 4=1100, 8=600, 12=400, 16=300, 20=220, 15=180, 20=100 55 | 04:20 tweak wind-theme. preamp: 0.15 56 | 04:20 tweak rain-stick. amp: 0=0.8, 4=0.55, 8=0.4, 12=0.33, 14=0.2, 18=0.1, 22=0.05, 25=0 57 | 04:25 tweak vibraphones. preamp: 0=0.1, 6=0 58 | 04:26 tweak vibraphones #2. preamp: 0=0.3, 5=0.2, 8=0.1, 13=0 59 | 04:28 tweak contrabass. preamp: 0=0.2, 8=0.1, 17=0 60 | 04:30 tweak wind-theme. preamp: 0.05 61 | 04:32 tweak wind-theme. preamp: 0 62 | 63 | 04:55 stop wind-theme. 64 | 04:55 stop vocal-overtones. 65 | 04:55 stop rain-stick. 66 | 04:55 stop vibraphones. 67 | 04:55 stop vibraphones #2. 68 | 04:55 stop contrabass. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # A Walk through the Ambient Garden 2 | 3 | This is an open-source music album based on [ambient.garden](https://github.com/pac-dev/AmbientGarden), an explorable musical landscape. This album contains the same musical elements as the landscape, but instead of being interactive, it's written as a deterministic composition. Everything is generated from code without using any pre-existing audio samples. 4 | 5 | Musically, I tried to escape sounds commonly associated with generative music. No AI models are used at any point, the composition is algorithmic but deliberate. The album is based on microtonal harmony and just-intonation, played by organic-sounding, physically modeled instruments which are heavily layered. The result is a deep style of ambient, with ethereal and classical influences. 6 | 7 | ## Listen to a pre-built version of the album 8 | 9 | You can hear the album on [Spotify](https://open.spotify.com/album/6RPvBkBjCymWOk7BeONDv4), [Apple Music](https://music.apple.com/us/album/a-walk-through-the-ambient-garden/1732863542), and [Bandcamp](https://purecode.bandcamp.com/album/a-walk-through-the-ambient-garden). Or you can build the album from code, the nerd way. 10 | 11 | ## Build the album 12 | 13 | This repository has no audio files, but contains the full source code to re-create the album. The build script has 2 requirements: 14 | 15 | - Deno, to run JS code. You can download it as a [single binary](https://github.com/denoland/deno/releases) or [install it properly](https://docs.deno.com/runtime/manual) if you prefer. 16 | - [FFmpeg.](https://ffmpeg.org/download.html), for audio encoding. 17 | 18 | To build the album, download or clone this repository, then run the following command in a terminal: 19 | 20 | deno run -A render.js album 21 | 22 | By default, this will create the album as mp3 files in the `Generated/final/` directory. Note that this project does not really focus on efficiency (wouldn't be using Javascript otherwise!), the generation process needs about 1.5GB of RAM and takes 10+ minutes on a typical laptop. To customize album rendering, see the reference section below. 23 | 24 | ## Composition and development environment 25 | 26 | The music generation code was mostly written using the [Teasynth](https://github.com/pac-dev/Teasynth) web editor, which has features to play and debug music code, inline help for Faust library functions, etc. The MusicSource folder can be opened as a Teasynth project. 27 | 28 | If you want to understand how any specific sound is made, start with the "macro" files in `MusicSource/tracks/`. These contain timestamped commands to start, stop and control "patches" within a track. The source for all these patches can be found in `MusicSource/patches/`. Javascript is used for high level composition code, while [Faust](https://faust.grame.fr/) is used for lower level audio-rate code. I recommend some familiarity with Faust before getting into `.dsp` files. 29 | 30 | ## Command reference 31 | 32 | ``` 33 | SUBCOMMAND: ALBUM 34 | ----------------- 35 | Render the entire album, including cover art, to Generated/final/ 36 | Usage: deno run -A render.js album [--format=fmt] [--partial] [--ffargs ...] 37 | Arguments: 38 | --format=fmt final audio format (wav, mp3, flac...) 39 | --partial keep existing rendered tracks 40 | --ffargs ... all arguments after this will be passed to ffmpeg 41 | Example 1: deno run -A render.js album 42 | Uses default settings, rendering the album as 290k mp3. 43 | Example 2: deno run -A render.js album --format=mp3 --ffargs -b:a 200k 44 | Renders the album at a bitrate of 200k. 45 | 46 | SUBCOMMAND: TRACK 47 | ----------------- 48 | Render a single track to a wav file. Track names are in MusicSource/tracks/ 49 | Usage: deno run -A render.js track 50 | Example: deno run -A render.js track "06. occultation" test.wav 51 | Renders the 6th track to test.wav 52 | 53 | SUBCOMMAND: MASTER-TRACK 54 | ------------------------ 55 | Master a single audio file, applying dynamics compression. 56 | Usage: deno run -A render.js master-track 57 | 58 | SUBCOMMAND: OVERLAP-ALBUM 59 | ------------------------- 60 | Add overlap between separately rendered audio tracks. 61 | Usage: deno run -A render.js overlap 62 | Example: deno run -A render.js overlap stage2/ stage3/ stage4/ 63 | 64 | SUBCOMMAND: ENCODE 65 | ------------------ 66 | Encode and add metadata to rendered tracks. 67 | Usage: deno run -A render.js encode [--format=fmt] [--ffargs ...] 68 | Arguments: 69 | --format=fmt final audio format (wav, mp3, flac...) 70 | --ffargs ... all arguments after this will be passed to ffmpeg 71 | Example: deno run -A render.js encode stage4/ final/ --ffargs -b:a 200k 72 | Encodes the album at a bitrate of 200k. 73 | 74 | SUBCOMMAND: COVER 75 | ----------------- 76 | Generate the cover art to a png file. 77 | Usage: deno run -A render.js cover [--scale=N] 78 | Example: deno run -A render.js cover test.png --scale=2 79 | Generates the cover to test.png at 2x size. 80 | ``` 81 | -------------------------------------------------------------------------------- /Scripts/CourierPrime-Subset.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pac-dev/AmbientGardenAlbum/e20da59004cb22f7d728f232623dee6d3f588e43/Scripts/CourierPrime-Subset.ttf -------------------------------------------------------------------------------- /Scripts/cover-preview.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 |
.
17 | 23 | -------------------------------------------------------------------------------- /Scripts/cover.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate album art for the ambient.garden album. Quick rundown: 3 | * 4 | * - This module doesn't import anything, it gets passed a canvas context object 5 | * and draws to it. 6 | * - For the CLI version, the canvas is provided by deno-canvas, which is 7 | * conveniently self-contained and consistent across platforms. 8 | * - A browser canvas also works. This was just used for development. 9 | * - A trimmed-down font is included to keep things reproducible. 10 | * - Most of the cover is drawn using fillText('█', ...), which originally made 11 | * sense because I wanted more ASCII art in the image. I mostly dropped the 12 | * ASCII art idea but kept the nonsensical drawing method. 13 | */ 14 | 15 | const prose = `┏━━━━━━━━━━━━━━━━ pac@baraka - Terminal ━━━━━━━━━━━ 🗕 🗗 🗙 ━┓ 16 | ┃ ┃ 17 | ┃ Mastering tracks to "./Generated/stage3"... ┃ 18 | ┃ Overlapping tracks to "./Generated/stage4"... ┃ 19 | ┃ Encoding tracks to "./Generated/final"... ┃ 20 | ┃ Adding metadata: { ┃ 21 | ┃ "artist": "Pure Code", ┃ 22 | ┃ "album": "A Walk Though the Ambient Garden", ┃ 23 | ┃ "date": "20240301" ┃ 24 | ┃ } ┃ 25 | ┃ Done building album. ┃ 26 | ┃ pac@baraka:~$ ▌ ┃ 27 | ┃ ┃ 28 | ┃ ⯅ 29 | ┃ ┃ 30 | ┃ ⯆ 31 | ┃ ┃ 32 | ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛`; 33 | 34 | /** 35 | * Returns a PRNG function roughly similar to Math.random. 36 | * Source: Tommy Ettinger. 37 | */ 38 | const randomSeed = (seed=0) => () => { 39 | seed = seed + 1831565813|0; 40 | let t = Math.imul(seed^seed>>>15, 1|seed); 41 | t = t+Math.imul(t^t>>>7, 61|t)^t; 42 | return ((t^t>>>14)>>>0)/2**32; 43 | }; 44 | 45 | // Uncomment to test different seeds: 46 | // const seed = Math.round(Math.random()*100) 47 | // console.log(seed) 48 | // const rand = randomSeed(seed); 49 | const rand = randomSeed(19); 50 | 51 | /** 52 | * Conversion between RGB and HSL colors with all values between 0 and 1. 53 | * Source: Kamil Kiełczewski. 54 | */ 55 | const hsl2rgb = (h,s,l) => { 56 | const a=s*Math.min(l,1-l); 57 | const f= (n,k=(n+h*12)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1); 58 | return [f(0),f(8),f(4)]; 59 | }; 60 | const rgb2hsl = (r,g,b) => { 61 | const v=Math.max(r,g,b), c=v-Math.min(r,g,b), f=(1-Math.abs(v+v-c-1)); 62 | const h= c && ((v==r) ? (g-b)/c : ((v==g) ? 2+(b-r)/c : 4+(r-g)/c)); 63 | return [(h<0?h+6:h)/6, f ? c/f : 0, (v+v-c)/2]; 64 | }; 65 | const offsetHsl = ([r,g,b],x,y,z) => { 66 | const ret = rgb2hsl(r,g,b); 67 | return hsl2rgb((ret[0]+x+90)%1,(ret[1]+y+90)%1,(ret[2]+z+90)%1); 68 | }; 69 | const formatColor = ([r, g, b]) => `rgb(${r}, ${g}, ${b})`; 70 | const clamp = (x,a,b) => Math.max(a,Math.min(b,x)); 71 | 72 | export const pxWidth = 900, pxHeight = 900; 73 | const rowChars = 87, colChars = 54; 74 | const charWidth = 10, charHeight = 16; 75 | const winWidth = prose.indexOf('\n')-3; 76 | const winHeight = prose.match(/\n/g).length+1; 77 | const winX = Math.floor(rowChars*0.5 - winWidth*0.5) - 1; 78 | const winY = Math.floor(colChars*0.5 - winHeight*0.5); 79 | const winBg = [0.2, 0.2, 0.2]; 80 | const charToPixel = (x,y) => [(x+1.2)*(charWidth), (y+1.95)*(charHeight)]; 81 | const bgColors = Array(rowChars * colChars * 3); 82 | const getBgColor = (x, y) => bgColors.slice(x*3+y*3*rowChars, x*3+y*3*rowChars+3); 83 | const setBgColor = (x, y, col) => { 84 | bgColors[x*3+y*3*rowChars] = col[0]; 85 | bgColors[x*3+y*3*rowChars+1] = col[1]; 86 | bgColors[x*3+y*3*rowChars+2] = col[2]; 87 | }; 88 | const mixColors = (col1, col2, x) => [0,1,2].map( 89 | i => Math.round(col1[i]*(1 - x) + col2[i]*x) 90 | ); 91 | const mixStops = (stop1, stop2, pos) => { 92 | if (stop1[0] === stop2[0]) return stop1[1]; 93 | const mixPos = (pos - stop1[0]) / (stop2[0] - stop1[0]); 94 | return mixColors(stop1[1], stop2[1], mixPos); 95 | }; 96 | const randomize = ([r,g,b], amt) => { 97 | const ret = offsetHsl([r/256,g/256,b/256], rand()*amt, rand()*amt, rand()*amt*0.5); 98 | return [Math.round(ret[0]*256), Math.round(ret[1]*256), Math.round(ret[2]*256)]; 99 | }; 100 | const sunRays = (x,y) => { 101 | y = y/colChars - 0.5; 102 | x = 0.6*Math.abs(x/rowChars - 0.25)/y; 103 | const shadow = x => clamp(Math.abs(x - clamp(Math.round(x),0,4))*20-4, 0, 1); 104 | return 0.8+(1-y*2)*0.2+y*2*shadow(x)*0.2; 105 | }; 106 | const calcBg = () => { 107 | for (let x=0; x 1-1*Math.sin((0.06*x+o)%Math.PI); 109 | const mount = -0.7*mountFn(x,3)*mountFn(x,3)-0.5*mountFn(x*3.3,2); 110 | const snow = mixColors([175,180,180], [137,124,134], clamp((mount+0.5)*10, 0, 1)); 111 | const gradStops = [ 112 | [0, [162,88,110]], 113 | [0.35+mount*0.08, [226,151,122]], 114 | [0.45+mount*0.15, snow], 115 | [0.47+mount*0.05, [137*0.9,124*0.9,134*0.9]], 116 | [0.50, [137*0.8,124*0.8,134*0.8]], 117 | [0.54, [93,112,83]], 118 | [1, [213,228,106]], 119 | ]; 120 | for (let y=0; y s[0] <= yprop); 123 | const stop2 = gradStops.find(s => s[0] >= yprop); 124 | let col = mixStops(stop1, stop2, yprop); 125 | col = randomize(col, 0.1); 126 | setBgColor(x, y, mixColors([0,0,0], col, sunRays(x,y))); 127 | } 128 | } 129 | for (let y=0; y 400*Math.pow(orig[i]/255, 8)%250), Math.pow(cloudAmt, 10)*0.7); 140 | setBgColor(x, y, mixColors(orig, randomize(run, 0.06), amt)); 141 | } 142 | } 143 | }; 144 | calcBg(); 145 | 146 | export const drawCover = (ctx) => { 147 | ctx.fillStyle = '#444'; 148 | ctx.fillRect(0, 0, pxWidth, pxHeight); 149 | ctx.font = `${charHeight}px Courier Prime Regular`; 150 | for (let x=0; x=winX && x=winY && y=winX+4 && x=winY+2 && y { 194 | const canvas = document.querySelector('canvas'); 195 | const scale = window.devicePixelRatio; 196 | canvas.width = pxWidth; 197 | canvas.height = pxHeight; 198 | canvas.style.width = (canvas.width / scale) + 'px'; 199 | canvas.style.height = (canvas.height / scale) + 'px'; 200 | const ctx = canvas.getContext('2d'); 201 | return ctx; 202 | }; -------------------------------------------------------------------------------- /Scripts/deps.js: -------------------------------------------------------------------------------- 1 | // Switch between local copy and published version of Teasynth here: 2 | export { renderMacro } from 'https://raw.githubusercontent.com/pac-dev/Teasynth/v1.0.1/teasynth.js'; 3 | // export { renderMacro } from '../../Teasynth/teasynth.js'; 4 | 5 | export { createCanvas } from 'https://deno.land/x/canvas@v1.4.1/mod.ts'; 6 | export { existsSync } from 'https://deno.land/std@0.217.0/fs/mod.ts'; 7 | export * as path from 'https://deno.land/std@0.217.0/path/mod.ts'; 8 | export { parse } from 'https://deno.land/std@0.217.0/flags/mod.ts'; 9 | -------------------------------------------------------------------------------- /Scripts/mastering.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Really basic audio mastering functions to apply dynamics compression to a track. 3 | */ 4 | 5 | import { path } from './deps.js'; 6 | 7 | const maxLen = 60 * 8; 8 | const envRate = 0.2; 9 | const response = (x) => 1-Math.pow(1-x,2); 10 | const env2gain = (x) => (x < 0.00001) ? 2 : response(x) / x; 11 | const outBufSamples = 1024; 12 | 13 | class Compressor { 14 | constructor() { 15 | this.envelope = new Float32Array(maxLen * envRate); 16 | } 17 | feedInput(fileIdx, sample) { 18 | const envIdx = Math.floor(((fileIdx / 2) / 44100) * envRate); 19 | this.envelope[envIdx] = Math.max(this.envelope[envIdx], Math.abs(sample)); 20 | } 21 | process() { 22 | this.gains = this.envelope.map(env2gain); 23 | } 24 | getGain(fileIdx) { 25 | let envPos = ((fileIdx / 2) / 44100) * envRate; 26 | envPos = Math.max(0, envPos - 0.5); 27 | const env0 = Math.floor(envPos); 28 | const env1 = env0 + 1; 29 | const fract = envPos % 1; 30 | return this.gains[env0]*(1-fract) + this.gains[env1]*fract; 31 | } 32 | } 33 | 34 | class InTrack { 35 | constructor(path) { 36 | const command = new Deno.Command('ffmpeg', { 37 | args: ['-i', path, '-f', 'f32le', '-channels', '2', '-ar', '44100', '-'], 38 | stdout: 'piped', stderr: globalThis.verbose ? 'inherit' : 'null' 39 | }); 40 | const proc = command.spawn(); 41 | this.reader = proc.stdout.getReader(); 42 | } 43 | async *getSamples() { 44 | let idx = 0; 45 | while (true) { 46 | const readResp = await this.reader.read(); 47 | if (readResp.done) break; 48 | const rVal = readResp.value; 49 | const nSamples = rVal.length / 4; 50 | const view = new Float32Array(rVal.buffer, 0, nSamples); 51 | for (const sample of view) { 52 | yield [idx, sample]; 53 | idx++; 54 | } 55 | } 56 | } 57 | } 58 | 59 | class OutTrack { 60 | constructor(path) { 61 | const ext = path.split('.').at(-1); 62 | const args = ['-y', '-f', 'f32le', '-channels', '2', '-ar', '44100', '-i', 'pipe:0']; 63 | if (ext === 'mp3') args.push('-b:a', '192k'); 64 | args.push(path); 65 | const command = new Deno.Command('ffmpeg', { 66 | args, stdin: 'piped', stderr: globalThis.verbose ? 'inherit' : 'null' 67 | }); 68 | this.proc = command.spawn(); 69 | this.writer = this.proc.stdin.getWriter(); 70 | this.outBuf = new Float32Array(outBufSamples); 71 | this.outView = new Uint8Array(this.outBuf.buffer); 72 | this.outPos = 0; 73 | } 74 | async pushSample(sample) { 75 | this.outBuf[this.outPos] = sample; 76 | this.outPos++; 77 | if (this.outPos >= outBufSamples) { 78 | await this.writer.write(this.outView); 79 | this.outPos = 0; 80 | } 81 | } 82 | async close() { 83 | // consider writing the remaining samples here 84 | await this.writer.close(); 85 | } 86 | } 87 | 88 | export const masterTrack = async (inPath, outPath) => { 89 | console.log(`Mastering ${path.basename(inPath)}...`); 90 | Deno.mkdirSync(path.dirname(outPath), { recursive: true }); 91 | const comp = new Compressor(); 92 | const in1 = new InTrack(inPath); 93 | for await (const [idx, sample] of in1.getSamples()) { 94 | comp.feedInput(idx, sample); 95 | } 96 | comp.process(); 97 | const in2 = new InTrack(inPath); 98 | const out = new OutTrack(outPath); 99 | for await (const [idx, sample] of in2.getSamples()) { 100 | const outSample = sample * comp.getGain(idx); 101 | await out.pushSample(outSample); 102 | } 103 | await out.close(); 104 | }; -------------------------------------------------------------------------------- /Scripts/overlap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functions for overlapping tracks together. The goal is to take out the end of 3 | * each track, and mix it into the beginning of the next track. 4 | * 5 | * For example, to overlap track_1 and track_2, the script first determines the 6 | * length of track_1 and subtracts the transition time from that length. 7 | * Assuming the resulting length is 42 seconds, the script first executes this 8 | * command to mix one end into another start: 9 | * 10 | * ffmpeg -ss 42 -i track_1_in.wav -i track_2_in.wav -filter_complex \ 11 | * amix=inputs=2:duration=longest:dropout_transition=999999,volume=2 \ 12 | * track_2_out.wav 13 | * 14 | * Then executes this to cut off the end, which is now in the next track: 15 | * 16 | * ffmpeg -i track_1_in.wav -t 42 track_1_out.wav 17 | * 18 | * "999999" is a hack to disable ffmpeg's mixing normalization. This could be 19 | * avoided by using the new normalize=false instead of dropout_transition and 20 | * volume, but I don't mind a small hack to support older versions of ffmpeg 21 | * that are still widespread. 22 | */ 23 | 24 | import { path } from './deps.js'; 25 | 26 | const getDuration = async (inPath) => { 27 | const command = new Deno.Command('ffmpeg', { args: ['-i', inPath], stderr: 'piped' }); 28 | const { stderr } = await command.output(); 29 | const errText = new TextDecoder().decode(stderr); 30 | const durationRegex = /Duration: (?
\d\d):(?\d\d):(?\d\d\.\d\d)/; 31 | const match = durationRegex.exec(errText); 32 | if (!match) throw new Error(`Could not extract duration from ${inPath}.\nffmpeg output:\n` + errText); 33 | return parseFloat(match.groups['hr'])*60*60 + parseFloat(match.groups['min'])*60 + parseFloat(match.groups['sec']); 34 | }; 35 | 36 | const mixTail = async (inPath1, inPath2, outPath, delay1) => { 37 | console.log(`mixing end of ${path.basename(inPath1)} into beginning of ${path.basename(inPath2)}`) 38 | const command = new Deno.Command('ffmpeg', { args: [ 39 | '-ss', delay1, '-i', inPath1, '-i', inPath2, 40 | '-filter_complex', 'amix=inputs=2:duration=longest:dropout_transition=999999,volume=2', 41 | outPath 42 | ], stderr: globalThis.verbose ? 'inherit' : 'null' }); 43 | const { code } = await command.output(); 44 | if (code) throw new Error(`ffmpeg error overlapping tracks: ${inPath1} + ${inPath2} -> ${outPath}`); 45 | }; 46 | 47 | const trimTail = async (inPath, outPath, length) => { 48 | console.log(`trimming end of ${path.basename(inPath)}`) 49 | const command = new Deno.Command('ffmpeg', { args: [ 50 | '-i', inPath, '-t', length, outPath 51 | ], stderr: globalThis.verbose ? 'inherit' : 'null' }); 52 | const { code } = await command.output(); 53 | if (code) throw new Error(`ffmpeg error trimming track: ${inPath} -> ${outPath}`); 54 | }; 55 | 56 | const sep = path.SEPARATOR; 57 | 58 | export const overlapAlbum = async (inDir, stageDir, outDir, overlap=8) => { 59 | if (!inDir.endsWith(sep)) inDir += sep; 60 | if (!stageDir.endsWith(sep)) stageDir += sep; 61 | if (!outDir.endsWith(sep)) outDir += sep; 62 | Deno.mkdirSync(stageDir, { recursive: true }); 63 | Deno.mkdirSync(outDir, { recursive: true }); 64 | const inNames = [...Deno.readDirSync(inDir)].filter(f => f.name.endsWith('.wav')).map(f => f.name); 65 | const lengths = []; 66 | for (const name of inNames) lengths.push((await getDuration(inDir + name)) - overlap); 67 | for (const [idx, name] of inNames.entries()) { 68 | if (idx) await mixTail(inDir + inNames[idx-1], inDir + name, stageDir + name, lengths[idx-1]); 69 | else Deno.copyFileSync(inDir + name, stageDir + name); 70 | } 71 | const stageNames = [...Deno.readDirSync(stageDir)].filter(f => f.name.endsWith('.wav')).map(f => f.name); 72 | for (const [idx, name] of stageNames.entries()) { 73 | const next = stageNames[idx+1]; 74 | if (next) await trimTail(stageDir + name, outDir + name, lengths[idx]); 75 | else Deno.copyFileSync(stageDir + name, outDir + name); 76 | } 77 | }; -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmt": { 3 | "useTabs": true, 4 | "singleQuote": true 5 | }, 6 | "lock": false 7 | } -------------------------------------------------------------------------------- /render.js: -------------------------------------------------------------------------------- 1 | import { parse, path, existsSync, renderMacro, createCanvas } from './Scripts/deps.js'; 2 | import { masterTrack } from './Scripts/mastering.js'; 3 | import { overlapAlbum } from './Scripts/overlap.js'; 4 | import { drawCover, pxWidth, pxHeight } from './Scripts/cover.js'; 5 | 6 | const helpText = ` 7 | Script to render the ambient.garden album. Requires deno and ffmpeg. 8 | All subcommands can take an optional --verbose flag, which mainly shows 9 | ffmpeg output. 10 | 11 | SUBCOMMAND: ALBUM 12 | ----------------- 13 | Render the entire album, including cover art, to Generated/final/ 14 | Usage: deno run -A render.js album [--format=fmt] [--partial] [--ffargs ...] 15 | Arguments: 16 | --format=fmt final audio format (wav, mp3, flac...) 17 | --partial keep existing rendered tracks 18 | --ffargs ... all arguments after this will be passed to ffmpeg 19 | Example 1: deno run -A render.js album 20 | Uses default settings, rendering the album as 290k mp3. 21 | Example 2: deno run -A render.js album --format=mp3 --ffargs -b:a 200k 22 | Renders the album at a bitrate of 200k. 23 | 24 | SUBCOMMAND: TRACK 25 | ----------------- 26 | Render a single track to a wav file. Track names are in MusicSource/tracks/ 27 | Usage: deno run -A render.js track 28 | Example: deno run -A render.js track "06. occultation" test.wav 29 | Renders the 6th track to test.wav 30 | 31 | SUBCOMMAND: MASTER-TRACK 32 | ------------------------ 33 | Master a single audio file, applying dynamics compression. 34 | Usage: deno run -A render.js master-track 35 | 36 | SUBCOMMAND: OVERLAP-ALBUM 37 | ------------------------- 38 | Add overlap between separately rendered audio tracks. 39 | Usage: deno run -A render.js overlap 40 | Example: deno run -A render.js overlap stage2/ stage3/ stage4/ 41 | 42 | SUBCOMMAND: ENCODE 43 | ------------------ 44 | Encode and add metadata to rendered tracks. 45 | Usage: deno run -A render.js encode [--format=fmt] [--ffargs ...] 46 | Arguments: 47 | --format=fmt final audio format (wav, mp3, flac...) 48 | --ffargs ... all arguments after this will be passed to ffmpeg 49 | Example: deno run -A render.js encode stage4/ final/ --ffargs -b:a 200k 50 | Encodes the album at a bitrate of 200k. 51 | 52 | SUBCOMMAND: COVER 53 | ----------------- 54 | Generate the cover art to a png file. 55 | Usage: deno run -A render.js cover [--scale=N] 56 | Example: deno run -A render.js cover test.png --scale=2 57 | Generates the cover to test.png at 2x size. 58 | `; 59 | 60 | const albumMetadata = { 61 | 'artist': 'Pure Code', 62 | 'album': 'A Walk Though the Ambient Garden', 63 | 'date': '20240301' 64 | }; 65 | 66 | const helpAndExit = () => { 67 | console.log(helpText); 68 | Deno.exit(); 69 | }; 70 | 71 | const moduleDir = path.dirname(path.fromFileUrl(import.meta.url)); 72 | const patchDir = path.join(moduleDir, 'MusicSource', 'patches'); 73 | const macroDir = path.join(moduleDir, 'MusicSource', 'tracks'); 74 | const stage1Dir = path.join(moduleDir, 'Generated', 'stage1'); // rendered 75 | const stage2Dir = path.join(moduleDir, 'Generated', 'stage2'); // mastered 76 | const stage3Dir = path.join(moduleDir, 'Generated', 'stage3'); // overlap start 77 | const stage4Dir = path.join(moduleDir, 'Generated', 'stage4'); // overlap end 78 | const finalDir = path.join(moduleDir, 'Generated', 'final'); 79 | 80 | const renderTrack = async (trackName, destPath) => { 81 | const trackPath = path.join(macroDir, trackName+'.tmac'); 82 | Deno.mkdirSync(path.dirname(destPath), { recursive: true }); 83 | await renderMacro(patchDir, trackPath, destPath); 84 | return destPath; 85 | }; 86 | 87 | const encodeTrack = async (inPath, outPath, metadata, ffargs) => { 88 | console.log(`Encoding ${path.basename(inPath)}...`); 89 | const metaList = Object.entries(metadata) 90 | .map(([key, val]) => ['-metadata', `${key}=${val}`]) 91 | .flat(); 92 | // console.dir(['-i', inPath, ...ffargs, ...metaList, outPath]); 93 | const command = new Deno.Command('ffmpeg', { args: [ 94 | '-i', inPath, ...ffargs, ...metaList, outPath 95 | ], stderr: globalThis.verbose ? 'inherit' : 'null' }); 96 | const { code } = await command.output(); 97 | if (code) throw new Error(`ffmpeg error encoding track: ${inPath} -> ${outPath}`); 98 | }; 99 | 100 | const encodeAlbum = async (inDir, outDir, format='mp3', ffargs=[], cover='') => { 101 | Deno.mkdirSync(outDir, { recursive: true }); 102 | const coverArgs = cover ? [ 103 | '-i', cover, '-map', '0:0', '-map', '1:0', 104 | '-metadata:s:v','title="Album cover"', '-metadata:s:v', 'comment="Cover (front)"' 105 | ] : []; 106 | ffargs = [...coverArgs, ...ffargs]; 107 | const fileNames = [...Deno.readDirSync(inDir)].map(f => f.name); 108 | for (const [idx, fileName] of fileNames.entries()) { 109 | const title = fileName.replace(/\.wav$/, '').replace(/^\d+\.\s?/, ''); 110 | const inPath = path.join(inDir, fileName); 111 | const outPath = path.join(outDir, fileName.replace(/wav$/, format)); 112 | const metaMap = Object.assign({ title, track: idx+1 }, albumMetadata); 113 | await encodeTrack(inPath, outPath, metaMap, ffargs); 114 | } 115 | }; 116 | 117 | const renderAlbum = async (format='mp3', ffargs=['-b:a', '290k'], partial=false) => { 118 | const toDelete = partial ? [] : [stage1Dir, stage2Dir]; 119 | toDelete.push(stage3Dir, stage4Dir, finalDir); 120 | console.log('The following intermediate directories will be deleted if they exist:\n' 121 | + toDelete.join('\n') 122 | ); 123 | const answer = prompt('Delete? (y/n)'); 124 | if (! 'yY'.includes(answer)) Deno.exit(); 125 | toDelete.filter(existsSync).forEach(d => Deno.removeSync(d, { recursive: true })); 126 | const trackNames = [...Deno.readDirSync(macroDir)] 127 | .filter(f => f.name.endsWith('.tmac')) 128 | .map(f => f.name.slice(0, -5)); 129 | console.log(`\nRendering tracks to "${stage1Dir}"...`); 130 | for (const name of trackNames) { 131 | const outPath = path.join(stage1Dir, name+'.wav'); 132 | if (partial && existsSync(outPath)) { 133 | console.log('skipping existing: '+name); 134 | continue; 135 | } 136 | await renderTrack(name, outPath); 137 | } 138 | console.log(`\nMastering tracks to "${stage2Dir}"...`); 139 | for (const name of trackNames) { 140 | const outPath = path.join(stage2Dir, name+'.wav'); 141 | if (partial && existsSync(outPath)) { 142 | console.log('skipping existing: '+name); 143 | continue; 144 | } 145 | await masterTrack(path.join(stage1Dir, name+'.wav'), outPath); 146 | } 147 | console.log(`\nOverlapping tracks to "${stage4Dir}"...`); 148 | await overlapAlbum(stage2Dir, stage3Dir, stage4Dir); 149 | console.log(`\nRendering cover art to "${finalDir}"...`); 150 | const coverPath = path.join(finalDir, 'cover.png'); 151 | await renderCover(coverPath); 152 | console.log(`\nEncoding tracks to "${finalDir}"...`); 153 | console.log(`\nAdding metadata: "${JSON.stringify(albumMetadata, null, ' ')}"`); 154 | await encodeAlbum(stage4Dir, finalDir, format, ffargs, coverPath); 155 | console.log(`\nDone.`); 156 | }; 157 | 158 | const renderCover = async (outPath, scale=1) => { 159 | Deno.mkdirSync(path.dirname(outPath), { recursive: true }); 160 | const canvas = createCanvas(pxWidth*scale, pxHeight*scale); 161 | const font = await Deno.readFile('Scripts/CourierPrime-Subset.ttf'); 162 | canvas.loadFont(font, { family: 'Courier Prime Regular' }); 163 | const ctx = canvas.getContext('2d'); 164 | ctx.scale(scale,scale); 165 | drawCover(ctx); 166 | await Deno.writeFile(outPath, canvas.toBuffer()); 167 | }; 168 | 169 | const runCommandLine = async () => { 170 | const ffstart = Deno.args.indexOf('--ffargs'); 171 | const ffargs = (ffstart === -1) ? [] : Deno.args.slice(ffstart+1); 172 | const args = parse(Deno.args); 173 | const format = args['format'] ?? 'mp3'; 174 | globalThis.verbose = args['verbose']; 175 | const partial = args['partial']; 176 | switch (args._[0]) { 177 | case 'album': 178 | return await renderAlbum(format, ffargs, partial); 179 | case 'track': 180 | return await renderTrack(args._[1], args._[2]); 181 | case 'master-track': 182 | return await masterTrack(args._[1], args._[2]); 183 | case 'overlap-album': 184 | return await overlapTracks(args._[1], args._[2], args._[3]); 185 | case 'cover': { 186 | const scale = args['scale'] ?? 1; 187 | return await renderCover(args._[1], scale); 188 | } case 'encode': 189 | default: 190 | return helpAndExit(); 191 | } 192 | }; 193 | 194 | if (import.meta.main) await runCommandLine(); --------------------------------------------------------------------------------