Merge pull request #138 from tidalcycles/webaudio-rewrite
Webaudio rewrite
This commit is contained in:
commit
ad053361dc
15 changed files with 273 additions and 106 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
|
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i",
|
"setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i",
|
||||||
|
"snapshot": "cd repl && npm run snapshot",
|
||||||
"repl": "cd repl && npm run dev",
|
"repl": "cd repl && npm run dev",
|
||||||
"osc": "cd packages/osc && npm run server",
|
"osc": "cd packages/osc && npm run server",
|
||||||
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
|
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
|
||||||
|
|
|
||||||
|
|
@ -750,19 +750,22 @@ const generic_params = [
|
||||||
|
|
||||||
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
||||||
|
|
||||||
const _setter = (func) =>
|
const _setter = (func, name) =>
|
||||||
function (...pats) {
|
function (...pats) {
|
||||||
|
if (!pats.length) {
|
||||||
|
return this.fmap((value) => ({ [name]: value }));
|
||||||
|
}
|
||||||
return this.set(func(...pats));
|
return this.set(func(...pats));
|
||||||
};
|
};
|
||||||
|
|
||||||
generic_params.forEach(([type, name, description]) => {
|
generic_params.forEach(([type, name, description]) => {
|
||||||
controls[name] = (...pats) => _name(name, ...pats);
|
controls[name] = (...pats) => _name(name, ...pats);
|
||||||
Pattern.prototype[name] = _setter(controls[name]);
|
Pattern.prototype[name] = _setter(controls[name], name);
|
||||||
});
|
});
|
||||||
|
|
||||||
// create custom param
|
// create custom param
|
||||||
controls.createParam = (name) => {
|
controls.createParam = (name) => {
|
||||||
Pattern.prototype[name] = _setter(controls[name]);
|
Pattern.prototype[name] = _setter(controls[name], name);
|
||||||
return (...pats) => _name(name, ...pats);
|
return (...pats) => _name(name, ...pats);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||||
|
|
||||||
// this is a shortcut to eval code from a gist
|
// this is a shortcut to eval code from a gist
|
||||||
// why? to be able to shorten strudel code + e.g. be able to change instruments after links have been generated
|
// why? to be able to shorten strudel code + e.g. be able to change instruments after links have been generated
|
||||||
export default (route) =>
|
export default (route, cache = true) =>
|
||||||
fetch(`https://gist.githubusercontent.com/${route}?cachebust=${Date.now()}`)
|
fetch(`https://gist.githubusercontent.com/${route}?cachebust=${cache ? '' : Date.now()}`)
|
||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
.then((code) => eval(code));
|
.then((code) => eval(code));
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,9 @@ export class Hap {
|
||||||
}
|
}
|
||||||
|
|
||||||
showWhole() {
|
showWhole() {
|
||||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
return `${this.whole == undefined ? '~' : this.whole.show()}: ${
|
||||||
}
|
typeof this.value === 'object' ? JSON.stringify(this.value) : this.value
|
||||||
|
}`;
|
||||||
showWhole() {
|
|
||||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
combineContext(b) {
|
combineContext(b) {
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,5 @@ export * from './state.mjs';
|
||||||
export * from './timespan.mjs';
|
export * from './timespan.mjs';
|
||||||
export * from './util.mjs';
|
export * from './util.mjs';
|
||||||
export * from './speak.mjs';
|
export * from './speak.mjs';
|
||||||
export * as gist from './gist.js';
|
export { default as gist } from './gist.js';
|
||||||
// export * from './value.mjs';
|
// export * from './value.mjs';
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||||
export * from './clockworker.mjs';
|
export * from './clockworker.mjs';
|
||||||
export * from './scheduler.mjs';
|
export * from './scheduler.mjs';
|
||||||
export * from './webaudio.mjs';
|
export * from './webaudio.mjs';
|
||||||
|
export * from './sampler.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||||
|
|
||||||
|
export const getCachedBuffer = (url) => bufferCache[url];
|
||||||
|
|
||||||
export const loadBuffer = (url, ac) => {
|
export const loadBuffer = (url, ac) => {
|
||||||
if (!loadCache[url]) {
|
if (!loadCache[url]) {
|
||||||
loadCache[url] = fetch(url)
|
loadCache[url] = fetch(url)
|
||||||
|
|
@ -91,7 +93,7 @@ export const loadGithubSamples = async (path, nameFn) => {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const samples = (sampleMap, baseUrl = '') => {
|
export const samples = (sampleMap, baseUrl = sampleMap._base) => {
|
||||||
sampleCache.current = {
|
sampleCache.current = {
|
||||||
...sampleCache.current,
|
...sampleCache.current,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
|
|
@ -6,8 +6,11 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||||
|
|
||||||
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
|
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
import * as strudel from '@strudel.cycles/core';
|
||||||
import { Tone } from '@strudel.cycles/tone';
|
import { fromMidi } from '@strudel.cycles/core';
|
||||||
const { Pattern, getFrequency, patternify2 } = strudel;
|
import { loadBuffer } from './sampler.mjs';
|
||||||
|
const { Pattern } = strudel;
|
||||||
|
|
||||||
|
// export const getAudioContext = () => Tone.getContext().rawContext;
|
||||||
|
|
||||||
let audioContext;
|
let audioContext;
|
||||||
export const getAudioContext = () => {
|
export const getAudioContext = () => {
|
||||||
|
|
@ -17,9 +20,15 @@ export const getAudioContext = () => {
|
||||||
return audioContext;
|
return audioContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
const lookahead = 0.2;
|
const getFilter = (type, frequency, Q) => {
|
||||||
|
const filter = getAudioContext().createBiquadFilter();
|
||||||
|
filter.type = type;
|
||||||
|
filter.frequency.value = frequency;
|
||||||
|
filter.Q.value = Q;
|
||||||
|
return filter;
|
||||||
|
};
|
||||||
|
|
||||||
const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
|
const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||||
const gainNode = getAudioContext().createGain();
|
const gainNode = getAudioContext().createGain();
|
||||||
gainNode.gain.setValueAtTime(0, begin);
|
gainNode.gain.setValueAtTime(0, begin);
|
||||||
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
|
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
|
||||||
|
|
@ -30,65 +39,115 @@ const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||||
return gainNode;
|
return gainNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.withAudioNode = function (createAudioNode) {
|
|
||||||
return this._withHap((hap) => {
|
|
||||||
return hap.setContext({
|
|
||||||
...hap.context,
|
|
||||||
createAudioNode: (t, e) => createAudioNode(t, e, hap.context.createAudioNode?.(t, hap)),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Pattern.prototype._wave = function (type) {
|
|
||||||
return this.withAudioNode((t, e) => {
|
|
||||||
const osc = getAudioContext().createOscillator();
|
|
||||||
osc.type = type;
|
|
||||||
const f = getFrequency(e);
|
|
||||||
osc.frequency.value = f; // expects frequency..
|
|
||||||
const begin = t ?? e.whole.begin.valueOf() + lookahead;
|
|
||||||
const end = begin + e.duration.valueOf();
|
|
||||||
osc.start(begin);
|
|
||||||
osc.stop(end); // release?
|
|
||||||
return osc;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
|
|
||||||
return this.withAudioNode((t, e, node) => {
|
|
||||||
const velocity = e.context?.velocity || 1;
|
|
||||||
const begin = t ?? e.whole.begin.valueOf() + lookahead;
|
|
||||||
const end = begin + e.duration.valueOf() + lookahead;
|
|
||||||
const envelope = adsr(a, d, s, r, velocity, begin, end);
|
|
||||||
node?.connect(envelope);
|
|
||||||
return envelope;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Pattern.prototype._filter = function (type = 'lowpass', frequency = 1000) {
|
|
||||||
return this.withAudioNode((t, e, node) => {
|
|
||||||
const filter = getAudioContext().createBiquadFilter();
|
|
||||||
filter.type = type;
|
|
||||||
filter.frequency.value = frequency;
|
|
||||||
node?.connect(filter);
|
|
||||||
return filter;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Pattern.prototype.filter = function (type, frequency) {
|
|
||||||
return patternify2(Pattern.prototype._filter)(reify(type), reify(frequency), this);
|
|
||||||
};
|
|
||||||
|
|
||||||
Pattern.prototype.out = function () {
|
Pattern.prototype.out = function () {
|
||||||
const master = getAudioContext().createGain();
|
return this.onTrigger(async (t, hap, ct) => {
|
||||||
master.gain.value = 0.1;
|
const ac = getAudioContext();
|
||||||
master.connect(getAudioContext().destination);
|
// calculate correct time (tone.js workaround)
|
||||||
return this.withAudioNode((t, e, node) => {
|
t = ac.currentTime + t - ct;
|
||||||
if (!node) {
|
// destructure value
|
||||||
console.warn('out: no source! call .osc() first');
|
let {
|
||||||
|
freq,
|
||||||
|
s,
|
||||||
|
n = 0,
|
||||||
|
gain = 1,
|
||||||
|
cutoff,
|
||||||
|
resonance = 1,
|
||||||
|
hcutoff,
|
||||||
|
hresonance = 1,
|
||||||
|
bandf,
|
||||||
|
bandq = 1,
|
||||||
|
pan,
|
||||||
|
attack = 0.001,
|
||||||
|
decay = 0,
|
||||||
|
sustain = 1,
|
||||||
|
release = 0.001,
|
||||||
|
speed = 1, // sample playback speed
|
||||||
|
begin = 0,
|
||||||
|
end = 1,
|
||||||
|
} = hap.value;
|
||||||
|
// the chain will hold all audio nodes that connect to each other
|
||||||
|
const chain = [];
|
||||||
|
if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) {
|
||||||
|
// get frequency
|
||||||
|
if (!freq && typeof n === 'number') {
|
||||||
|
freq = fromMidi(n); // + 48);
|
||||||
|
}
|
||||||
|
if (!freq && typeof n === 'string') {
|
||||||
|
freq = fromMidi(toMidi(n));
|
||||||
|
}
|
||||||
|
// make oscillator
|
||||||
|
const o = ac.createOscillator();
|
||||||
|
o.type = s || 'triangle';
|
||||||
|
o.frequency.value = Number(freq);
|
||||||
|
o.start(t);
|
||||||
|
o.stop(t + hap.duration + release);
|
||||||
|
chain.push(o);
|
||||||
|
// level down oscillators as they are really loud compared to samples i've tested
|
||||||
|
const g = ac.createGain();
|
||||||
|
g.gain.value = 0.5;
|
||||||
|
chain.push(g);
|
||||||
|
// TODO: make adsr work with samples without pops
|
||||||
|
// envelope
|
||||||
|
const adsr = getADSR(attack, decay, sustain, release, 1, t, t + hap.duration);
|
||||||
|
chain.push(adsr);
|
||||||
|
} else {
|
||||||
|
// load sample
|
||||||
|
const samples = getLoadedSamples();
|
||||||
|
if (!samples) {
|
||||||
|
console.warn('no samples loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bank = samples?.[s];
|
||||||
|
if (!bank) {
|
||||||
|
console.warn('sample not found:', s, 'try one of ' + Object.keys(samples));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (speed === 0) {
|
||||||
|
// no playback
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!s) {
|
||||||
|
console.warn('no sample specified');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bank = samples[s];
|
||||||
|
const sampleUrl = bank[n % bank.length];
|
||||||
|
let buffer = await loadBuffer(sampleUrl, ac);
|
||||||
|
if (ac.currentTime > t) {
|
||||||
|
console.warn('sample still loading:', s, n);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const src = ac.createBufferSource();
|
||||||
|
src.buffer = buffer;
|
||||||
|
src.playbackRate.value = Math.abs(speed);
|
||||||
|
// TODO: nudge, unit, cut, loop
|
||||||
|
|
||||||
|
let duration = src.buffer.duration;
|
||||||
|
const offset = begin * duration;
|
||||||
|
duration = ((end - begin) * duration) / Math.abs(speed);
|
||||||
|
src.start(t, offset, duration);
|
||||||
|
src.stop(t + duration);
|
||||||
|
chain.push(src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
node?.connect(master);
|
// filters
|
||||||
})._withHap((hap) => {
|
cutoff !== undefined && chain.push(getFilter('lowpass', cutoff, resonance));
|
||||||
const onTrigger = (time, e) => e.context?.createAudioNode?.(time, e);
|
hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance));
|
||||||
return hap.setContext({ ...hap.context, onTrigger });
|
bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq));
|
||||||
|
// TODO vowel
|
||||||
|
// TODO delay / delaytime / delayfeedback
|
||||||
|
// panning
|
||||||
|
if (pan !== undefined) {
|
||||||
|
const panner = ac.createStereoPanner();
|
||||||
|
panner.pan.value = 2 * pan - 1;
|
||||||
|
chain.push(panner);
|
||||||
|
}
|
||||||
|
// master out
|
||||||
|
const master = ac.createGain();
|
||||||
|
master.gain.value = 0.8 * gain;
|
||||||
|
chain.push(master);
|
||||||
|
chain.push(ac.destination);
|
||||||
|
// connect chain elements together
|
||||||
|
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.define('wave', (type, pat) => pat.wave(type), { patternified: true });
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
export * from './webdirt.mjs';
|
export * from './webdirt.mjs';
|
||||||
export * from './sampler.mjs';
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as strudel from '@strudel.cycles/core';
|
import * as strudel from '@strudel.cycles/core';
|
||||||
const { Pattern } = strudel;
|
const { Pattern } = strudel;
|
||||||
import * as WebDirt from 'WebDirt';
|
import * as WebDirt from 'WebDirt';
|
||||||
import { getLoadedSamples, loadBuffer, getLoadedBuffer } from './sampler.mjs';
|
import { getLoadedSamples, loadBuffer, getLoadedBuffer } from '@strudel.cycles/webaudio';
|
||||||
|
|
||||||
let webDirt;
|
let webDirt;
|
||||||
|
|
||||||
|
|
|
||||||
2
repl/.gitignore
vendored
2
repl/.gitignore
vendored
|
|
@ -22,3 +22,5 @@ dist-ssr
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
oldtunes.mjs
|
||||||
|
|
@ -13,7 +13,8 @@ import './App.css';
|
||||||
import logo from './logo.svg';
|
import logo from './logo.svg';
|
||||||
import * as tunes from './tunes.mjs';
|
import * as tunes from './tunes.mjs';
|
||||||
import * as WebDirt from 'WebDirt';
|
import * as WebDirt from 'WebDirt';
|
||||||
import { loadWebDirt, resetLoadedSamples } from '@strudel.cycles/webdirt';
|
import { loadWebDirt } from '@strudel.cycles/webdirt';
|
||||||
|
import { resetLoadedSamples } from '@strudel.cycles/webaudio';
|
||||||
|
|
||||||
evalScope(
|
evalScope(
|
||||||
Tone,
|
Tone,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
import { evaluate } from '@strudel.cycles/eval';
|
import { evaluate } from '@strudel.cycles/eval';
|
||||||
import { extend } from '@strudel.cycles/eval';
|
import { extend } from '@strudel.cycles/eval';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
import * as strudel from '@strudel.cycles/core';
|
||||||
|
import * as webaudio from '@strudel.cycles/webaudio';
|
||||||
|
import controls from '@strudel.cycles/core/controls.mjs';
|
||||||
// import gist from '@strudel.cycles/core/gist.js';
|
// import gist from '@strudel.cycles/core/gist.js';
|
||||||
import { mini } from '@strudel.cycles/mini/mini.mjs';
|
import { mini } from '@strudel.cycles/mini/mini.mjs';
|
||||||
// import { Tone } from '@strudel.cycles/tone';
|
// import { Tone } from '@strudel.cycles/tone';
|
||||||
|
|
@ -83,6 +85,9 @@ const toneHelpersMocked = {
|
||||||
strudel.Pattern.prototype.tone = function () {
|
strudel.Pattern.prototype.tone = function () {
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
strudel.Pattern.prototype.webdirt = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
// draw mock
|
// draw mock
|
||||||
strudel.Pattern.prototype.pianoroll = function () {
|
strudel.Pattern.prototype.pianoroll = function () {
|
||||||
|
|
@ -116,6 +121,7 @@ const uiHelpersMocked = {
|
||||||
backgroundImage: id,
|
backgroundImage: id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// TODO: refactor to evalScope
|
// TODO: refactor to evalScope
|
||||||
extend(
|
extend(
|
||||||
// Tone,
|
// Tone,
|
||||||
|
|
@ -123,6 +129,8 @@ extend(
|
||||||
strudel.Pattern.prototype.bootstrap(),
|
strudel.Pattern.prototype.bootstrap(),
|
||||||
toneHelpersMocked,
|
toneHelpersMocked,
|
||||||
uiHelpersMocked,
|
uiHelpersMocked,
|
||||||
|
controls,
|
||||||
|
webaudio,
|
||||||
/* controls,
|
/* controls,
|
||||||
toneHelpers,
|
toneHelpers,
|
||||||
voicingHelpers,
|
voicingHelpers,
|
||||||
|
|
@ -181,6 +189,6 @@ export const testCycles = {
|
||||||
randomBells: 24,
|
randomBells: 24,
|
||||||
waa: 16,
|
waa: 16,
|
||||||
waar: 16,
|
waar: 16,
|
||||||
hyperpop: 60,
|
hyperpop: 10,
|
||||||
festivalOfFingers3: 16,
|
festivalOfFingers3: 16,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -728,7 +728,7 @@ bell = bell.chain(vol(0.6).connect(delay),out());
|
||||||
.slow(6)
|
.slow(6)
|
||||||
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
|
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
|
||||||
|
|
||||||
export const waa = `"a4 [a3 c3] a3 c3"
|
/* export const waa = `n("a4 [a3 c3] a3 c3")
|
||||||
.sub("<7 12>/2")
|
.sub("<7 12>/2")
|
||||||
.off(1/8, add("12"))
|
.off(1/8, add("12"))
|
||||||
.off(1/4, add("7"))
|
.off(1/4, add("7"))
|
||||||
|
|
@ -736,23 +736,37 @@ export const waa = `"a4 [a3 c3] a3 c3"
|
||||||
.slow(2)
|
.slow(2)
|
||||||
.wave("sawtooth square")
|
.wave("sawtooth square")
|
||||||
.filter('lowpass', "<2000 1000 500>")
|
.filter('lowpass', "<2000 1000 500>")
|
||||||
.out()`;
|
.out()`; */
|
||||||
|
|
||||||
export const waar = `"a4 [a3 c3] a3 c3".color('#F9D649')
|
export const waa = `n(
|
||||||
.sub("<7 12 5 12>".slow(2))
|
"a4 [a3 c3] a3 c3"
|
||||||
.off(1/4,x=>x.add(7).color("#FFFFFF #0C3AA1 #C63928"))
|
.sub("<7 12>/2")
|
||||||
.off(1/8,x=>x.add(12).color('#215CB6'))
|
.off(1/8, add("12"))
|
||||||
.slow(2)
|
.off(1/4, add("7"))
|
||||||
.legato(sine.range(0.3, 2).slow(28))
|
)
|
||||||
.wave("sawtooth square".fast(2))
|
.legato(.5)
|
||||||
.filter('lowpass', cosine.range(500,4000).slow(16))
|
.slow(2)
|
||||||
.out()
|
.s("sawtooth square")
|
||||||
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})`;
|
.cutoff("<2000 1000 500>")
|
||||||
|
.out()
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const waa2 = `n(
|
||||||
|
"a4 [a3 c3] a3 c3"
|
||||||
|
.sub("<7 12 5 12>".slow(2))
|
||||||
|
.off(1/4,x=>x.add(7))
|
||||||
|
.off(1/8,x=>x.add(12))
|
||||||
|
)
|
||||||
|
.slow(2)
|
||||||
|
.legato(sine.range(0.3, 2).slow(28))
|
||||||
|
.s("sawtooth square".fast(2))
|
||||||
|
.cutoff(cosine.range(500,4000).slow(16))
|
||||||
|
.out()`;
|
||||||
|
|
||||||
export const hyperpop = `const lfo = cosine.slow(15);
|
export const hyperpop = `const lfo = cosine.slow(15);
|
||||||
const lfo2 = sine.slow(16);
|
const lfo2 = sine.slow(16);
|
||||||
const filter1 = x=>x.filter('lowpass', lfo2.range(300,3000));
|
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
|
||||||
const filter2 = x=>x.filter('highpass', lfo.range(1000,6000)).filter('lowpass',4000)
|
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
|
||||||
const scales = cat('D3 major', 'G3 major').slow(8)
|
const scales = cat('D3 major', 'G3 major').slow(8)
|
||||||
|
|
||||||
const drums = await players({
|
const drums = await players({
|
||||||
|
|
@ -765,24 +779,30 @@ const drums = await players({
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
"-7 0 -7 7".struct("x(5,8,2)").fast(2).sub(7)
|
"-7 0 -7 7".struct("x(5,8,2)").fast(2).sub(7)
|
||||||
.scale(scales).wave("sawtooth,square").velocity(.3).adsr(0.01,0.1,.5,0)
|
.scale(scales)
|
||||||
|
.n()
|
||||||
|
.s("sawtooth,square")
|
||||||
|
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
|
||||||
.apply(filter1),
|
.apply(filter1),
|
||||||
"~@3 [<2 3>,<4 5>]"
|
"~@3 [<2 3>,<4 5>]"
|
||||||
.echo(8,1/16,.7)
|
.echo(4,1/16,.7)
|
||||||
.scale(scales)
|
.scale(scales)
|
||||||
.wave('square').velocity(.7).adsr(0.01,0.1,0).apply(filter1),
|
.n()
|
||||||
"6 5 4".add(14)
|
.s('square').gain(.7)
|
||||||
|
.attack(0.01).decay(0.1).sustain(0)
|
||||||
|
.apply(filter1),
|
||||||
|
"6 4 2".add(14)
|
||||||
.superimpose(sub("5"))
|
.superimpose(sub("5"))
|
||||||
.fast(1).euclidLegato(3,8)
|
.fast(1).euclidLegato(3,8)
|
||||||
.mask("<1 0@7>")
|
.mask("<1 0@7>")
|
||||||
.fast(2)
|
.fast(2)
|
||||||
.echo(32, 1/8, .9)
|
.echo(32, 1/8, .8)
|
||||||
.scale(scales)
|
.scale(scales)
|
||||||
.wave("sawtooth")
|
.n()
|
||||||
.velocity(.2)
|
.s("sawtooth")
|
||||||
.adsr(.01,.5,0)
|
.gain(sine.range(.1,.4).slow(8))
|
||||||
|
.attack(.001).decay(.2).sustain(0)
|
||||||
.apply(filter2)
|
.apply(filter2)
|
||||||
//.echo(4,1/16,.5)
|
|
||||||
).out().stack(
|
).out().stack(
|
||||||
stack(
|
stack(
|
||||||
"bd <~@7 [~ bd]>".fast(2),
|
"bd <~@7 [~ bd]>".fast(2),
|
||||||
|
|
@ -790,7 +810,7 @@ stack(
|
||||||
"[~ hh3]*2"
|
"[~ hh3]*2"
|
||||||
).tone(drums.chain(vol(.18),out())).fast(2)
|
).tone(drums.chain(vol(.18),out())).fast(2)
|
||||||
).slow(2)
|
).slow(2)
|
||||||
|
|
||||||
//.pianoroll({minMidi:20, maxMidi:160})
|
//.pianoroll({minMidi:20, maxMidi:160})
|
||||||
// strudel disable-highlighting`;
|
// strudel disable-highlighting`;
|
||||||
|
|
||||||
|
|
@ -816,3 +836,73 @@ stack(
|
||||||
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
|
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
|
||||||
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
|
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
|
||||||
).tone((await piano()).toDestination()).slow(2)`;
|
).tone((await piano()).toDestination()).slow(2)`;
|
||||||
|
|
||||||
|
export const customTrigger = `stack(
|
||||||
|
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
|
||||||
|
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
|
||||||
|
).s("<sawtooth square>/2")
|
||||||
|
.onTrigger((t,hap,ct)=>{
|
||||||
|
const ac = Tone.getContext().rawContext;
|
||||||
|
t = ac.currentTime + t - ct;
|
||||||
|
const { freq, s, gain = 1 } = hap.value;
|
||||||
|
const master = ac.createGain();
|
||||||
|
master.gain.value = 0.1 * gain;
|
||||||
|
master.connect(ac.destination);
|
||||||
|
const o = ac.createOscillator();
|
||||||
|
o.type = s || 'triangle';
|
||||||
|
o.frequency.value = Number(freq);
|
||||||
|
o.connect(master);
|
||||||
|
o.start(t);
|
||||||
|
o.stop(t + hap.duration);
|
||||||
|
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`;
|
||||||
|
|
||||||
|
export const bornagain = `stack(
|
||||||
|
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)")
|
||||||
|
.layer(x=>x.mul("1.006,.995"))), // detune
|
||||||
|
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
|
||||||
|
).s("<sawtooth square>/2")
|
||||||
|
.cutoff(perlin.range(100,4000).slow(4))
|
||||||
|
.jux(rev)
|
||||||
|
.out()
|
||||||
|
.stack(s("bd(3,8),hh*4,~ sd").webdirt())`;
|
||||||
|
|
||||||
|
export const meltingsubmarine = `samples({
|
||||||
|
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||||
|
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||||
|
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||||
|
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||||
|
stack(
|
||||||
|
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
||||||
|
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||||
|
//.hush()
|
||||||
|
,"<a1 b1*2 a1(3,8) e2>" // bassline
|
||||||
|
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||||
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
|
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||||
|
.n() // wrap in "n"
|
||||||
|
.decay(.15).sustain(0) // make each note of equal length
|
||||||
|
.s('sawtooth') // waveform
|
||||||
|
.gain(.4) // turn down
|
||||||
|
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
|
||||||
|
//.hush()
|
||||||
|
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
|
||||||
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
|
.n() // wrap in "n"
|
||||||
|
.s('sawtooth') // waveform
|
||||||
|
.gain(.16) // turn down
|
||||||
|
.cutoff(500) // fixed cutoff
|
||||||
|
.attack(1) // slowly fade in
|
||||||
|
//.hush()
|
||||||
|
,"a4 c5 <e6 a6>".struct("x(5,8)")
|
||||||
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
|
.n() // wrap in "n"
|
||||||
|
.decay(.1).sustain(0) // make notes short
|
||||||
|
.s('triangle') // waveform
|
||||||
|
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
||||||
|
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
|
||||||
|
//.hush()
|
||||||
|
)
|
||||||
|
.out()
|
||||||
|
.slow(3/2)`;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue