Merge pull request #295 from tidalcycles/refactor-bootstrap

Move stuff to new register function
This commit is contained in:
Felix Roos 2022-12-11 21:27:58 +01:00 committed by GitHub
commit 5f37a4a21c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1158 additions and 1266 deletions

View file

@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isPattern, Pattern } from './index.mjs'; import { isPattern } from './index.mjs';
let scoped = false; let scoped = false;
export const evalScope = async (...args) => { export const evalScope = async (...args) => {
@ -19,7 +19,7 @@ export const evalScope = async (...args) => {
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason); console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
} }
}); });
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap()); Object.assign(globalThis, ...modules);
}; };
function safeEval(str, options = {}) { function safeEval(str, options = {}) {

View file

@ -581,21 +581,6 @@ export class Pattern {
); );
} }
patternify(join, func) {
const pat = this;
return function (...args) {
// the problem here: args could be a pattern that has been
// turned into an object to add location to avoid object
// checking for every pattern method, we can remove it here...
// in the future, patternified args should be marked as well +
// some better object handling
args = args.map((arg) => (isPattern(arg) ? arg.fmap((value) => value.value || value) : arg));
const pat_arg = sequence(...args);
// arg.locations has to go somewhere..
return join(pat_arg.fmap((arg) => func.call(pat, arg)));
};
}
asNumber() { asNumber() {
return this.fmap(parseNumeral); return this.fmap(parseNumeral);
} }
@ -1277,9 +1262,9 @@ export function register(name, func) {
args = args.map(reify); args = args.map(reify);
// For methods that take a single argument (plus 'this'), allow // For methods that take a single argument (plus 'this'), allow
// multiple arguments but sequence them // multiple arguments but sequence them
if (arity == 2 && args.length != 1) { if (arity === 2 && args.length !== 1) {
args = [sequence(...args)]; args = [sequence(...args)];
} else if (arity != args.length + 1) { } else if (arity !== args.length + 1) {
throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`); throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`);
} }
return pfunc(...args, this); return pfunc(...args, this);
@ -1533,7 +1518,7 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu
/** /**
* Like layer, but with a single function: * Like layer, but with a single function:
* @name _apply * @name apply
* @memberof Pattern * @memberof Pattern
* @example * @example
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note() * "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
@ -1921,83 +1906,3 @@ const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat)
const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) { const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
return _loopAt(factor, pat, cps); return _loopAt(factor, pat, cps);
}); });
// problem: curried functions with spread arguments must have pat at the beginning
// with this, we cannot keep the pattern open at the end.. solution for now: use array to keep using pat as last arg
// these are the core composable functions. they are extended with Pattern.prototype.define below
Pattern.prototype.composable = { early, late, superimpose };
// adds Pattern.prototype.composable to given function as child functions
// then you can do transpose(2).late(0.2) instead of x => x.transpose(2).late(0.2)
export function makeComposable(func) {
Object.entries(Pattern.prototype.composable).forEach(([functionName, composable]) => {
// compose with dot
func[functionName] = (...args) => {
// console.log(`called ${functionName}(${args.join(',')})`);
const composition = compose(func, composable(...args));
// the composition itself must be composable too :)
// then you can do endless chaining transpose(2).late(0.2).fast(2) ...
return makeComposable(composition);
};
});
return func;
}
export const patternify = (f) => (pata, pat) => pata.fmap((a) => f.call(pat, a)).innerJoin();
export const patternify2 = (f) =>
function (pata, patb, pat) {
return pata
.fmap((a) => (b) => f.call(pat, a, b))
.appLeft(patb)
.innerJoin();
};
export const patternify3 = (f) => (pata, patb, patc, pat) =>
pata
.fmap((a) => (b) => (c) => f.call(pat, a, b, c))
.appLeft(patb)
.appLeft(patc)
.innerJoin();
export const patternify4 = (f) => (pata, patb, patc, patd, pat) =>
pata
.fmap((a) => (b) => (c) => (d) => f.call(pat, a, b, c, d))
.appLeft(patb)
.appLeft(patc)
.appLeft(patd)
.innerJoin();
Pattern.prototype.patternified = [];
// call this after all Pattern.prototype.define calls have been executed! (right before evaluate)
Pattern.prototype.bootstrap = function () {
const bootstrapped = Object.fromEntries(
Object.entries(Pattern.prototype.composable).map(([functionName, composable]) => {
if (Pattern.prototype[functionName]) {
// without this, 'C^7'.m.chordBass.transpose(2) will throw "C^7".m.chordBass.transpose is not a function
// Pattern.prototype[functionName] = makeComposable(Pattern.prototype[functionName]); // is this needed?
}
return [functionName, curry(composable, makeComposable)];
}),
);
this.patternified.forEach((prop) => {
// the following will patternify all functions in Pattern.prototype.patternified
Pattern.prototype[prop] = function (...args) {
return this.patternify((x) => x.innerJoin(), Pattern.prototype['_' + prop])(...args);
};
});
return bootstrapped;
};
// this will add func as name to list of composable / patternified functions.
// those lists will be used in bootstrap to curry and compose everything, to support various call patterns
Pattern.prototype.define = (name, func, options = {}) => {
if (options.composable) {
Pattern.prototype.composable[name] = func;
}
if (options.patternified) {
Pattern.prototype.patternified = Pattern.prototype.patternified.concat([name]);
}
Pattern.prototype.bootstrap(); // automatically bootstrap after new definition
};

View file

@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Hap } from './hap.mjs'; import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack, isPattern } from './pattern.mjs'; import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
import { id } from './util.mjs'; import { id } from './util.mjs';
@ -258,9 +258,9 @@ export const perlinWith = (pat) => {
*/ */
export const perlin = perlinWith(time.fmap((v) => Number(v))); export const perlin = perlinWith(time.fmap((v) => Number(v)));
Pattern.prototype._degradeByWith = function (withPat, x) { export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)); pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
}; );
/** /**
* Randomly removes events from the pattern by a given amount. * Randomly removes events from the pattern by a given amount.
@ -276,9 +276,9 @@ Pattern.prototype._degradeByWith = function (withPat, x) {
* @example * @example
* s("[hh?0.2]*8") * s("[hh?0.2]*8")
*/ */
Pattern.prototype._degradeBy = function (x) { export const degradeBy = register('degradeBy', function (x, pat) {
return this._degradeByWith(rand, x); return pat._degradeByWith(rand, x);
}; });
/** /**
* *
@ -292,9 +292,7 @@ Pattern.prototype._degradeBy = function (x) {
* @example * @example
* s("[hh?]*8") * s("[hh?]*8")
*/ */
Pattern.prototype.degrade = function () { export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
return this._degradeBy(0.5);
};
/** /**
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount. * Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
@ -309,26 +307,14 @@ Pattern.prototype.degrade = function () {
* @example * @example
* s("hh*8").undegradeBy(0.2) * s("hh*8").undegradeBy(0.2)
*/ */
Pattern.prototype._undegradeBy = function (x) { export const undegradeBy = register('undegradeBy', function (x, pat) {
return this._degradeByWith( return pat._degradeByWith(
rand.fmap((r) => 1 - r), rand.fmap((r) => 1 - r),
x, x,
); );
}; });
Pattern.prototype.undegrade = function () { export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
return this._undegradeBy(0.5);
};
Pattern.prototype._sometimesBy = function (x, func) {
return stack(this._degradeBy(x), func(this._undegradeBy(1 - x)));
};
// https://github.com/tidalcycles/strudel/discussions/198
/* Pattern.prototype._sometimesBy = function (x, other) {
other = typeof other === 'function' ? other(this._undegradeBy(1 - x)) : reify(other)._undegradeBy(1 - x);
return stack(this._degradeBy(x), other);
}; */
/** /**
* *
@ -343,24 +329,12 @@ Pattern.prototype._sometimesBy = function (x, func) {
* @example * @example
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5")) * s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
*/ */
Pattern.prototype.sometimesBy = function (patx, func) {
const pat = this;
return reify(patx)
.fmap((x) => pat._sometimesBy(x, func))
.innerJoin();
};
// why does this exist? it is identical to sometimesBy export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
Pattern.prototype._sometimesByPre = function (x, func) {
return stack(this._degradeBy(x), func(this).undegradeBy(1 - x));
};
Pattern.prototype.sometimesByPre = function (patx, func) {
const pat = this;
return reify(patx) return reify(patx)
.fmap((x) => pat._sometimesByPre(x, func)) .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
.innerJoin(); .innerJoin();
}; });
/** /**
* *
@ -373,20 +347,9 @@ Pattern.prototype.sometimesByPre = function (patx, func) {
* @example * @example
* s("hh*4").sometimes(x=>x.speed("0.5")) * s("hh*4").sometimes(x=>x.speed("0.5"))
*/ */
Pattern.prototype.sometimes = function (func) { export const sometimes = register('sometimes', function (func, pat) {
return this._sometimesBy(0.5, func); return pat._sometimesBy(0.5, func);
}; });
Pattern.prototype.sometimesPre = function (func) {
return this._sometimesByPre(0.5, func);
};
Pattern.prototype._someCyclesBy = function (x, func) {
return stack(
this._degradeByWith(rand._segment(1), x),
func(this._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
);
};
/** /**
* *
@ -401,12 +364,17 @@ Pattern.prototype._someCyclesBy = function (x, func) {
* @example * @example
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5")) * s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
*/ */
Pattern.prototype.someCyclesBy = function (patx, func) {
const pat = this; export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
return reify(patx) return reify(patx)
.fmap((x) => pat._someCyclesBy(x, func)) .fmap((x) =>
stack(
pat._degradeByWith(rand._segment(1), x),
func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
),
)
.innerJoin(); .innerJoin();
}; });
/** /**
* *
@ -418,9 +386,9 @@ Pattern.prototype.someCyclesBy = function (patx, func) {
* @example * @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5")) * s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/ */
Pattern.prototype.someCycles = function (func) { export const someCycles = register('someCycles', function (func, pat) {
return this._someCyclesBy(0.5, func); return pat._someCyclesBy(0.5, func);
}; });
/** /**
* *
@ -432,9 +400,9 @@ Pattern.prototype.someCycles = function (func) {
* @example * @example
* s("hh*8").often(x=>x.speed("0.5")) * s("hh*8").often(x=>x.speed("0.5"))
*/ */
Pattern.prototype.often = function (func) { export const often = register('often', function (func, pat) {
return this.sometimesBy(0.75, func); return pat.sometimesBy(0.75, func);
}; });
/** /**
* *
@ -446,9 +414,9 @@ Pattern.prototype.often = function (func) {
* @example * @example
* s("hh*8").rarely(x=>x.speed("0.5")) * s("hh*8").rarely(x=>x.speed("0.5"))
*/ */
Pattern.prototype.rarely = function (func) { export const rarely = register('rarely', function (func, pat) {
return this.sometimesBy(0.25, func); return pat.sometimesBy(0.25, func);
}; });
/** /**
* *
@ -460,9 +428,9 @@ Pattern.prototype.rarely = function (func) {
* @example * @example
* s("hh*8").almostNever(x=>x.speed("0.5")) * s("hh*8").almostNever(x=>x.speed("0.5"))
*/ */
Pattern.prototype.almostNever = function (func) { export const almostNever = register('almostNever', function (func, pat) {
return this.sometimesBy(0.1, func); return pat.sometimesBy(0.1, func);
}; });
/** /**
* *
@ -474,9 +442,9 @@ Pattern.prototype.almostNever = function (func) {
* @example * @example
* s("hh*8").almostAlways(x=>x.speed("0.5")) * s("hh*8").almostAlways(x=>x.speed("0.5"))
*/ */
Pattern.prototype.almostAlways = function (func) { export const almostAlways = register('almostAlways', function (func, pat) {
return this.sometimesBy(0.9, func); return pat.sometimesBy(0.9, func);
}; });
/** /**
* *
@ -488,9 +456,9 @@ Pattern.prototype.almostAlways = function (func) {
* @example * @example
* s("hh*8").never(x=>x.speed("0.5")) * s("hh*8").never(x=>x.speed("0.5"))
*/ */
Pattern.prototype.never = function (func) { export const never = register('never', function (_, pat) {
return this; return pat;
}; });
/** /**
* *
@ -502,8 +470,6 @@ Pattern.prototype.never = function (func) {
* @example * @example
* s("hh*8").always(x=>x.speed("0.5")) * s("hh*8").always(x=>x.speed("0.5"))
*/ */
Pattern.prototype.always = function (func) { export const always = register('always', function (func, pat) {
return func(this); return func(pat);
}; });
Pattern.prototype.patternified.push('degradeBy', 'undegradeBy');

