diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index afae87b1..fec26804 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -809,15 +809,6 @@ export class Pattern { ////////////////////////////////////////////////////////////////////// // Multi-pattern functions - /** - * Stacks the given pattern(s) to the current pattern. - * @name stack - * @memberof Pattern - * @example - * s("hh*4").stack( - * note("c4(5,8)") - * ) - */ stack(...pats) { return stack(this, ...pats); } @@ -826,30 +817,9 @@ export class Pattern { return sequence(this, ...pats); } - /** - * Appends the given pattern(s) to the current pattern. - * @name seq - * @memberof Pattern - * @synonyms sequence, fastcat - * @example - * s("hh*4").seq( - * note("c4(5,8)") - * ) - */ seq(...pats) { return sequence(this, ...pats); } - - /** - * Appends the given pattern(s) to the next cycle. - * @name cat - * @memberof Pattern - * @synonyms slowcat - * @example - * s("hh*4").cat( - * note("c4(5,8)") - * ) - */ cat(...pats) { return cat(this, ...pats); } @@ -1280,6 +1250,12 @@ export function reify(thing) { * @example * stack("g3", "b3", ["e4", "d4"]).note() * // "g3,b3,[e4,d4]".note() + * + * @example + * // As a chained function: + * s("hh*4").stack( + * note("c4(5,8)") + * ) */ export function stack(...pats) { // Array test here is to avoid infinite recursions.. @@ -1408,6 +1384,11 @@ export function slowcatPrime(...pats) { * cat("e5", "b4", ["d5", "c5"]).note() * // "".note() * + * @example + * // As a chained function: + * s("hh*4").cat( + * note("c4(5,8)") + * ) */ export function cat(...pats) { return slowcat(...pats); @@ -1430,6 +1411,36 @@ export function arrange(...sections) { return s_cat(...sections).slow(total); } +/** + * Similarly to `arrange`, allows you to arrange multiple patterns together over multiple cycles. + * Unlike `arrange`, you specify a start and stop time for each pattern rather than duration, which + * means that patterns can overlap. + * @return {Pattern} + * @example +seqPLoop([0, 2, "bd(3,8)"], + [1, 3, "cp(3,8)"] + ) + .sound() + */ +export function seqPLoop(...parts) { + let total = Fraction(0); + const pats = []; + for (let part of parts) { + if (part.length == 2) { + part.unshift(total); + } + total = part[1]; + } + + return stack( + ...parts.map(([start, stop, pat]) => + pure(reify(pat)).compress(Fraction(start).div(total), Fraction(stop).div(total)), + ), + ) + .slow(total) + .innerJoin(); // or resetJoin or restartJoin ?? +} + export function fastcat(...pats) { let result = slowcat(...pats); if (pats.length > 1) { @@ -1448,12 +1459,18 @@ export function sequence(...pats) { } /** Like **cat**, but the items are crammed into one cycle. - * @synonyms fastcat, sequence + * @synonyms sequence, fastcat * @example * seq("e5", "b4", ["d5", "c5"]).note() * // "e5 b4 [d5 c5]".note() * + * @example + * // As a chained function: + * s("hh*4").seq( + * note("c4(5,8)") + * ) */ + export function seq(...pats) { return fastcat(...pats); } @@ -2016,6 +2033,34 @@ export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a return pat.zoom(a.begin, a.end); }); +/** + * Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers. + * Similar to `slice`, but slices up patterns rather than sound samples. + * @param {number} number of slices + * @param {number} slices to play + * @example + * note("0 1 2 3 4 5 6 7".scale('c:mixolydian')) + *.bite(4, "3 2 1 0") + * @example + * sound("bd - bd bd*2, - sd:6 - sd:5 sd:1 - [- sd:2] -, hh [- cp:7]") + .bank("RolandTR909").speed(1.2) + .bite(4, "0 0 [1 2] <3 2> 0 0 [2 1] 3") + */ +export const bite = register( + 'bite', + (npat, ipat, pat) => { + return ipat + .fmap((i) => (n) => { + const a = Fraction(i).div(n).mod(1); + const b = a.add(Fraction(1).div(n)); + return pat.zoom(a, b); + }) + .appLeft(npat) + .squeezeJoin(); + }, + false, +); + /** * Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. * @param {number} fraction fraction to select @@ -2447,6 +2492,37 @@ Pattern.prototype.tag = function (tag) { return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) })); }; +/** + * Filters haps using the given function + * @name filter + * @param {Function} test function to test Hap + * @example + * s("hh!7 oh").filter(hap => hap.value.s==='hh') + */ +export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test))); + +/** + * Filters haps by their begin time + * @name filterWhen + * @noAutocomplete + * @param {Function} test function to test Hap.whole.begin + */ +export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin))); + +/** + * Use within to apply a function to only a part of a pattern. + * @name within + * @param {number} start start within cycle (0 - 1) + * @param {number} end end within cycle (0 - 1). Must be > start + * @param {Function} func function to be applied to the sub-pattern + */ +export const within = register('within', (a, b, fn, pat) => + stack( + fn(pat.filterWhen((t) => t.cyclePos() >= a && t.cyclePos() <= b)), + pat.filterWhen((t) => t.cyclePos() < a || t.cyclePos() > b), + ), +); + ////////////////////////////////////////////////////////////////////// // Tactus-related functions, i.e. ones that do stepwise // transformations diff --git a/packages/midi/midi.mjs b/packages/midi/midi.mjs index 3ce894d6..32e66f6c 100644 --- a/packages/midi/midi.mjs +++ b/packages/midi/midi.mjs @@ -169,13 +169,18 @@ const refs = {}; export async function midin(input) { if (isPattern(input)) { throw new Error( - `.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${ + `midin: does not accept Pattern as input. Make sure to pass device name with single quotes. Example: midin('${ WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1' }')`, ); } const initial = await enableWebMidi(); // only returns on first init const device = getDevice(input, WebMidi.inputs); + if (!device) { + throw new Error( + `midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`, + ); + } if (initial) { const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); logger( diff --git a/packages/serial/serial.mjs b/packages/serial/serial.mjs index 81eb42f2..e0eeaced 100644 --- a/packages/serial/serial.mjs +++ b/packages/serial/serial.mjs @@ -68,7 +68,7 @@ Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids if (!(name in writeMessagers)) { getWriter(name, br); } - const onTrigger = (time, hap, currentTime) => { + const onTrigger = (t_deprecate, hap, currentTime, cps, targetTime) => { var message = ''; var chk = 0; if (typeof hap.value === 'object') { @@ -105,7 +105,7 @@ Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids } else { message = hap.value; } - const offset = (time - currentTime + latency) * 1000; + const offset = (targetTime - currentTime + latency) * 1000; window.setTimeout(function () { writeMessagers[name](message, chk); diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 10087fc6..f5e46d6b 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -22,16 +22,13 @@ function humanFileSize(bytes, si) { return bytes.toFixed(1) + ' ' + units[u]; } -export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resolveUrl) => { - let transpose = 0; - if (freq !== undefined && note !== undefined) { - logger('[sampler] hap has note and freq. ignoring note', 'warning'); - } - let midi = valueToMidi({ freq, note }, 36); - transpose = midi - 36; // C3 is middle C - - const ac = getAudioContext(); - +// deduces relevant info for sample loading from hap.value and sample definition +// it encapsulates the core sampler logic into a pure and synchronous function +// hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format) +export function getSampleInfo(hapValue, bank) { + const { s, n = 0, speed = 1.0 } = hapValue; + let midi = valueToMidi(hapValue, 36); + let transpose = midi - 36; // C3 is middle C; let sampleUrl; let index = 0; if (Array.isArray(bank)) { @@ -50,19 +47,54 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol index = getSoundIndex(n, bank[closest].length); sampleUrl = bank[closest][index]; } + const label = `${s}:${index}`; + let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12); + return { transpose, sampleUrl, index, midi, label, playbackRate }; +} + +// takes hapValue and returns buffer + playbackRate. +export const getSampleBuffer = async (hapValue, bank, resolveUrl) => { + let { sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank); if (resolveUrl) { sampleUrl = await resolveUrl(sampleUrl); } - let buffer = await loadBuffer(sampleUrl, ac, s, index); - if (speed < 0) { + const ac = getAudioContext(); + const buffer = await loadBuffer(sampleUrl, ac, label); + + if (hapValue.unit === 'c') { + playbackRate = playbackRate * buffer.duration; + } + return { buffer, playbackRate }; +}; + +// creates playback ready AudioBufferSourceNode from hapValue +export const getSampleBufferSource = async (hapValue, bank, resolveUrl) => { + let { buffer, playbackRate } = await getSampleBuffer(hapValue, bank, resolveUrl); + if (hapValue.speed < 0) { // should this be cached? buffer = reverseBuffer(buffer); } + const ac = getAudioContext(); const bufferSource = ac.createBufferSource(); bufferSource.buffer = buffer; - const playbackRate = 1.0 * Math.pow(2, transpose / 12); bufferSource.playbackRate.value = playbackRate; - return bufferSource; + + const { s, loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue; + + // "The computation of the offset into the sound is performed using the sound buffer's natural sample rate, + // rather than the current playback rate, so even if the sound is playing at twice its normal speed, + // the midway point through a 10-second audio buffer is still 5." + const offset = begin * bufferSource.buffer.duration; + + const loop = s.startsWith('wt_') ? 1 : hapValue.loop; + if (loop) { + bufferSource.loop = true; + bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset; + bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; + } + const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; + const sliceDuration = (end - begin) * bufferDuration; + return { bufferSource, offset, bufferDuration, sliceDuration }; }; export const loadBuffer = (url, ac, s, n = 0) => { @@ -232,10 +264,10 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option const { prebake, tag } = options; processSampleMap( sampleMap, - (key, value) => - registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + (key, bank) => + registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, bank), { type: 'sample', - samples: value, + samples: bank, baseUrl, prebake, tag, @@ -249,38 +281,26 @@ const cutGroups = []; export async function onTriggerSample(t, value, onended, bank, resolveUrl) { let { s, - freq, - unit, nudge = 0, // TODO: is this in seconds? cut, loop, clip = undefined, // if set, samples will be cut off when the hap ends n = 0, - note, speed = 1, // sample playback speed - loopBegin = 0, - begin = 0, - loopEnd = 1, - end = 1, duration, } = value; + // load sample if (speed === 0) { // no playback return; } - loop = s.startsWith('wt_') ? 1 : value.loop; const ac = getAudioContext(); + // destructure adsr here, because the default should be different for synths and samples - let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); - //const soundfont = getSoundfontKey(s); - const time = t + nudge; - const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl); - - // vibrato - let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t); + const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl); // asny stuff above took too long? if (ac.currentTime > t) { @@ -292,26 +312,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { logger(`[sampler] could not load "${s}:${n}"`, 'error'); return; } - bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value; - if (unit === 'c') { - // are there other units? - bufferSource.playbackRate.value = bufferSource.playbackRate.value * bufferSource.buffer.duration * 1; //cps; - } - // "The computation of the offset into the sound is performed using the sound buffer's natural sample rate, - // rather than the current playback rate, so even if the sound is playing at twice its normal speed, - // the midway point through a 10-second audio buffer is still 5." - const offset = begin * bufferSource.buffer.duration; - if (loop) { - bufferSource.loop = true; - bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset; - bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; - } + + // vibrato + let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t); + + const time = t + nudge; bufferSource.start(time, offset); + const envGain = ac.createGain(); const node = bufferSource.connect(envGain); + + // if none of these controls is set, the duration of the sound will be set to the duration of the sample slice if (clip == null && loop == null && value.release == null) { - const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; - duration = (end - begin) * bufferDuration; + duration = sliceDuration; } let holdEnd = t + duration; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index c0f9e451..7d57b5bb 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -421,6 +421,7 @@ export const superdough = async (value, t, hapDuration) => { }; if (bank && s) { s = `${bank}_${s}`; + value.s = s; } // get source AudioNode diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index b34212ab..a3419e98 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -967,6 +967,156 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; +exports[`runs examples > example "bite" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:Bb3 ]", + "[ 1/8 → 1/4 | note:C4 ]", + "[ 1/4 → 3/8 | note:G3 ]", + "[ 3/8 → 1/2 | note:A3 ]", + "[ 1/2 → 5/8 | note:E3 ]", + "[ 5/8 → 3/4 | note:F3 ]", + "[ 3/4 → 7/8 | note:C3 ]", + "[ 7/8 → 1/1 | note:D3 ]", + "[ 1/1 → 9/8 | note:Bb3 ]", + "[ 9/8 → 5/4 | note:C4 ]", + "[ 5/4 → 11/8 | note:G3 ]", + "[ 11/8 → 3/2 | note:A3 ]", + "[ 3/2 → 13/8 | note:E3 ]", + "[ 13/8 → 7/4 | note:F3 ]", + "[ 7/4 → 15/8 | note:C3 ]", + "[ 15/8 → 2/1 | note:D3 ]", + "[ 2/1 → 17/8 | note:Bb3 ]", + "[ 17/8 → 9/4 | note:C4 ]", + "[ 9/4 → 19/8 | note:G3 ]", + "[ 19/8 → 5/2 | note:A3 ]", + "[ 5/2 → 21/8 | note:E3 ]", + "[ 21/8 → 11/4 | note:F3 ]", + "[ 11/4 → 23/8 | note:C3 ]", + "[ 23/8 → 3/1 | note:D3 ]", + "[ 3/1 → 25/8 | note:Bb3 ]", + "[ 25/8 → 13/4 | note:C4 ]", + "[ 13/4 → 27/8 | note:G3 ]", + "[ 27/8 → 7/2 | note:A3 ]", + "[ 7/2 → 29/8 | note:E3 ]", + "[ 29/8 → 15/4 | note:F3 ]", + "[ 15/4 → 31/8 | note:C3 ]", + "[ 31/8 → 4/1 | note:D3 ]", +] +`; + +exports[`runs examples > example "bite" example index 1 1`] = ` +[ + "[ 0/1 → 1/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 0/1 → 1/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 1/16 → 1/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 1/8 → 1/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 1/8 → 1/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 3/16 → 1/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 1/4 → 5/16 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 9/32 → 5/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 5/16 → 11/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 5/16 → 3/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 3/8 → 7/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 3/8 → 1/2 | s:cp n:7 bank:RolandTR909 speed:1.2 ]", + "[ 13/32 → 7/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]", + "[ 7/16 → 1/2 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 1/2 → 5/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 1/2 → 5/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 9/16 → 5/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 5/8 → 3/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 5/8 → 3/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 11/16 → 3/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 3/4 → 25/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 3/4 → 13/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 13/16 → 7/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 27/32 → 7/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 7/8 → 15/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 7/8 → 1/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]", + "[ 29/32 → 15/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]", + "[ 15/16 → 1/1 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 1/1 → 9/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 1/1 → 9/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 17/16 → 9/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 9/8 → 5/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 9/8 → 5/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 19/16 → 5/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 5/4 → 21/16 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 41/32 → 21/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 21/16 → 43/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 21/16 → 11/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 11/8 → 23/16 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 11/8 → 3/2 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 3/2 → 13/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 3/2 → 13/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 25/16 → 13/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 13/8 → 7/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 13/8 → 7/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 27/16 → 7/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 7/4 → 57/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 7/4 → 29/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 29/16 → 15/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 59/32 → 15/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 15/8 → 31/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 15/8 → 2/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]", + "[ 61/32 → 31/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]", + "[ 31/16 → 2/1 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 2/1 → 17/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 2/1 → 17/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 33/16 → 17/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 17/8 → 9/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 17/8 → 9/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 35/16 → 9/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 9/4 → 37/16 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 73/32 → 37/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 37/16 → 75/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 37/16 → 19/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 19/8 → 39/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 19/8 → 5/2 | s:cp n:7 bank:RolandTR909 speed:1.2 ]", + "[ 77/32 → 39/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]", + "[ 39/16 → 5/2 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 5/2 → 21/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 5/2 → 21/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 41/16 → 21/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 21/8 → 11/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 21/8 → 11/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 43/16 → 11/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 11/4 → 89/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 11/4 → 45/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 45/16 → 23/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 91/32 → 23/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 23/8 → 47/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 23/8 → 3/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]", + "[ 93/32 → 47/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]", + "[ 47/16 → 3/1 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 3/1 → 25/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 3/1 → 25/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 49/16 → 25/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 25/8 → 13/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 25/8 → 13/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 51/16 → 13/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 13/4 → 53/16 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 105/32 → 53/16 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 53/16 → 107/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 53/16 → 27/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 27/8 → 55/16 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 27/8 → 7/2 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 7/2 → 29/8 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 7/2 → 29/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 57/16 → 29/8 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 29/8 → 15/4 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 29/8 → 15/4 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 59/16 → 15/4 | s:sd n:6 bank:RolandTR909 speed:1.2 ]", + "[ 15/4 → 121/32 | s:sd n:1 bank:RolandTR909 speed:1.2 ]", + "[ 15/4 → 61/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 61/16 → 31/8 | s:hh bank:RolandTR909 speed:1.2 ]", + "[ 123/32 → 31/8 | s:sd n:5 bank:RolandTR909 speed:1.2 ]", + "[ 31/8 → 63/16 | s:bd bank:RolandTR909 speed:1.2 ]", + "[ 31/8 → 4/1 | s:cp n:7 bank:RolandTR909 speed:1.2 ]", + "[ 125/32 → 63/16 | s:sd n:2 bank:RolandTR909 speed:1.2 ]", + "[ 63/16 → 4/1 | s:bd bank:RolandTR909 speed:1.2 ]", +] +`; + exports[`runs examples > example "bpattack" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]", @@ -1229,6 +1379,16 @@ exports[`runs examples > example "brandBy" example index 0 1`] = ` `; exports[`runs examples > example "cat" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | note:e5 ]", + "[ 1/1 → 2/1 | note:b4 ]", + "[ 2/1 → 5/2 | note:d5 ]", + "[ 5/2 → 3/1 | note:c5 ]", + "[ 3/1 → 4/1 | note:e5 ]", +] +`; + +exports[`runs examples > example "cat" example index 1 1`] = ` [ "[ 0/1 → 1/4 | s:hh ]", "[ 1/4 → 1/2 | s:hh ]", @@ -1251,16 +1411,6 @@ exports[`runs examples > example "cat" example index 0 1`] = ` ] `; -exports[`runs examples > example "cat" example index 0 2`] = ` -[ - "[ 0/1 → 1/1 | note:e5 ]", - "[ 1/1 → 2/1 | note:b4 ]", - "[ 2/1 → 5/2 | note:d5 ]", - "[ 5/2 → 3/1 | note:c5 ]", - "[ 3/1 → 4/1 | note:e5 ]", -] -`; - exports[`runs examples > example "ceil" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:42 ]", @@ -2666,6 +2816,43 @@ exports[`runs examples > example "fastGap" example index 0 1`] = ` ] `; +exports[`runs examples > example "filter" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | s:hh ]", + "[ 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:oh ]", + "[ 1/1 → 9/8 | s:hh ]", + "[ 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:oh ]", + "[ 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 ]", + "[ 23/8 → 3/1 | s:oh ]", + "[ 3/1 → 25/8 | 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:oh ]", +] +`; + exports[`runs examples > example "firstOf" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:g3 ]", @@ -6789,6 +6976,27 @@ exports[`runs examples > example "segment" example index 0 1`] = ` `; exports[`runs examples > example "seq" example index 0 1`] = ` +[ + "[ 0/1 → 1/3 | note:e5 ]", + "[ 1/3 → 2/3 | note:b4 ]", + "[ 2/3 → 5/6 | note:d5 ]", + "[ 5/6 → 1/1 | note:c5 ]", + "[ 1/1 → 4/3 | note:e5 ]", + "[ 4/3 → 5/3 | note:b4 ]", + "[ 5/3 → 11/6 | note:d5 ]", + "[ 11/6 → 2/1 | note:c5 ]", + "[ 2/1 → 7/3 | note:e5 ]", + "[ 7/3 → 8/3 | note:b4 ]", + "[ 8/3 → 17/6 | note:d5 ]", + "[ 17/6 → 3/1 | note:c5 ]", + "[ 3/1 → 10/3 | note:e5 ]", + "[ 10/3 → 11/3 | note:b4 ]", + "[ 11/3 → 23/6 | note:d5 ]", + "[ 23/6 → 4/1 | note:c5 ]", +] +`; + +exports[`runs examples > example "seq" example index 1 1`] = ` [ "[ 0/1 → 1/8 | s:hh ]", "[ 1/8 → 1/4 | s:hh ]", @@ -6829,24 +7037,23 @@ exports[`runs examples > example "seq" example index 0 1`] = ` ] `; -exports[`runs examples > example "seq" example index 0 2`] = ` +exports[`runs examples > example "seqPLoop" example index 0 1`] = ` [ - "[ 0/1 → 1/3 | note:e5 ]", - "[ 1/3 → 2/3 | note:b4 ]", - "[ 2/3 → 5/6 | note:d5 ]", - "[ 5/6 → 1/1 | note:c5 ]", - "[ 1/1 → 4/3 | note:e5 ]", - "[ 4/3 → 5/3 | note:b4 ]", - "[ 5/3 → 11/6 | note:d5 ]", - "[ 11/6 → 2/1 | note:c5 ]", - "[ 2/1 → 7/3 | note:e5 ]", - "[ 7/3 → 8/3 | note:b4 ]", - "[ 8/3 → 17/6 | note:d5 ]", - "[ 17/6 → 3/1 | note:c5 ]", - "[ 3/1 → 10/3 | note:e5 ]", - "[ 10/3 → 11/3 | note:b4 ]", - "[ 11/3 → 23/6 | note:d5 ]", - "[ 23/6 → 4/1 | note:c5 ]", + "[ 0/1 → 1/8 | s:bd ]", + "[ 3/8 → 1/2 | s:bd ]", + "[ 3/4 → 7/8 | s:bd ]", + "[ 1/1 → 9/8 | s:bd ]", + "[ 1/1 → 9/8 | s:cp ]", + "[ 11/8 → 3/2 | s:bd ]", + "[ 11/8 → 3/2 | s:cp ]", + "[ 7/4 → 15/8 | s:bd ]", + "[ 7/4 → 15/8 | s:cp ]", + "[ 2/1 → 17/8 | s:cp ]", + "[ 19/8 → 5/2 | s:cp ]", + "[ 11/4 → 23/8 | s:cp ]", + "[ 3/1 → 25/8 | s:bd ]", + "[ 27/8 → 7/2 | s:bd ]", + "[ 15/4 → 31/8 | s:bd ]", ] `; @@ -7523,6 +7730,27 @@ exports[`runs examples > example "squiz" example index 0 1`] = ` `; exports[`runs examples > example "stack" example index 0 1`] = ` +[ + "[ 0/1 → 1/2 | note:e4 ]", + "[ 0/1 → 1/1 | note:g3 ]", + "[ 0/1 → 1/1 | note:b3 ]", + "[ 1/2 → 1/1 | note:d4 ]", + "[ 1/1 → 3/2 | note:e4 ]", + "[ 1/1 → 2/1 | note:g3 ]", + "[ 1/1 → 2/1 | note:b3 ]", + "[ 3/2 → 2/1 | note:d4 ]", + "[ 2/1 → 5/2 | note:e4 ]", + "[ 2/1 → 3/1 | note:g3 ]", + "[ 2/1 → 3/1 | note:b3 ]", + "[ 5/2 → 3/1 | note:d4 ]", + "[ 3/1 → 7/2 | note:e4 ]", + "[ 3/1 → 4/1 | note:g3 ]", + "[ 3/1 → 4/1 | note:b3 ]", + "[ 7/2 → 4/1 | note:d4 ]", +] +`; + +exports[`runs examples > example "stack" example index 1 1`] = ` [ "[ 0/1 → 1/8 | note:c4 ]", "[ 0/1 → 1/4 | s:hh ]", @@ -7563,27 +7791,6 @@ exports[`runs examples > example "stack" example index 0 1`] = ` ] `; -exports[`runs examples > example "stack" example index 0 2`] = ` -[ - "[ 0/1 → 1/2 | note:e4 ]", - "[ 0/1 → 1/1 | note:g3 ]", - "[ 0/1 → 1/1 | note:b3 ]", - "[ 1/2 → 1/1 | note:d4 ]", - "[ 1/1 → 3/2 | note:e4 ]", - "[ 1/1 → 2/1 | note:g3 ]", - "[ 1/1 → 2/1 | note:b3 ]", - "[ 3/2 → 2/1 | note:d4 ]", - "[ 2/1 → 5/2 | note:e4 ]", - "[ 2/1 → 3/1 | note:g3 ]", - "[ 2/1 → 3/1 | note:b3 ]", - "[ 5/2 → 3/1 | note:d4 ]", - "[ 3/1 → 7/2 | note:e4 ]", - "[ 3/1 → 4/1 | note:g3 ]", - "[ 3/1 → 4/1 | note:b3 ]", - "[ 7/2 → 4/1 | note:d4 ]", -] -`; - exports[`runs examples > example "steps" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:bd ]", diff --git a/website/src/content/blog/release-0.0.2-schwindlig.mdx b/website/src/content/blog/release-0.0.2-schwindlig.mdx index 49e5319a..cf4bc19d 100644 --- a/website/src/content/blog/release-0.0.2-schwindlig.mdx +++ b/website/src/content/blog/release-0.0.2-schwindlig.mdx @@ -26,7 +26,7 @@ author: froos - fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in https://github.com/tidalcycles/strudel/pull/19 - Stateful queries and events (WIP) by @yaxu in https://github.com/tidalcycles/strudel/pull/14 - Fix resolveState by @yaxu in https://github.com/tidalcycles/strudel/pull/22 -- added \_asNumber + interprete numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21 +- added \_asNumber + interpret numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21 - Update package.json by @ChiakiUehira in https://github.com/tidalcycles/strudel/pull/23 - packaging by @felixroos in https://github.com/tidalcycles/strudel/pull/24 diff --git a/website/src/content/blog/release-0.6.0-zimtschnecke.mdx b/website/src/content/blog/release-0.6.0-zimtschnecke.mdx index 80dbc145..2736974b 100644 --- a/website/src/content/blog/release-0.6.0-zimtschnecke.mdx +++ b/website/src/content/blog/release-0.6.0-zimtschnecke.mdx @@ -28,7 +28,7 @@ author: froos - support freq in pianoroll by @felixroos in https://github.com/tidalcycles/strudel/pull/308 - ICLC2023 paper WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/306 -- fix: copy share link to clipboard was broken for some browers by @felixroos in https://github.com/tidalcycles/strudel/pull/311 +- fix: copy share link to clipboard was broken for some browsers by @felixroos in https://github.com/tidalcycles/strudel/pull/311 - Jsdoc component by @felixroos in https://github.com/tidalcycles/strudel/pull/312 - object support for .scale by @felixroos in https://github.com/tidalcycles/strudel/pull/307 - Astro build by @felixroos in https://github.com/tidalcycles/strudel/pull/315 diff --git a/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx b/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx index 70176ad9..5784fe52 100644 --- a/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx +++ b/website/src/content/blog/release-1.0.0-geburtstagskuchen.mdx @@ -138,7 +138,7 @@ Plenty of things have been added to the docs, including a [showcase of what peop - Export patterns + ui tweaks by @felixroos in https://github.com/tidalcycles/strudel/pull/855 - Pattern organization by @felixroos in https://github.com/tidalcycles/strudel/pull/858 - Sound Import from local file system by @daslyfe in https://github.com/tidalcycles/strudel/pull/839 -- bugfix: suspend and close exisiting audio context when changing interface by @daslyfe in https://github.com/tidalcycles/strudel/pull/882 +- bugfix: suspend and close existing audio context when changing interface by @daslyfe in https://github.com/tidalcycles/strudel/pull/882 - add root mode for voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/887 - scales can now be anchored by @felixroos in https://github.com/tidalcycles/strudel/pull/888 - add dough function for raw dsp by @felixroos in https://github.com/tidalcycles/strudel/pull/707 (experimental) @@ -195,7 +195,7 @@ Plenty of things have been added to the docs, including a [showcase of what peop - Update tauri.yml workflow file by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/705 - vite-vanilla-repl readme fix by @felixroos in https://github.com/tidalcycles/strudel/pull/737 - completely revert config mess by @felixroos in https://github.com/tidalcycles/strudel/pull/745 -- hopefully fix trainling slashes bug by @felixroos in https://github.com/tidalcycles/strudel/pull/753 +- hopefully fix trailing slashes bug by @felixroos in https://github.com/tidalcycles/strudel/pull/753 - Update vite pwa by @felixroos in https://github.com/tidalcycles/strudel/pull/772 - Update to Astro 3 by @felixroos in https://github.com/tidalcycles/strudel/pull/775 - support multiple named serial connections, change default baudrate by @yaxu in https://github.com/tidalcycles/strudel/pull/551 diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index 0884786e..ca76c362 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -120,6 +120,7 @@ export function MiniRepl({ 'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background', started ? 'animate-pulse' : '', )} + aria-label={started ? 'stop' : 'play'} onClick={() => editorRef.current?.toggle()} > @@ -129,6 +130,7 @@ export function MiniRepl({ 'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight', isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed', )} + aria-label="update" onClick={() => editorRef.current?.evaluate()} > @@ -140,6 +142,7 @@ export function MiniRepl({ className={ 'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background' } + aria-label="previous example" onClick={() => changeTune(tuneIndex - 1)} >
@@ -150,6 +153,7 @@ export function MiniRepl({ className={ 'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background' } + aria-label="next example" onClick={() => changeTune(tuneIndex + 1)} > diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx index a823bfed..6a2e165b 100644 --- a/website/src/pages/learn/factories.mdx +++ b/website/src/pages/learn/factories.mdx @@ -25,26 +25,14 @@ These are the equivalents used by the Mini Notation: -You can also use cat as a chained function like this: - - - ## seq -Or as a chained function: - - - ## stack -As a chained function: - - - ## s_cat diff --git a/website/src/repl/components/Header.jsx b/website/src/repl/components/Header.jsx index c513f451..8d6e2f9a 100644 --- a/website/src/repl/components/Header.jsx +++ b/website/src/repl/components/Header.jsx @@ -43,7 +43,7 @@ export function Header({ context, embedded = false }) { )} >
{ if (!isEmbedded) { setIsZen(!isZen); diff --git a/website/src/repl/components/panel/ConsoleTab.jsx b/website/src/repl/components/panel/ConsoleTab.jsx index b5681ab1..e1bbbe64 100644 --- a/website/src/repl/components/panel/ConsoleTab.jsx +++ b/website/src/repl/components/panel/ConsoleTab.jsx @@ -3,7 +3,7 @@ import cx from '@src/cx.mjs'; export function ConsoleTab({ log }) { return (
-
{`███████╗████████╗██████╗ ██╗   ██╗██████╗ ███████╗██╗     
+