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/>.
*/
import { isPattern, Pattern } from './index.mjs';
import { isPattern } from './index.mjs';
let scoped = false;
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);
}
});
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
Object.assign(globalThis, ...modules);
};
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() {
return this.fmap(parseNumeral);
}
@ -1277,9 +1262,9 @@ export function register(name, func) {
args = args.map(reify);
// For methods that take a single argument (plus 'this'), allow
// multiple arguments but sequence them
if (arity == 2 && args.length != 1) {
if (arity === 2 && args.length !== 1) {
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}.`);
}
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:
* @name _apply
* @name apply
* @memberof Pattern
* @example
* "<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) {
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 { 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 { id } from './util.mjs';
@ -258,9 +258,9 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x));
};
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
);
/**
* Randomly removes events from the pattern by a given amount.
@ -276,9 +276,9 @@ Pattern.prototype._degradeByWith = function (withPat, x) {
* @example
* s("[hh?0.2]*8")
*/
Pattern.prototype._degradeBy = function (x) {
return this._degradeByWith(rand, x);
};
export const degradeBy = register('degradeBy', function (x, pat) {
return pat._degradeByWith(rand, x);
});
/**
*
@ -292,9 +292,7 @@ Pattern.prototype._degradeBy = function (x) {
* @example
* s("[hh?]*8")
*/
Pattern.prototype.degrade = function () {
return this._degradeBy(0.5);
};
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
/**
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
@ -309,26 +307,14 @@ Pattern.prototype.degrade = function () {
* @example
* s("hh*8").undegradeBy(0.2)
*/
Pattern.prototype._undegradeBy = function (x) {
return this._degradeByWith(
export const undegradeBy = register('undegradeBy', function (x, pat) {
return pat._degradeByWith(
rand.fmap((r) => 1 - r),
x,
);
};
});
Pattern.prototype.undegrade = function () {
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);
}; */
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
/**
*
@ -343,24 +329,12 @@ Pattern.prototype._sometimesBy = function (x, func) {
* @example
* 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
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;
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
return reify(patx)
.fmap((x) => pat._sometimesByPre(x, func))
.fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
.innerJoin();
};
});
/**
*
@ -373,20 +347,9 @@ Pattern.prototype.sometimesByPre = function (patx, func) {
* @example
* s("hh*4").sometimes(x=>x.speed("0.5"))
*/
Pattern.prototype.sometimes = function (func) {
return this._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)),
);
};
export const sometimes = register('sometimes', function (func, pat) {
return pat._sometimesBy(0.5, func);
});
/**
*
@ -401,12 +364,17 @@ Pattern.prototype._someCyclesBy = function (x, func) {
* @example
* 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)
.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();
};
});
/**
*
@ -418,9 +386,9 @@ Pattern.prototype.someCyclesBy = function (patx, func) {
* @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/
Pattern.prototype.someCycles = function (func) {
return this._someCyclesBy(0.5, func);
};
export const someCycles = register('someCycles', function (func, pat) {
return pat._someCyclesBy(0.5, func);
});
/**
*
@ -432,9 +400,9 @@ Pattern.prototype.someCycles = function (func) {
* @example
* s("hh*8").often(x=>x.speed("0.5"))
*/
Pattern.prototype.often = function (func) {
return this.sometimesBy(0.75, func);
};
export const often = register('often', function (func, pat) {
return pat.sometimesBy(0.75, func);
});
/**
*
@ -446,9 +414,9 @@ Pattern.prototype.often = function (func) {
* @example
* s("hh*8").rarely(x=>x.speed("0.5"))
*/
Pattern.prototype.rarely = function (func) {
return this.sometimesBy(0.25, func);
};
export const rarely = register('rarely', function (func, pat) {
return pat.sometimesBy(0.25, func);
});
/**
*
@ -460,9 +428,9 @@ Pattern.prototype.rarely = function (func) {
* @example
* s("hh*8").almostNever(x=>x.speed("0.5"))
*/
Pattern.prototype.almostNever = function (func) {
return this.sometimesBy(0.1, func);
};
export const almostNever = register('almostNever', function (func, pat) {
return pat.sometimesBy(0.1, func);
});
/**
*
@ -474,9 +442,9 @@ Pattern.prototype.almostNever = function (func) {
* @example
* s("hh*8").almostAlways(x=>x.speed("0.5"))
*/
Pattern.prototype.almostAlways = function (func) {
return this.sometimesBy(0.9, func);
};
export const almostAlways = register('almostAlways', function (func, pat) {
return pat.sometimesBy(0.9, func);
});
/**
*
@ -488,9 +456,9 @@ Pattern.prototype.almostAlways = function (func) {
* @example
* s("hh*8").never(x=>x.speed("0.5"))
*/
Pattern.prototype.never = function (func) {
return this;
};
export const never = register('never', function (_, pat) {
return pat;
});
/**
*
@ -502,8 +470,6 @@ Pattern.prototype.never = function (func) {
* @example
* s("hh*8").always(x=>x.speed("0.5"))
*/
Pattern.prototype.always = function (func) {
return func(this);
};
Pattern.prototype.patternified.push('degradeBy', 'undegradeBy');
export const always = register('always', function (func, pat) {
return func(pat);
});

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/>.
*/
import { Pattern, patternify2, reify } from './index.mjs';
import { register } from './index.mjs';
let synth;
try {
@ -16,7 +16,7 @@ try {
let allVoices = synth?.getVoices();
// console.log('voices', allVoices);
function speak(words, lang, voice) {
function triggerSpeech(words, lang, voice) {
synth.cancel();
const utterance = new SpeechSynthesisUtterance(words);
utterance.lang = lang;
@ -31,12 +31,8 @@ function speak(words, lang, voice) {
speechSynthesis.speak(utterance);
}
Pattern.prototype._speak = function (lang, voice) {
return this.onTrigger((_, hap) => {
speak(hap.value, lang, voice);
export const speak = register('speak', function (lang, voice, pat) {
return pat.onTrigger((_, hap) => {
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 csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw';
@ -6,12 +6,24 @@ import presetsOrc from './presets.orc?raw';
let csoundLoader, _csound;
// triggers given instrument name using csound.
Pattern.prototype._csound = function (instrument) {
// initializes csound + can be used to reevaluate given instrument code
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';
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)
return this.onTrigger((time, hap) => {
return pat.onTrigger((time, hap) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
@ -40,20 +52,7 @@ Pattern.prototype._csound = function (instrument) {
const msg = `i ${params.join(' ')}`;
_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) {
const [msg] = args;

View File

@ -225,9 +225,8 @@ function isPatternArg(parents) {
function hasModifierCall(parent) {
// TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier..
// need all prototypes of Pattern
return (
parent?.type === 'StaticMemberExpression' && Object.keys(Pattern.prototype.composable).includes(parent.property)
);
return parent?.type === 'StaticMemberExpression';
// && Object.keys(Pattern.prototype.composable).includes(parent.property)
}
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 { 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;
function _nextSeed() {
return _seedState++;
}
} */
const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i];
@ -30,10 +30,28 @@ const applyOptions = (parent) => (pat, i) => {
case 'bjorklund':
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
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(
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': "%"
}
console.warn(`operator "${operator.type_}" not implemented`);
@ -96,7 +114,9 @@ export function patternifyAST(ast) {
return stack(...children);
}
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);
if (!weightedChildren && alignment === 't') {
@ -178,11 +198,6 @@ export const h = (string) => {
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) {
if (typeof thing === 'string') {
return mini(thing);

View File

@ -77,7 +77,7 @@ describe('mini', () => {
expect(haps.length < 230).toBe(true);
// '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 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.
@ -103,5 +103,5 @@ describe('mini', () => {
// PRNG, this test should succeed
expect(chisq <= 15.086).toBe(true);
// 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 './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 { Pattern, _mod } from '@strudel.cycles/core';
import { register, _mod } from '@strudel.cycles/core';
// transpose note inside scale by offset steps
// 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()
*/
Pattern.prototype._transpose = function (intervalOrSemitones) {
return this.withHap((hap) => {
export const transpose = register('transpose', function (intervalOrSemitones, pat) {
return pat.withHap((hap) => {
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones /* as number */)
: 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
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))
// 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
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
@ -110,8 +108,8 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
* .note()
*/
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
return this.withHap((hap) => {
export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) {
return pat.withHap((hap) => {
if (!hap.context.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));
});
};
});
/**
* 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()
*/
Pattern.prototype._scale = function (scale /* : string */) {
return this.withHap((hap) => {
export const scale = register('scale', function (scale /* : string */, pat) {
return pat.withHap((hap) => {
let note = hap.value;
const asNumber = Number(note);
if (!isNaN(asNumber)) {
@ -152,8 +150,4 @@ Pattern.prototype._scale = function (scale /* : string */) {
}
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/>.
*/
import { Pattern as _Pattern, stack, Hap, reify } from '@strudel.cycles/core';
import { stack, register } from '@strudel.cycles/core';
import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
dictionaryVoicing({
export const voicingRegistry = {
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,
dictionary: lefthand,
dictionary,
range,
picker: minTopNoteDiff,
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
* @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
* @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;
if (!range?.length) {
// allows to pass empty array, if too lazy to specify range
range = ['F3', 'A4'];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing).withContext(() => ({
locations: event.context.locations || [],
}));
});
};
let lastVoicing; // this now has to be global until another solution is found :-/
// it used to be local to the voicings function at evaluation time
// but since register will patternify by default, means that
// the function is called over and over again, resetting the lastVoicing variables
export const voicings = register('voicings', function (dictionary, pat) {
return pat
.fmap((value) => {
lastVoicing = getVoicing(value, dictionary, lastVoicing);
return stack(...lastVoicing);
})
.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];
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/>.
*/
import { Pattern } from '@strudel.cycles/core';
import { register } from '@strudel.cycles/core';
import * as _Tone from 'tone';
// import Tone from here, to make sure to get the same AudioContext
export const Tone = _Tone;
const {
AutoFilter,
Destination,
Filter,
Gain,
isNote,
Synth,
PolySynth,
MembraneSynth,
@ -49,8 +46,8 @@ export const getDefaultSynth = () => {
// https://www.charlie-roberts.com/gibberish/playground/
// with this function, you can play the pattern with any tone synth
Pattern.prototype.tone = function (instrument) {
return this.onTrigger((time, hap) => {
export const tone = register('tone', function (instrument, pat) {
return pat.onTrigger((time, hap) => {
let note;
let velocity = hap.context?.velocity ?? 0.75;
if (instrument instanceof PluckSynth) {
@ -74,9 +71,7 @@ Pattern.prototype.tone = function (instrument) {
instrument.triggerAttackRelease(note, hap.duration.valueOf(), time, velocity);
}
});
};
Pattern.prototype.define('tone', (type, pat) => pat.tone(type), { composable: true, patternified: false });
});
// synth helpers
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/>.
*/
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import { fromMidi, logger, toMidi } from '@strudel.cycles/core';
import './feedbackdelay.mjs';

View File

@ -1,2 +1,4 @@
import './xen.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 { 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();
if (!tune.isValidScale(scale)) {
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
}
tune.loadScale(scale);
tune.tonicize(tonic);
return this._asNumber()._withHap((hap) => {
return hap.withValue(() => tune.note(hap.value)).setContext({ ...hap.context, type: 'frequency' });
tune.tonicize(1);
return pat.withHap((hap) => {
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/>.
*/
import { Pattern, _mod } from '@strudel.cycles/core';
import { register, _mod, parseNumeral } from '@strudel.cycles/core';
export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
@ -48,20 +48,17 @@ function xenOffset(xenScale, offset, index = 0) {
}
// scaleNameOrRatios: string || number[], steps?: number
Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
return this._asNumber()._withHap((hap) => {
export const xen = register('xen', function (scaleNameOrRatios, pat) {
return pat.withHap((hap) => {
const scale = getXenScale(scaleNameOrRatios);
steps = steps || scale.length;
const frequency = xenOffset(scale, hap.value);
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
const frequency = xenOffset(scale, parseNumeral(hap.value));
return hap.withValue(() => frequency);
});
};
});
Pattern.prototype.tuning = function (steps) {
return this._asNumber()._withHap((hap) => {
const frequency = xenOffset(steps, hap.value);
return hap.withValue(() => frequency).setContext({ ...hap.context, type: 'frequency' });
export const tuning = register('tuning', function (ratios, pat) {
return pat.withHap((hap) => {
const frequency = xenOffset(ratios, parseNumeral(hap.value));
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 = [
import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
@ -46,7 +45,6 @@ const modules = [
];
evalScope(
// Tone,
controls, // sadly, this cannot be exported from core direclty
{ WebDirt },
...modules,

View File

@ -19,8 +19,7 @@ import { mini } from '@strudel.cycles/mini/mini.mjs';
// import euclid from '@strudel.cycles/core/euclid.mjs';
// import '@strudel.cycles/tone/tone.mjs';
// import '@strudel.cycles/midi/midi.mjs';
import '@strudel.cycles/tonal/voicings.mjs';
import '@strudel.cycles/tonal/tonal.mjs';
import * as tonalHelpers from '@strudel.cycles/tonal';
import '@strudel.cycles/xen/xen.mjs';
// import '@strudel.cycles/xen/tune.mjs';
// import '@strudel.cycles/core/euclid.mjs';
@ -154,16 +153,19 @@ const audioCtx = {
const getDrawContext = () => canvasCtx;
const getAudioContext = () => audioCtx;
const loadSoundfont = () => {};
const loadCsound = () => {};
const loadCSound = () => {};
const loadcsound = () => {};
// TODO: refactor to evalScope
evalScope(
// Tone,
strudel,
strudel.Pattern.prototype.bootstrap(),
toneHelpersMocked,
uiHelpersMocked,
controls,
webaudio,
tonalHelpers,
/* controls,
toneHelpers,
voicingHelpers,
@ -179,6 +181,9 @@ evalScope(
getDrawContext,
getAudioContext,
loadSoundfont,
loadCSound,
loadCsound,
loadcsound,
Clock: {}, // whatever
// 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(
"c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2)
"Cm7".voicings('lefthand').slow(2)
).transpose(
"<1 2 3 2>".slow(2)
).transpose(5)`;
// range ['g2','c4']
export const scaleTranspose = `"f2,f3,c4,ab4"
.scale(seq('F minor', 'F harmonic minor').slow(4))
@ -102,13 +103,14 @@ export const scaleTranspose = `"f2,f3,c4,ab4"
export const struct = `stack(
"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")
.voicings(['G3','A4'])
.voicings('lefthand')
).slow(4)`;
// range ['G3','A4']
export const magicSofa = `stack(
"<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2))
.voicings(),
.voicings('lefthand'),
"<c2 f2 g2> <d2 g2 a2 e2>"
).transpose("<0 2 3 4>")`;
// below doesn't work anymore due to constructor cleanup
@ -156,7 +158,7 @@ const synths = stack(
scaleTranspose(8).early(3/8)
).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),
"<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(
drums.fast(2),
@ -197,7 +199,7 @@ export const giantStepsReggae = `stack(
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
)
.struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']),
.voicings('lefthand'),
// bass
seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
@ -208,6 +210,8 @@ export const giantStepsReggae = `stack(
.struct("x ~".fast(4*8))
).slow(25).note()`;
// range ['E3', 'G4']
// TODO:
/*
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)
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)
).note().piano().slow(2)`;
@ -443,9 +447,11 @@ export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
const roots = chords.rootNotes(2)
stack(
chords.voicings(['F4', 'A5']).struct(
chords.voicings('lefthand').struct(
\` x@2 ~ x ~ ~ ~ x |
x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano()`;
// range ['F4', 'A5']

View File

@ -67,6 +67,8 @@ stack(
`;
export const giantSteps = `// John Coltrane - Giant Steps
setVoicingRange('lefthand', ['E3', 'G4']);
stack(
// melody
seq(
@ -81,7 +83,7 @@ stack(
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).voicings(['E3', 'G4']),
).voicings('lefthand'),
// bass
seq(
"[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'])
.slow(8)).struct("[~ x]*2")
.layer(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
x=>x.scaleTranspose(0).early(0),
x=>x.scaleTranspose(2).early(1/8),
x=>x.scaleTranspose(7).early(1/4),
x=>x.scaleTranspose(8).early(3/8)
).apply(thru).note().apply(keys).mask("<~ x>/16"),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.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)
.delay(.4).delaytime(.12)
.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
const chords = "<Cm7 Fm7 G7 F#7>";
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(4)
.scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
@ -501,7 +503,7 @@ stack(
.gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
//.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
.add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n"
@ -531,7 +533,7 @@ samples({
perc: ['perc/002_perc2.wav'],
}, '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'))
.n().slow(4)
.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("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4),
// chords
note("[~ Gm7] ~ [~ Dm7] ~".voicings().superimpose(x=>x.add(.1)))
note("[~ Gm7] ~ [~ Dm7] ~".voicings('lefthand').superimpose(x=>x.add(.1)))
.s('sawtooth').gain(.5)
.cutoff(perlin.range(400,3000).slow(8))
.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
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
setVoicingRange('lefthand', ['c3','a4'])
stack(
s('bass').loopAt(8).clip(1),
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')
.superimpose(x=>x.add(.1))
.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/
// by Felix Roos
await csound\`
await loadCsound\`
instr CoolSynth
iduration = p3
ifreq = p4
@ -806,7 +809,7 @@ export const loungeSponge = `
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
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))
.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
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)
.add(perlin.range(0,0.2)).sub("<0 -12>/8")
.note().cutoff(perlin.range(500,4000)).resonance(12)

View File

@ -1,22 +1,5 @@
// 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`] = `
[
"[ (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`] = `
[
"[ 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`] = `
[
"[ 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`] = `
[
"[ 0/1 → 1/8 | s:hh ]",
"[ 3/8 → 1/2 | s:hh ]",
"[ 1/2 → 5/8 | s:hh ]",
"[ 5/8 → 3/4 | s:hh ]",
"[ 1/8 → 1/4 | s:hh ]",
"[ 3/4 → 7/8 | s:hh ]",
"[ 1/1 → 9/8 | s:hh ]",
"[ 9/8 → 5/4 | s:hh ]",
"[ 5/4 → 11/8 | s:hh ]",
"[ 3/2 → 13/8 | s:hh ]",
"[ 13/8 → 7/4 | s:hh ]",
"[ 11/8 → 3/2 | s:hh ]",
"[ 7/4 → 15/8 | s:hh ]",
"[ 2/1 → 17/8 | s:hh ]",
"[ 9/4 → 19/8 | s:hh ]",
"[ 19/8 → 5/2 | s:hh ]",
"[ 5/2 → 21/8 | s:hh ]",
"[ 3/1 → 25/8 | s:hh ]",
"[ 21/8 → 11/4 | s:hh ]",
"[ 11/4 → 23/8 | s:hh ]",
"[ 25/8 → 13/4 | 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/4 → 3/8 | s:hh ]",
"[ 3/8 → 1/2 | s:hh ]",
"[ 1/2 → 5/8 | s:hh ]",
"[ 5/8 → 3/4 | s:hh ]",
"[ 3/4 → 7/8 | 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 ]",
"[ 5/4 → 11/8 | s:hh ]",
"[ 11/8 → 3/2 | s:hh ]",
"[ 3/2 → 13/8 | s:hh ]",
"[ 13/8 → 7/4 | s:hh ]",
"[ 7/4 → 15/8 | s:hh ]",
"[ 15/8 → 2/1 | s:hh ]",
"[ 2/1 → 17/8 | s:hh ]",
"[ 17/8 → 9/4 | 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 ]",
"[ 11/4 → 23/8 | s:hh ]",
"[ 3/1 → 25/8 | s:hh ]",
"[ 23/8 → 3/1 | s:hh ]",
"[ 25/8 → 13/4 | 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 ]",
"[ 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`] = `
[
"[ 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`] = `
[
"[ 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 ]",
"[ 3/8 → 1/2 | 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 ]",
"[ 19/8 → 5/2 | 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`] = `
[
"[ 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/2 → 3/4 | 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 ]",
"[ 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 ]",
"[ 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 ]",
"[ 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`] = `
[
"[ 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: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 ]",
"[ 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: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 ]",
"[ 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: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 ]",
"[ 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: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 ]",
"[ 0/1 → 1/1 | note:C3 ]",
"[ 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
.gain(.4) // turn down
.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
.add(perlin.range(0,.5)) // random pitch variation
.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:
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()`} />
<!-- TODO: use voicing collection as first param + patternify. -->
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()`} />
### rootNotes(octave = 2)
@ -769,7 +767,7 @@ Together with layer, struct and voicings, this can be used to create a basic bac
<MiniRepl
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)
)`}
/>
@ -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.
<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()`}
/>