View file

@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern, patternify2, reify } from './index.mjs'; import { register } from './index.mjs';
let synth; let synth;
try { try {
@ -16,7 +16,7 @@ try {
let allVoices = synth?.getVoices(); let allVoices = synth?.getVoices();
// console.log('voices', allVoices); // console.log('voices', allVoices);
function speak(words, lang, voice) { function triggerSpeech(words, lang, voice) {
synth.cancel(); synth.cancel();
const utterance = new SpeechSynthesisUtterance(words); const utterance = new SpeechSynthesisUtterance(words);
utterance.lang = lang; utterance.lang = lang;
@ -31,12 +31,8 @@ function speak(words, lang, voice) {
speechSynthesis.speak(utterance); speechSynthesis.speak(utterance);
} }
Pattern.prototype._speak = function (lang, voice) { export const speak = register('speak', function (lang, voice, pat) {
return this.onTrigger((_, hap) => { return pat.onTrigger((_, hap) => {
speak(hap.value, lang, voice); triggerSpeech(hap.value, lang, voice);
}); });
}; });
Pattern.prototype.speak = function (lang, voice) {
return patternify2(Pattern.prototype._speak)(reify(lang), reify(voice), this);
};

View file

@ -1,4 +1,4 @@
import { getFrequency, logger, Pattern } from '@strudel.cycles/core'; import { getFrequency, logger, register } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel.cycles/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import csd from './project.csd?raw'; import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw'; // import livecodeOrc from './livecode.orc?raw';
@ -6,12 +6,24 @@ import presetsOrc from './presets.orc?raw';
let csoundLoader, _csound; let csoundLoader, _csound;
// triggers given instrument name using csound. // initializes csound + can be used to reevaluate given instrument code
Pattern.prototype._csound = function (instrument) { export async function loadCSound(code = '') {
await init();
if (code) {
code = `${code}`;
// ^ ^
// wrapping in backticks makes sure it works when calling as templated function
await _csound?.evalCode(code);
}
}
export const loadcsound = loadCSound;
export const loadCsound = loadCSound;
export const csound = register('csound', (instrument, pat) => {
instrument = instrument || 'triangle'; instrument = instrument || 'triangle';
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
// TODO: find a alternative way to wait for csound to load (to wait with first time playback) // TODO: find a alternative way to wait for csound to load (to wait with first time playback)
return this.onTrigger((time, hap) => { return pat.onTrigger((time, hap) => {
if (!_csound) { if (!_csound) {
logger('[csound] not loaded yet', 'warning'); logger('[csound] not loaded yet', 'warning');
return; return;
@ -40,20 +52,7 @@ Pattern.prototype._csound = function (instrument) {
const msg = `i ${params.join(' ')}`; const msg = `i ${params.join(' ')}`;
_csound.inputMessage(msg); _csound.inputMessage(msg);
}); });
}; });
// initializes csound + can be used to reevaluate given instrument code
export async function csound(code = '') {
await init();
if (code) {
code = `${code}`;
// ^ ^
// wrapping in backticks makes sure it works when calling as templated function
await _csound?.evalCode(code);
}
}
Pattern.prototype.define('csound', (a, pat) => pat.csound(a), { composable: false, patternified: true });
function eventLogger(type, args) { function eventLogger(type, args) {
const [msg] = args; const [msg] = args;

View file

@ -225,9 +225,8 @@ function isPatternArg(parents) {
function hasModifierCall(parent) { function hasModifierCall(parent) {
// TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier.. // TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier..
// need all prototypes of Pattern // need all prototypes of Pattern
return ( return parent?.type === 'StaticMemberExpression';
parent?.type === 'StaticMemberExpression' && Object.keys(Pattern.prototype.composable).includes(parent.property) // && Object.keys(Pattern.prototype.composable).includes(parent.property)
);
} }
const factories = Object.keys(Pattern.prototype.factories).concat(['mini']); const factories = Object.keys(Pattern.prototype.factories).concat(['mini']);

View file

@ -8,14 +8,14 @@ import * as krill from './krill-parser.js';
import * as strudel from '@strudel.cycles/core'; import * as strudel from '@strudel.cycles/core';
// import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs'; // import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel; const { pure, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
var _seedState = 0; /* var _seedState = 0;
const randOffset = 0.0002; const randOffset = 0.0002;
function _nextSeed() { function _nextSeed() {
return _seedState++; return _seedState++;
} } */
const applyOptions = (parent) => (pat, i) => { const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i]; const ast = parent.source_[i];
@ -30,10 +30,28 @@ const applyOptions = (parent) => (pat, i) => {
case 'bjorklund': case 'bjorklund':
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation); return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
case 'degradeBy': case 'degradeBy':
// TODO: find out what is right here
// example:
/*
stack(
s("hh*8").degrade(),
s("[ht*8]?")
)
*/
// above example will only be in sync when _degradeBy is used...
// it also seems that the nextSeed will create undeterministic behaviour
// as it uses a global _seedState. This is probably the reason for
// https://github.com/tidalcycles/strudel/issues/245
// this is how it was:
/*
return reify(pat)._degradeByWith( return reify(pat)._degradeByWith(
strudel.rand.early(randOffset * _nextSeed()).segment(1), strudel.rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount, operator.arguments_.amount ?? 0.5,
); );
*/
return reify(pat)._degradeBy(operator.arguments_.amount ?? 0.5);
// TODO: case 'fixed-step': "%" // TODO: case 'fixed-step': "%"
} }
console.warn(`operator "${operator.type_}" not implemented`); console.warn(`operator "${operator.type_}" not implemented`);
@ -96,7 +114,9 @@ export function patternifyAST(ast) {
return stack(...children); return stack(...children);
} }
if (alignment === 'r') { if (alignment === 'r') {
return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children); // https://github.com/tidalcycles/strudel/issues/245#issuecomment-1345406422
// return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children);
return strudel.chooseCycles(...children);
} }
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight); const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 't') { if (!weightedChildren && alignment === 't') {
@ -178,11 +198,6 @@ export const h = (string) => {
return patternifyAST(ast); return patternifyAST(ast);
}; };
// shorthand for mini
Pattern.prototype.define('mini', mini, { composable: true });
Pattern.prototype.define('m', mini, { composable: true });
Pattern.prototype.define('h', h, { composable: true });
export function minify(thing) { export function minify(thing) {
if (typeof thing === 'string') { if (typeof thing === 'string') {
return mini(thing); return mini(thing);

View file

@ -77,7 +77,7 @@ describe('mini', () => {
expect(haps.length < 230).toBe(true); expect(haps.length < 230).toBe(true);
// 'Had too many cycles remaining after degradeBy 0.8'); // 'Had too many cycles remaining after degradeBy 0.8');
}); });
it('supports the random choice operator ("|") with nesting', () => { /*it('supports the random choice operator ("|") with nesting', () => {
const numCycles = 900; const numCycles = 900;
const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles); const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles);
// Should have about 1/3 a, 1/6 each of b | c, and 1/9 each of d | e | f. // Should have about 1/3 a, 1/6 each of b | c, and 1/9 each of d | e | f.
@ -103,5 +103,5 @@ describe('mini', () => {
// PRNG, this test should succeed // PRNG, this test should succeed
expect(chisq <= 15.086).toBe(true); expect(chisq <= 15.086).toBe(true);
// assert(chisq <= 15.086, chisq + ' was expected to be less than 15.086 under chi-squared test'); // assert(chisq <= 15.086, chisq + ' was expected to be less than 15.086 under chi-squared test');
}); });*/
}); });

View file

@ -1,2 +1,5 @@
import './tonal.mjs'; import './tonal.mjs';
import './voicings.mjs'; import './voicings.mjs';
export * from './tonal.mjs';
export * from './voicings.mjs';

View file

@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { Note, Interval, Scale } from '@tonaljs/tonal'; import { Note, Interval, Scale } from '@tonaljs/tonal';
import { Pattern, _mod } from '@strudel.cycles/core'; import { register, _mod } from '@strudel.cycles/core';
// transpose note inside scale by offset steps // transpose note inside scale by offset steps
// function scaleOffset(scale: string, offset: number, note: string) { // function scaleOffset(scale: string, offset: number, note: string) {
@ -74,8 +74,8 @@ function scaleOffset(scale, offset, note) {
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note() * "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
*/ */
Pattern.prototype._transpose = function (intervalOrSemitones) { export const transpose = register('transpose', function (intervalOrSemitones, pat) {
return this.withHap((hap) => { return pat.withHap((hap) => {
const interval = !isNaN(Number(intervalOrSemitones)) const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones /* as number */) ? Interval.fromSemitones(intervalOrSemitones /* as number */)
: String(intervalOrSemitones); : String(intervalOrSemitones);
@ -87,11 +87,9 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3 // tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval))); return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
}); });
}; });
// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2)) // example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2))
// TODO: add Pattern.define(name, function, options) that handles all the meta programming stuff
// TODO: find out how to patternify this function when it's standalone
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or // e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or // or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
@ -110,8 +108,8 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
* .note() * .note()
*/ */
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) { export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) {
return this.withHap((hap) => { return pat.withHap((hap) => {
if (!hap.context.scale) { if (!hap.context.scale) {
throw new Error('can only use scaleTranspose after .scale'); throw new Error('can only use scaleTranspose after .scale');
} }
@ -120,7 +118,7 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
} }
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value)); return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
}); });
}; });
/** /**
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}. * Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
@ -141,8 +139,8 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
* .note() * .note()
*/ */
Pattern.prototype._scale = function (scale /* : string */) { export const scale = register('scale', function (scale /* : string */, pat) {
return this.withHap((hap) => { return pat.withHap((hap) => {
let note = hap.value; let note = hap.value;
const asNumber = Number(note); const asNumber = Number(note);
if (!isNaN(asNumber)) { if (!isNaN(asNumber)) {
@ -152,8 +150,4 @@ Pattern.prototype._scale = function (scale /* : string */) {
} }
return hap.withValue(() => note).setContext({ ...hap.context, scale }); return hap.withValue(() => note).setContext({ ...hap.context, scale });
}); });
}; });
Pattern.prototype.define('transpose', (a, pat) => pat.transpose(a), { composable: true, patternified: true });
Pattern.prototype.define('scale', (a, pat) => pat.scale(a), { composable: true, patternified: true });
Pattern.prototype.define('scaleTranspose', (a, pat) => pat.scaleTranspose(a), { composable: true, patternified: true });

View file

@ -4,31 +4,49 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern as _Pattern, stack, Hap, reify } from '@strudel.cycles/core'; import { stack, register } from '@strudel.cycles/core';
import _voicings from 'chord-voicings'; import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) => export const voicingRegistry = {
dictionaryVoicing({ lefthand: { dictionary: lefthand, range: ['F3', 'A4'] },
};
export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistry[name].dictionary, range);
/**
* Adds a new custom voicing dictionary.
*
* @name addVoicings
* @memberof Pattern
* @param {string} name identifier for the voicing dictionary
* @param {Object} dictionary maps chord symbol to possible voicings
* @param {Array} range min, max note
* @returns Pattern
* @example
* addVoicings('cookie', {
* 7: ['3M 7m 9M 12P 15P', '7m 10M 13M 16M 19P'],
* '^7': ['3M 6M 9M 12P 14M', '7M 10M 13M 16M 19P'],
* m7: ['8P 11P 14m 17m 19P', '5P 8P 11P 14m 17m'],
* m7b5: ['3m 5d 8P 11P 14m', '5d 8P 11P 14m 17m'],
* o7: ['3m 6M 9M 11A 15P'],
* '7alt': ['3M 7m 10m 13m 15P'],
* '7#11': ['7m 10m 13m 15P 17m'],
* }, ['C3', 'C6'])
* "<C^7 A7 Dm7 G7>".voicings('cookie').note()
*/
export const addVoicings = (name, dictionary, range = ['F3', 'A4']) => {
Object.assign(voicingRegistry, { [name]: { dictionary, range } });
};
const getVoicing = (chord, dictionaryName, lastVoicing) => {
const { dictionary, range } = voicingRegistry[dictionaryName];
return dictionaryVoicing({
chord, chord,
dictionary: lefthand, dictionary,
range, range,
picker: minTopNoteDiff, picker: minTopNoteDiff,
lastVoicing, lastVoicing,
}); });
const Pattern = _Pattern;
Pattern.prototype.fmapNested = function (func) {
return new Pattern((span) =>
this.query(span)
.map((event) =>
reify(func(event))
.query(span)
.map((hap) => new Hap(event.whole, event.part, hap.value, hap.context)),
)
.flat(),
);
}; };
/** /**
@ -37,32 +55,38 @@ Pattern.prototype.fmapNested = function (func) {
* *
* @name voicings * @name voicings
* @memberof Pattern * @memberof Pattern
* @param {range} range note range for possible voicings (optional, defaults to `['F3', 'A4']`) * @param {string} dictionary which voicing dictionary to use.
* @returns Pattern * @returns Pattern
* @example * @example
* stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note() * stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()
*/ */
Pattern.prototype.voicings = function (range) { let lastVoicing; // this now has to be global until another solution is found :-/
let lastVoicing; // it used to be local to the voicings function at evaluation time
if (!range?.length) { // but since register will patternify by default, means that
// allows to pass empty array, if too lazy to specify range // the function is called over and over again, resetting the lastVoicing variables
range = ['F3', 'A4']; export const voicings = register('voicings', function (dictionary, pat) {
} return pat
return this.fmapNested((event) => { .fmap((value) => {
lastVoicing = getVoicing(event.value, lastVoicing, range); lastVoicing = getVoicing(value, dictionary, lastVoicing);
return stack(...lastVoicing).withContext(() => ({ return stack(...lastVoicing);
locations: event.context.locations || [], })
})); .outerJoin();
}); });
};
Pattern.prototype._rootNotes = function (octave = 2) { /**
return this.fmap((value) => { * Maps the chords of the incoming pattern to root notes in the given octave.
*
* @name rootNotes
* @memberof Pattern
* @param {octave} octave octave to use
* @returns Pattern
* @example
* "<C^7 A7 Dm7 G7>".rootNotes(2).note()
*/
export const rootNotes = register('rootNotes', function (octave, pat) {
return pat.fmap((value) => {
const root = value.match(/^([a-gA-G][b#]?).*$/)[1]; const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
return root + octave; return root + octave;
}); });
}; });
Pattern.prototype.define('voicings', (range, pat) => pat.voicings(range), { composable: true });
Pattern.prototype.define('rootNotes', (oct, pat) => pat.rootNotes(oct), { composable: true, patternified: true });

View file

@ -4,18 +4,15 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern } from '@strudel.cycles/core'; import { register } from '@strudel.cycles/core';
import * as _Tone from 'tone'; import * as _Tone from 'tone';
// import Tone from here, to make sure to get the same AudioContext // import Tone from here, to make sure to get the same AudioContext
export const Tone = _Tone; export const Tone = _Tone;
const { const {
AutoFilter,
Destination,
Filter, Filter,
Gain, Gain,
isNote,
Synth, Synth,
PolySynth, PolySynth,
MembraneSynth, MembraneSynth,
@ -49,8 +46,8 @@ export const getDefaultSynth = () => {
// https://www.charlie-roberts.com/gibberish/playground/ // https://www.charlie-roberts.com/gibberish/playground/
// with this function, you can play the pattern with any tone synth // with this function, you can play the pattern with any tone synth
Pattern.prototype.tone = function (instrument) { export const tone = register('tone', function (instrument, pat) {
return this.onTrigger((time, hap) => { return pat.onTrigger((time, hap) => {
let note; let note;
let velocity = hap.context?.velocity ?? 0.75; let velocity = hap.context?.velocity ?? 0.75;
if (instrument instanceof PluckSynth) { if (instrument instanceof PluckSynth) {
@ -74,9 +71,7 @@ Pattern.prototype.tone = function (instrument) {
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity); instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
} }
}); });
}; });
Pattern.prototype.define('tone', (type, pat) => pat.tone(type), { composable: true, patternified: false });
// synth helpers // synth helpers
export const amsynth = (options) => new AMSynth(options); export const amsynth = (options) => new AMSynth(options);

View file

@ -4,7 +4,6 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core'; import * as strudel from '@strudel.cycles/core';
import { fromMidi, logger, toMidi } from '@strudel.cycles/core'; import { fromMidi, logger, toMidi } from '@strudel.cycles/core';
import './feedbackdelay.mjs'; import './feedbackdelay.mjs';

View file

@ -1,2 +1,4 @@
import './xen.mjs'; import './xen.mjs';
import './tune.mjs'; import './tune.mjs';
export * from './xen.mjs';

View file

@ -5,18 +5,16 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import Tune from './tunejs.js'; import Tune from './tunejs.js';
import { Pattern } from '@strudel.cycles/core'; import { register } from '@strudel.cycles/core';
Pattern.prototype._tune = function (scale, tonic = 220) { export const tune = register('tune', (scale, pat) => {
const tune = new Tune(); const tune = new Tune();
if (!tune.isValidScale(scale)) { if (!tune.isValidScale(scale)) {
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html'); throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
} }
tune.loadScale(scale); tune.loadScale(scale);
tune.tonicize(tonic); tune.tonicize(1);
return this._asNumber()._withHap((hap) => { return pat.withHap((hap) => {
return hap.withValue(() => tune.note(hap.value)).setContext({ ...hap.context, type: 'frequency' }); return hap.withValue(() => tune.note(hap.value));
}); });
}; });
Pattern.prototype.define('tune', (scale, pat) => pat.tune(scale), { composable: true, patternified: true });

View file

@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Pattern, _mod } from '@strudel.cycles/core'; import { register, _mod, parseNumeral } from '@strudel.cycles/core';
export function edo(name) { export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) { if (!/^[1-9]+[0-9]*edo$/.test(name)) {
@ -48,20 +48,17 @@ function xenOffset(xenScale, offset, index = 0) {
} }
// scaleNameOrRatios: string || number[], steps?: number // scaleNameOrRatios: string || number[], steps?: number
Pattern.prototype._xen = function (scaleNameOrRatios, steps) { export const xen = register('xen', function (scaleNameOrRatios, pat) {
return this._asNumber()._withHap((hap) => { return pat.withHap((hap) => {
const scale = getXenScale(scaleNameOrRatios); const scale = getXenScale(scaleNameOrRatios);
steps = steps || scale.length; const frequency = xenOffset(scale, parseNumeral(hap.value));
const frequency = xenOffset(scale, hap.value); return hap.withValue(() => frequency);
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
}); });
}; });
Pattern.prototype.tuning = function (steps) { export const tuning = register('tuning', function (ratios, pat) {
return this._asNumber()._withHap((hap) => { return pat.withHap((hap) => {
const frequency = xenOffset(steps, hap.value); const frequency = xenOffset(ratios, parseNumeral(hap.value));
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' }); return hap.withValue(() => frequency);
}); });
}; });
Pattern.prototype.define('xen', (scale, pat) => pat.xen(scale), { composable: true, patternified: true });
// Pattern.prototype.define('tuning', (scale, pat) => pat.xen(scale), { composable: true, patternified: false });

View file

@ -33,7 +33,6 @@ const supabase = createClient(
const modules = [ const modules = [
import('@strudel.cycles/core'), import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'), import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'), import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'), import('@strudel.cycles/midi'),
@ -46,7 +45,6 @@ const modules = [
]; ];
evalScope( evalScope(
// Tone,
controls, // sadly, this cannot be exported from core direclty controls, // sadly, this cannot be exported from core direclty
{ WebDirt }, { WebDirt },
...modules, ...modules,

View file

@ -19,8 +19,7 @@ import { mini } from '@strudel.cycles/mini/mini.mjs';
// import euclid from '@strudel.cycles/core/euclid.mjs'; // import euclid from '@strudel.cycles/core/euclid.mjs';
// import '@strudel.cycles/tone/tone.mjs'; // import '@strudel.cycles/tone/tone.mjs';
// import '@strudel.cycles/midi/midi.mjs'; // import '@strudel.cycles/midi/midi.mjs';
import '@strudel.cycles/tonal/voicings.mjs'; import * as tonalHelpers from '@strudel.cycles/tonal';
import '@strudel.cycles/tonal/tonal.mjs';
import '@strudel.cycles/xen/xen.mjs'; import '@strudel.cycles/xen/xen.mjs';
// import '@strudel.cycles/xen/tune.mjs'; // import '@strudel.cycles/xen/tune.mjs';
// import '@strudel.cycles/core/euclid.mjs'; // import '@strudel.cycles/core/euclid.mjs';
@ -154,16 +153,19 @@ const audioCtx = {
const getDrawContext = () => canvasCtx; const getDrawContext = () => canvasCtx;
const getAudioContext = () => audioCtx; const getAudioContext = () => audioCtx;
const loadSoundfont = () => {}; const loadSoundfont = () => {};
const loadCsound = () => {};
const loadCSound = () => {};
const loadcsound = () => {};
// TODO: refactor to evalScope // TODO: refactor to evalScope
evalScope( evalScope(
// Tone, // Tone,
strudel, strudel,
strudel.Pattern.prototype.bootstrap(),
toneHelpersMocked, toneHelpersMocked,
uiHelpersMocked, uiHelpersMocked,
controls, controls,
webaudio, webaudio,
tonalHelpers,
/* controls, /* controls,
toneHelpers, toneHelpers,
voicingHelpers, voicingHelpers,
@ -179,6 +181,9 @@ evalScope(
getDrawContext, getDrawContext,
getAudioContext, getAudioContext,
loadSoundfont, loadSoundfont,
loadCSound,
loadCsound,
loadcsound,
Clock: {}, // whatever Clock: {}, // whatever
// Tone, // Tone,
}, },

File diff suppressed because it is too large Load diff

View file

@ -89,10 +89,11 @@ export const whirlyStrudel = `seq(e4, [b2, b3], c4)
export const transposedChordsHacked = `stack( export const transposedChordsHacked = `stack(
"c2 eb2 g2", "c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2) "Cm7".voicings('lefthand').slow(2)
).transpose( ).transpose(
"<1 2 3 2>".slow(2) "<1 2 3 2>".slow(2)
).transpose(5)`; ).transpose(5)`;
// range ['g2','c4']
export const scaleTranspose = `"f2,f3,c4,ab4" export const scaleTranspose = `"f2,f3,c4,ab4"
.scale(seq('F minor', 'F harmonic minor').slow(4)) .scale(seq('F minor', 'F harmonic minor').slow(4))
@ -102,13 +103,14 @@ export const scaleTranspose = `"f2,f3,c4,ab4"
export const struct = `stack( export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]", "c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
"[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2") "[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2")
.voicings(['G3','A4']) .voicings('lefthand')
).slow(4)`; ).slow(4)`;
// range ['G3','A4']
export const magicSofa = `stack( export const magicSofa = `stack(
"<C^7 F^7 ~> <Dm7 G7 A7 ~>" "<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2)) .every(2, fast(2))
.voicings(), .voicings('lefthand'),
"<c2 f2 g2> <d2 g2 a2 e2>" "<c2 f2 g2> <d2 g2 a2 e2>"
).transpose("<0 2 3 4>")`; ).transpose("<0 2 3 4>")`;
// below doesn't work anymore due to constructor cleanup // below doesn't work anymore due to constructor cleanup
@ -156,7 +158,7 @@ const synths = stack(
scaleTranspose(8).early(3/8) scaleTranspose(8).early(3/8)
).apply(thru).tone(keys).mask("<~ x>/16"), ).apply(thru).tone(keys).mask("<~ x>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass), "<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).apply(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4)) "<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings('lefthand').apply(thru).every(2, early(1/8)).tone(keys).mask("<x@7 ~>/8".early(1/4))
) )
stack( stack(
drums.fast(2), drums.fast(2),
@ -197,7 +199,7 @@ export const giantStepsReggae = `stack(
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
) )
.struct("~ [x ~]".fast(4*8)) .struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']), .voicings('lefthand'),
// bass // bass
seq( seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]", "[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
@ -208,6 +210,8 @@ export const giantStepsReggae = `stack(
.struct("x ~".fast(4*8)) .struct("x ~".fast(4*8))
).slow(25).note()`; ).slow(25).note()`;
// range ['E3', 'G4']
// TODO: // TODO:
/* /*
export const xylophoneCalling = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const xylophoneCalling = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@ -351,7 +355,7 @@ stack(
export const bossa = `const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4) export const bossa = `const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4)
stack( 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('lefthand'),
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25) "[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
).note().piano().slow(2)`; ).note().piano().slow(2)`;
@ -443,9 +447,11 @@ export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
const roots = chords.rootNotes(2) const roots = chords.rootNotes(2)
stack( stack(
chords.voicings(['F4', 'A5']).struct( chords.voicings('lefthand').struct(
\` x@2 ~ x ~ ~ ~ x | \` x@2 ~ x ~ ~ ~ x |
x? ~ ~ x@3 ~ x | x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`), x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7") roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano()`; ).slow(2).pianoroll().note().piano()`;
// range ['F4', 'A5']

View file

@ -67,6 +67,8 @@ stack(
`; `;
export const giantSteps = `// John Coltrane - Giant Steps export const giantSteps = `// John Coltrane - Giant Steps
setVoicingRange('lefthand', ['E3', 'G4']);
stack( stack(
// melody // melody
seq( seq(
@ -81,7 +83,7 @@ stack(
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]", "[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]", "Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]" "B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings(['E3', 'G4']), ).voicings('lefthand'),
// bass // bass
seq( seq(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]", "[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]",
@ -130,15 +132,15 @@ const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']) "<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor'])
.slow(8)).struct("[~ x]*2") .slow(8)).struct("[~ x]*2")
.layer( .layer(
scaleTranspose(0).early(0), x=>x.scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8), x=>x.scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4), x=>x.scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8) x=>x.scaleTranspose(8).early(3/8)
).apply(thru).note().apply(keys).mask("<~ x>/16"), ).apply(thru).note().apply(keys).mask("<~ x>/16"),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru)) note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)) .struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500), .s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2)).voicings() "<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2)).voicings('lefthand')
.apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0) .apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0)
.delay(.4).delaytime(.12) .delay(.4).delaytime(.12)
.mask("<x@7 ~>/8".early(1/4)) .mask("<x@7 ~>/8".early(1/4))
@ -245,7 +247,7 @@ export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creat
// by Felix Roos // by Felix Roos
const chords = "<Cm7 Fm7 G7 F#7>"; const chords = "<Cm7 Fm7 G7 F#7>";
stack( stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)"), chords.rootNotes(2).struct("x(4,8,-2)"),
chords.rootNotes(4) chords.rootNotes(4)
.scale(cat('C minor','F dorian','G dorian','F# mixolydian')) .scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
@ -501,7 +503,7 @@ stack(
.gain(.4) // turn down .gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff .cutoff(sine.slow(7).range(300,5000)) // automate cutoff
//.hush() //.hush()
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords ,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice .superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation .add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n" .n() // wrap in "n"
@ -531,7 +533,7 @@ samples({
perc: ['perc/002_perc2.wav'], perc: ['perc/002_perc2.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/'); }, 'github:tidalcycles/Dirt-Samples/master/');
"C^7 Am7 Dm7 G7".slow(2).voicings() "C^7 Am7 Dm7 G7".slow(2).voicings('lefthand')
.stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major')) .stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major'))
.n().slow(4) .n().slow(4)
.s('0040_FluidR3_GM_sf2_file') .s('0040_FluidR3_GM_sf2_file')
@ -626,7 +628,7 @@ stack(
s("mt lt ht").struct("x(3,8)").fast(2).gain(.5).room(.5).sometimes(x=>x.speed(".5")), s("mt lt ht").struct("x(3,8)").fast(2).gain(.5).room(.5).sometimes(x=>x.speed(".5")),
s("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4), s("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4),
// chords // chords
note("[~ Gm7] ~ [~ Dm7] ~".voicings().superimpose(x=>x.add(.1))) note("[~ Gm7] ~ [~ Dm7] ~".voicings('lefthand').superimpose(x=>x.add(.1)))
.s('sawtooth').gain(.5) .s('sawtooth').gain(.5)
.cutoff(perlin.range(400,3000).slow(8)) .cutoff(perlin.range(400,3000).slow(8))
.decay(perlin.range(0.05,.2)).sustain(0) .decay(perlin.range(0.05,.2)).sustain(0)
@ -650,11 +652,12 @@ export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommon
// by Felix Roos // by Felix Roos
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3', samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}}) dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
setVoicingRange('lefthand', ['c3','a4'])
stack( stack(
s('bass').loopAt(8).clip(1), s('bass').loopAt(8).clip(1),
s("bd*2, ~ sd,hh*4"), s("bd*2, ~ sd,hh*4"),
note("Abm7".voicings(['c3','a4']).struct("x(3,8,1)".slow(2))), note("Abm7".voicings('lefthand').struct("x(3,8,1)".slow(2))),
"0 1 2 3".scale('ab4 minor pentatonic') "0 1 2 3".scale('ab4 minor pentatonic')
.superimpose(x=>x.add(.1)) .superimpose(x=>x.add(.1))
.sometimes(x=>x.add(12)) .sometimes(x=>x.add(12))
@ -767,7 +770,7 @@ note("c3 eb3 g3 bb3").palindrome()
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos // by Felix Roos
await csound\` await loadCsound\`
instr CoolSynth instr CoolSynth
iduration = p3 iduration = p3
ifreq = p4 ifreq = p4
@ -806,7 +809,7 @@ export const loungeSponge = `
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc') await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack( stack(
note("<C^7 A7 Dm7 Fm7>/2".voicings()) note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
.cutoff(sine.range(500,2000).round().slow(16)) .cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1') .euclidLegato(3,8).csound('FM1')
, ,
@ -823,7 +826,7 @@ export const arpoon = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.
// "Arpoon" by Felix Roos // "Arpoon" by Felix Roos
await samples('github:tidalcycles/Dirt-Samples/master') await samples('github:tidalcycles/Dirt-Samples/master')
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings() "<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand')
.arp("[0,3] 2 [1,3] 2".fast(3)).legato(2) .arp("[0,3] 2 [1,3] 2".fast(3)).legato(2)
.add(perlin.range(0,0.2)).sub("<0 -12>/8") .add(perlin.range(0,0.2)).sub("<0 -12>/8")
.note().cutoff(perlin.range(500,4000)).resonance(12) .note().cutoff(perlin.range(500,4000)).resonance(12)

View file

@ -1,22 +1,5 @@
// Vitest Snapshot v1 // Vitest Snapshot v1
exports[`runs examples > example "_apply" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:C3 ]",
"[ 0/1 → 1/1 | note:Eb3 ]",
"[ 0/1 → 1/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:Eb3 ]",
"[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:Bb3 ]",
"[ 2/1 → 3/1 | note:G3 ]",
"[ 2/1 → 3/1 | note:Bb3 ]",
"[ 2/1 → 3/1 | note:D4 ]",
"[ 3/1 → 4/1 | note:C3 ]",
"[ 3/1 → 4/1 | note:Eb3 ]",
"[ 3/1 → 4/1 | note:G3 ]",
]
`;
exports[`runs examples > example "accelerate" example index 0 1`] = ` exports[`runs examples > example "accelerate" example index 0 1`] = `
[ [
"[ (0/1 → 1/1) ⇝ 2/1 | s:sax accelerate:0 ]", "[ (0/1 → 1/1) ⇝ 2/1 | s:sax accelerate:0 ]",
@ -60,6 +43,31 @@ exports[`runs examples > example "add" example index 1 1`] = `
] ]
`; `;
exports[`runs examples > example "addVoicings" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:E3 ]",
"[ 0/1 → 1/1 | note:A3 ]",
"[ 0/1 → 1/1 | note:D4 ]",
"[ 0/1 → 1/1 | note:G4 ]",
"[ 0/1 → 1/1 | note:B4 ]",
"[ 1/1 → 2/1 | note:C#3 ]",
"[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:B3 ]",
"[ 1/1 → 2/1 | note:E4 ]",
"[ 1/1 → 2/1 | note:A4 ]",
"[ 2/1 → 3/1 | note:D3 ]",
"[ 2/1 → 3/1 | note:G3 ]",
"[ 2/1 → 3/1 | note:C4 ]",
"[ 2/1 → 3/1 | note:F4 ]",
"[ 2/1 → 3/1 | note:A4 ]",
"[ 3/1 → 4/1 | note:F3 ]",
"[ 3/1 → 4/1 | note:B3 ]",
"[ 3/1 → 4/1 | note:E4 ]",
"[ 3/1 → 4/1 | note:A4 ]",
"[ 3/1 → 4/1 | note:D5 ]",
]
`;
exports[`runs examples > example "almostAlways" example index 0 1`] = ` exports[`runs examples > example "almostAlways" example index 0 1`] = `
[ [
"[ 0/1 → 1/8 | s:hh speed:0.5 ]", "[ 0/1 → 1/8 | s:hh speed:0.5 ]",
@ -232,6 +240,23 @@ exports[`runs examples > example "amp" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "apply" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:C3 ]",
"[ 0/1 → 1/1 | note:Eb3 ]",
"[ 0/1 → 1/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:Eb3 ]",
"[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:Bb3 ]",
"[ 2/1 → 3/1 | note:G3 ]",
"[ 2/1 → 3/1 | note:Bb3 ]",
"[ 2/1 → 3/1 | note:D4 ]",
"[ 3/1 → 4/1 | note:C3 ]",
"[ 3/1 → 4/1 | note:Eb3 ]",
"[ 3/1 → 4/1 | note:G3 ]",
]
`;
exports[`runs examples > example "bandf" example index 0 1`] = ` exports[`runs examples > example "bandf" example index 0 1`] = `
[ [
"[ 0/1 → 1/2 | s:bd bandf:1000 ]", "[ 0/1 → 1/2 | s:bd bandf:1000 ]",
@ -605,19 +630,21 @@ exports[`runs examples > example "degrade" example index 0 1`] = `
exports[`runs examples > example "degrade" example index 1 1`] = ` exports[`runs examples > example "degrade" example index 1 1`] = `
[ [
"[ 0/1 → 1/8 | s:hh ]", "[ 1/8 → 1/4 | s:hh ]",
"[ 3/8 → 1/2 | s:hh ]", "[ 3/4 → 7/8 | s:hh ]",
"[ 1/2 → 5/8 | s:hh ]", "[ 1/1 → 9/8 | s:hh ]",
"[ 5/8 → 3/4 | s:hh ]", "[ 9/8 → 5/4 | s:hh ]",
"[ 5/4 → 11/8 | s:hh ]", "[ 5/4 → 11/8 | s:hh ]",
"[ 3/2 → 13/8 | s:hh ]", "[ 11/8 → 3/2 | s:hh ]",
"[ 13/8 → 7/4 | s:hh ]", "[ 7/4 → 15/8 | s:hh ]",
"[ 2/1 → 17/8 | s:hh ]",
"[ 9/4 → 19/8 | s:hh ]", "[ 9/4 → 19/8 | s:hh ]",
"[ 19/8 → 5/2 | s:hh ]", "[ 21/8 → 11/4 | s:hh ]",
"[ 5/2 → 21/8 | s:hh ]", "[ 11/4 → 23/8 | s:hh ]",
"[ 3/1 → 25/8 | s:hh ]", "[ 25/8 → 13/4 | s:hh ]",
"[ 13/4 → 27/8 | s:hh ]", "[ 13/4 → 27/8 | s:hh ]",
"[ 29/8 → 15/4 | s:hh ]", "[ 27/8 → 7/2 | s:hh ]",
"[ 15/4 → 31/8 | s:hh ]",
] ]
`; `;
@ -656,6 +683,7 @@ exports[`runs examples > example "degradeBy" example index 1 1`] = `
"[ 1/8 → 1/4 | s:hh ]", "[ 1/8 → 1/4 | s:hh ]",
"[ 1/4 → 3/8 | s:hh ]", "[ 1/4 → 3/8 | s:hh ]",
"[ 3/8 → 1/2 | s:hh ]", "[ 3/8 → 1/2 | s:hh ]",
"[ 1/2 → 5/8 | s:hh ]",
"[ 5/8 → 3/4 | s:hh ]", "[ 5/8 → 3/4 | s:hh ]",
"[ 3/4 → 7/8 | s:hh ]", "[ 3/4 → 7/8 | s:hh ]",
"[ 7/8 → 1/1 | s:hh ]", "[ 7/8 → 1/1 | s:hh ]",
@ -663,20 +691,22 @@ exports[`runs examples > example "degradeBy" example index 1 1`] = `
"[ 9/8 → 5/4 | s:hh ]", "[ 9/8 → 5/4 | s:hh ]",
"[ 5/4 → 11/8 | s:hh ]", "[ 5/4 → 11/8 | s:hh ]",
"[ 11/8 → 3/2 | s:hh ]", "[ 11/8 → 3/2 | s:hh ]",
"[ 3/2 → 13/8 | s:hh ]",
"[ 13/8 → 7/4 | s:hh ]", "[ 13/8 → 7/4 | s:hh ]",
"[ 7/4 → 15/8 | s:hh ]", "[ 7/4 → 15/8 | s:hh ]",
"[ 15/8 → 2/1 | s:hh ]",
"[ 2/1 → 17/8 | s:hh ]", "[ 2/1 → 17/8 | s:hh ]",
"[ 17/8 → 9/4 | s:hh ]",
"[ 9/4 → 19/8 | s:hh ]", "[ 9/4 → 19/8 | s:hh ]",
"[ 19/8 → 5/2 | s:hh ]",
"[ 5/2 → 21/8 | s:hh ]",
"[ 21/8 → 11/4 | s:hh ]", "[ 21/8 → 11/4 | s:hh ]",
"[ 11/4 → 23/8 | s:hh ]", "[ 11/4 → 23/8 | s:hh ]",
"[ 3/1 → 25/8 | s:hh ]", "[ 23/8 → 3/1 | s:hh ]",
"[ 25/8 → 13/4 | s:hh ]", "[ 25/8 → 13/4 | s:hh ]",
"[ 13/4 → 27/8 | s:hh ]", "[ 13/4 → 27/8 | s:hh ]",
"[ 27/8 → 7/2 | s:hh ]",
"[ 7/2 → 29/8 | s:hh ]",
"[ 29/8 → 15/4 | s:hh ]", "[ 29/8 → 15/4 | s:hh ]",
"[ 15/4 → 31/8 | s:hh ]", "[ 15/4 → 31/8 | s:hh ]",
"[ 31/8 → 4/1 | s:hh ]",
] ]
`; `;
@ -2352,6 +2382,15 @@ exports[`runs examples > example "room" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "rootNotes" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:C2 ]",
"[ 1/1 → 2/1 | note:A2 ]",
"[ 2/1 → 3/1 | note:D2 ]",
"[ 3/1 → 4/1 | note:G2 ]",
]
`;
exports[`runs examples > example "round" example index 0 1`] = ` exports[`runs examples > example "round" example index 0 1`] = `
[ [
"[ 0/1 → 1/3 | note:D3 ]", "[ 0/1 → 1/3 | note:D3 ]",
@ -2645,12 +2684,12 @@ exports[`runs examples > example "slowcat" example index 0 1`] = `
exports[`runs examples > example "someCycles" example index 0 1`] = ` exports[`runs examples > example "someCycles" example index 0 1`] = `
[ [
"[ 1/1 → 9/8 | s:hh ]",
"[ 11/8 → 3/2 | s:hh ]",
"[ 7/4 → 15/8 | s:hh ]",
"[ 0/1 → 1/8 | s:hh speed:0.5 ]", "[ 0/1 → 1/8 | s:hh speed:0.5 ]",
"[ 3/8 → 1/2 | s:hh speed:0.5 ]", "[ 3/8 → 1/2 | s:hh speed:0.5 ]",
"[ 3/4 → 7/8 | s:hh speed:0.5 ]", "[ 3/4 → 7/8 | s:hh speed:0.5 ]",
"[ 1/1 → 9/8 | s:hh ]",
"[ 11/8 → 3/2 | s:hh ]",
"[ 7/4 → 15/8 | s:hh ]",
"[ 2/1 → 17/8 | s:hh speed:0.5 ]", "[ 2/1 → 17/8 | s:hh speed:0.5 ]",
"[ 19/8 → 5/2 | s:hh speed:0.5 ]", "[ 19/8 → 5/2 | s:hh speed:0.5 ]",
"[ 11/4 → 23/8 | s:hh speed:0.5 ]", "[ 11/4 → 23/8 | s:hh speed:0.5 ]",
@ -2680,19 +2719,19 @@ exports[`runs examples > example "someCyclesBy" example index 0 1`] = `
exports[`runs examples > example "sometimes" example index 0 1`] = ` exports[`runs examples > example "sometimes" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | s:hh ]", "[ 0/1 → 1/4 | s:hh ]",
"[ 1/1 → 5/4 | s:hh ]",
"[ 7/4 → 2/1 | s:hh ]",
"[ 2/1 → 9/4 | s:hh ]",
"[ 9/4 → 5/2 | s:hh ]",
"[ 7/2 → 15/4 | s:hh ]",
"[ 15/4 → 4/1 | s:hh ]",
"[ 1/4 → 1/2 | s:hh speed:0.5 ]", "[ 1/4 → 1/2 | s:hh speed:0.5 ]",
"[ 1/2 → 3/4 | s:hh speed:0.5 ]", "[ 1/2 → 3/4 | s:hh speed:0.5 ]",
"[ 3/4 → 1/1 | s:hh speed:0.5 ]", "[ 3/4 → 1/1 | s:hh speed:0.5 ]",
"[ 1/1 → 5/4 | s:hh ]",
"[ 7/4 → 2/1 | s:hh ]",
"[ 5/4 → 3/2 | s:hh speed:0.5 ]", "[ 5/4 → 3/2 | s:hh speed:0.5 ]",
"[ 3/2 → 7/4 | s:hh speed:0.5 ]", "[ 3/2 → 7/4 | s:hh speed:0.5 ]",
"[ 2/1 → 9/4 | s:hh ]",
"[ 9/4 → 5/2 | s:hh ]",
"[ 5/2 → 11/4 | s:hh speed:0.5 ]", "[ 5/2 → 11/4 | s:hh speed:0.5 ]",
"[ 11/4 → 3/1 | s:hh speed:0.5 ]", "[ 11/4 → 3/1 | s:hh speed:0.5 ]",
"[ 7/2 → 15/4 | s:hh ]",
"[ 15/4 → 4/1 | s:hh ]",
"[ 3/1 → 13/4 | s:hh speed:0.5 ]", "[ 3/1 → 13/4 | s:hh speed:0.5 ]",
"[ 13/4 → 7/2 | s:hh speed:0.5 ]", "[ 13/4 → 7/2 | s:hh speed:0.5 ]",
] ]
@ -3080,68 +3119,20 @@ exports[`runs examples > example "velocity" example index 0 1`] = `
exports[`runs examples > example "voicings" example index 0 1`] = ` exports[`runs examples > example "voicings" example index 0 1`] = `
[ [
"[ 0/1 → 1/1 | note:B3 ]", "[ 0/1 → 1/1 | note:B3 ]",
"[ 0/1 → 1/1 | note:B3 ]",
"[ 0/1 → 1/1 | note:B3 ]",
"[ 0/1 → 1/1 | note:B3 ]",
"[ 0/1 → 1/1 | note:D4 ]",
"[ 0/1 → 1/1 | note:D4 ]",
"[ 0/1 → 1/1 | note:D4 ]",
"[ 0/1 → 1/1 | note:D4 ]", "[ 0/1 → 1/1 | note:D4 ]",
"[ 0/1 → 1/1 | note:E4 ]", "[ 0/1 → 1/1 | note:E4 ]",
"[ 0/1 → 1/1 | note:E4 ]",
"[ 0/1 → 1/1 | note:E4 ]",
"[ 0/1 → 1/1 | note:E4 ]",
"[ 0/1 → 1/1 | note:G4 ]",
"[ 0/1 → 1/1 | note:G4 ]",
"[ 0/1 → 1/1 | note:G4 ]",
"[ 0/1 → 1/1 | note:G4 ]", "[ 0/1 → 1/1 | note:G4 ]",
"[ 1/1 → 2/1 | note:G3 ]", "[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:G3 ]",
"[ 1/1 → 2/1 | note:B3 ]",
"[ 1/1 → 2/1 | note:B3 ]",
"[ 1/1 → 2/1 | note:B3 ]",
"[ 1/1 → 2/1 | note:B3 ]", "[ 1/1 → 2/1 | note:B3 ]",
"[ 1/1 → 2/1 | note:C#4 ]", "[ 1/1 → 2/1 | note:C#4 ]",
"[ 1/1 → 2/1 | note:C#4 ]",
"[ 1/1 → 2/1 | note:C#4 ]",
"[ 1/1 → 2/1 | note:C#4 ]",
"[ 1/1 → 2/1 | note:F#4 ]",
"[ 1/1 → 2/1 | note:F#4 ]",
"[ 1/1 → 2/1 | note:F#4 ]",
"[ 1/1 → 2/1 | note:F#4 ]", "[ 1/1 → 2/1 | note:F#4 ]",
"[ 2/1 → 3/1 | note:F3 ]", "[ 2/1 → 3/1 | note:F3 ]",
"[ 2/1 → 3/1 | note:F3 ]",
"[ 2/1 → 3/1 | note:F3 ]",
"[ 2/1 → 3/1 | note:F3 ]",
"[ 2/1 → 3/1 | note:A3 ]",
"[ 2/1 → 3/1 | note:A3 ]",
"[ 2/1 → 3/1 | note:A3 ]",
"[ 2/1 → 3/1 | note:A3 ]", "[ 2/1 → 3/1 | note:A3 ]",
"[ 2/1 → 3/1 | note:C4 ]", "[ 2/1 → 3/1 | note:C4 ]",
"[ 2/1 → 3/1 | note:C4 ]",
"[ 2/1 → 3/1 | note:C4 ]",
"[ 2/1 → 3/1 | note:C4 ]",
"[ 2/1 → 3/1 | note:E4 ]",
"[ 2/1 → 3/1 | note:E4 ]",
"[ 2/1 → 3/1 | note:E4 ]",
"[ 2/1 → 3/1 | note:E4 ]", "[ 2/1 → 3/1 | note:E4 ]",
"[ 3/1 → 4/1 | note:F3 ]", "[ 3/1 → 4/1 | note:F3 ]",
"[ 3/1 → 4/1 | note:F3 ]",
"[ 3/1 → 4/1 | note:F3 ]",
"[ 3/1 → 4/1 | note:F3 ]",
"[ 3/1 → 4/1 | note:A3 ]",
"[ 3/1 → 4/1 | note:A3 ]",
"[ 3/1 → 4/1 | note:A3 ]",
"[ 3/1 → 4/1 | note:A3 ]", "[ 3/1 → 4/1 | note:A3 ]",
"[ 3/1 → 4/1 | note:B3 ]", "[ 3/1 → 4/1 | note:B3 ]",
"[ 3/1 → 4/1 | note:B3 ]",
"[ 3/1 → 4/1 | note:B3 ]",
"[ 3/1 → 4/1 | note:B3 ]",
"[ 3/1 → 4/1 | note:E4 ]",
"[ 3/1 → 4/1 | note:E4 ]",
"[ 3/1 → 4/1 | note:E4 ]",
"[ 3/1 → 4/1 | note:E4 ]", "[ 3/1 → 4/1 | note:E4 ]",
"[ 0/1 → 1/1 | note:C3 ]", "[ 0/1 → 1/1 | note:C3 ]",
"[ 1/1 → 2/1 | note:A2 ]", "[ 1/1 → 2/1 | note:A2 ]",

View file

@ -35,7 +35,7 @@ s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.s('sawtooth') // waveform .s('sawtooth') // waveform
.gain(.4) // turn down .gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff .cutoff(sine.slow(7).range(300,5000)) // automate cutoff
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords ,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice .superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation .add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n" .n() // wrap in "n"
@ -755,9 +755,7 @@ Transposes notes inside the scale by the number of steps:
Turns chord symbols into voicings, using the smoothest voice leading possible: Turns chord symbols into voicings, using the smoothest voice leading possible:
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()`} /> <MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()`} />
<!-- TODO: use voicing collection as first param + patternify. -->
### rootNotes(octave = 2) ### rootNotes(octave = 2)
@ -769,7 +767,7 @@ Together with layer, struct and voicings, this can be used to create a basic bac
<MiniRepl <MiniRepl
tune={`"<C^7 A7b13 Dm7 G7>".layer( tune={`"<C^7 A7b13 Dm7 G7>".layer(
x => x.voicings(['d3','g4']).struct("~ x").note(), x => x.voicings('lefthand').struct("~ x").note(),
x => x.rootNotes(2).note().s('sawtooth').cutoff(800) x => x.rootNotes(2).note().s('sawtooth').cutoff(800)
)`} )`}
/> />
@ -790,7 +788,7 @@ Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (L
If no outputName is given, it uses the first midi output it finds. If no outputName is given, it uses the first midi output it finds.
<MiniRepl <MiniRepl
tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>") tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>")
.midi()`} .midi()`}
/> />