From 95ae5191ceedfdaae1f85b8e5ed34926129bb9fa Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 16 May 2024 03:36:35 +0200 Subject: [PATCH 01/28] begin "understanding voicings" --- website/src/config.ts | 1 + website/src/pages/understand/voicings.mdx | 196 ++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 website/src/pages/understand/voicings.mdx diff --git a/website/src/config.ts b/website/src/config.ts index 9dee2aac2..f85d60d81 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -99,6 +99,7 @@ export const SIDEBAR: Sidebar = { { text: 'Coding syntax', link: 'learn/code' }, { text: 'Pitch', link: 'understand/pitch' }, { text: 'Cycles', link: 'understand/cycles' }, + { text: 'Voicings', link: 'understand/voicings' }, { text: 'Pattern Alignment', link: 'technical-manual/alignment' }, { text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' }, ], diff --git a/website/src/pages/understand/voicings.mdx b/website/src/pages/understand/voicings.mdx new file mode 100644 index 000000000..a36ecd8a7 --- /dev/null +++ b/website/src/pages/understand/voicings.mdx @@ -0,0 +1,196 @@ +--- +title: Understanding Chod Voicings +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { PitchSlider } from '../../components/PitchSlider'; +import Box from '@components/Box.astro'; + +# Understanding Chords and Voicings + +Let's dig deeper into how chords and voicings work. +I'll try to keep theory jargon to a minimum, so hopefully this is approachable for anyone interested. + +## What is a chord + +Playing more than one note at a time is generally called a chord. Here's an example: + +").room(.5)`} /> + +Here's the same with midi numbers: + +").room(.5)`} /> + +Here, we have two 3-note chords played in a loop. +You could already stop here and write chords in this style, which is totally fine and gives you control over individual notes. +One downside is that it can be difficult to find good sounding chords and maybe you're yearning for a way to organize chords in some other way.. + +## Labeling Chords + +Chords are typically given different labels depending on the relationship of the notes within. +In the number example above, we have `48,51,55` and `53,57,60`. + +To analyze the relationship of those notes, they are typically compared to some `root`, which is often the lowest note. +In our case, the `roots` would be `48` (= `c3`) and `53` (= `f3`). +We can express the same chords relative to those `roots` like this: + +".add("<48 53>")).room(.5)`} /> + +Now within each chord, each number represents the distance from the root. +A distance between pitches is typically called `interval`, but let's stick to distance for now. + +Now we can see that our 2 chords are actually quite similar, as the only difference is the middle note (and the root of course). +They are part of a group of chords called `triads` which are chords with 3 notes. + +### Triads + +These 4 shapes are the most common types of `triads` you will encounter: + +| shape | label | +| ----- | ---------- | +| 0,3,6 | diminished | +| 0,3,7 | minor | +| 0,4,7 | major | +| 0,4,8 | augmented | + +Here they are in succession: + +".add("60")) +.room(.5)._pitchwheel()`} +/> + +Many types of music often only use minor and major chords, so we already have the knowledge to accompany songs. Here's one: + +\`.add(\`< +a c d f +a e a e +>\`)).room(.5)`} +/> + +These are the chords for "The house of the rising sun" by The Animals. +So far it doesn't sound too exiting but at least it's recognizable.. + +## Voicings + +A `voicing` is one of many ways a certain chord shape could be played. +The term comes from choral music, where chords can be sung in different ways by changing which voice sings which note. +For example we could add 12 to one or more notes in the chord: + +".add("48")) +.room(.5)`} +/> + +Notes that are 12 steps apart (= 1 `octave`) are considered to be equal in a harmonic sense, which is why they get the same note letter. +Here's the same example with note letterns: + +") +.room(.5)`} +/> + +This type of voicings are also called `inversions`. There are many other ways we could `voice` this minor chord: + +".add("48")) +.room(.5)`} +/> + +Here we are changing the flavour of the chord slightly by + +1. doubling notes 12 steps higher, +2. using very wide distances +3. omitting notes + +## Voice Leading + +Let's revisit "The House of the Rising Sun", this time using our newly acquired voicing techniques: + +\`.add(\`< +a c d f +a e a e +>\`)).room(.5)`} + punchcard +/> + +These voicings make the chords sound more connected and less jumpy, compared to the version without voicings. +The way chords interact is also called voice leading, reminiscent of how a choir voice would move through a sequence of chords. + +For example, try singing the top voice in the above example. Then try the same on the example without voice leading. Which one's easier? + +Naturally, there are many ways a progression of chords could be voiced and there is no clear right or wrong. + +## Chord Symbols + +Musicians playing chord-based music often rely on a so called lead sheet, which is a simplified notation of a music piece. +The chords in those lead sheets are notated with symbols that allow a piece to be notated in a very concise manner. +A common way to write the chords "The House of the Rising Sun" would be: + +``` +Am | C | D | F +Am | E | Am | E +``` + +Here, each symbol consists of the `root` of the chord and optionally an `m` to signal it's a minor chord (just the root note means it's major). +We could mirror that notation in strudel using the `pick` function: + +" + .pick({ + Am: "57,60,64", + C: "55,60,64", + D: "50,57,66", + F: "57,60,65", + E: "56,59,64", + }) + .note().room(.5)`} + punchcard +/> + +## The voicing function + +Coming up with good sounding voicings that connect well can be a difficult and time consuming process. +The `chord` and `voicing` functions can be used to automate that: + +").voicing().room(.5)`} punchcard /> + +Here we're also using chord symbols but the voicings will be automatically generated with smooth voice leading. + +## Voicing Dictionaries + +The voicing function internally uses so called `voicing dictionaries`, which can also be customized: + +") + .dict('house').anchor(66) + .voicing().room(.5)`} + punchcard +/> + +In a `voicing dictionary`, each chord symbol is assigned one or more voicings. +The `voicing` function then picks the voicing that is closest to the `anchor` (defaults to `c5`). + +The handy thing about this approach is that a `voicing dictionary` can be used to play any chord progression with automated voice leading! From 83e46037d2b0156e6270ab2f46d533e4bb61d091 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 16 May 2024 08:59:07 +0200 Subject: [PATCH 02/28] typo --- website/src/pages/understand/timbre.mdx | 12 ++++++++++++ website/src/pages/understand/voicings.mdx | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 website/src/pages/understand/timbre.mdx diff --git a/website/src/pages/understand/timbre.mdx b/website/src/pages/understand/timbre.mdx new file mode 100644 index 000000000..47a8e2994 --- /dev/null +++ b/website/src/pages/understand/timbre.mdx @@ -0,0 +1,12 @@ +--- +title: Understanding Timbre +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { PitchSlider } from '../../components/PitchSlider'; +import Box from '@components/Box.astro'; + +# Understanding Timbre + +Let's learn what timbre is! diff --git a/website/src/pages/understand/voicings.mdx b/website/src/pages/understand/voicings.mdx index a36ecd8a7..d66e075e0 100644 --- a/website/src/pages/understand/voicings.mdx +++ b/website/src/pages/understand/voicings.mdx @@ -1,5 +1,5 @@ --- -title: Understanding Chod Voicings +title: Understanding Chord Voicings layout: ../../layouts/MainLayout.astro --- From 33fef379ada6fd96001510f5dc2f64eb01756738 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:35:09 -0400 Subject: [PATCH 03/28] Get a basic version of binary working --- packages/core/signal.mjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 215eac2f4..c5eff43d6 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -159,6 +159,17 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); */ export const run = (n) => saw.range(0, n).floor().segment(n); +/** + * A discrete binary pattern using a decimal/hex number as input + * @example + * "c a f e".struct(binary(0xA5B5)).note().piano() + * // "c a f e".struct("0 1 0 1 0 1 0 1 1 0 1 0 1").note().piano() + */ +export const binary = (n) => { + const binLen = 16; + return reify(n).segment(binLen).brshift(run(binLen)).band(pure(1)); +}; + export const randrun = (n) => { return signal((t) => { // Without adding 0.5, the first cycle is always 0,1,2,3,... From 0d660277f6c1c4f83e8163e98b616d35a0af77fc Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:36:29 -0400 Subject: [PATCH 04/28] Put msb on the right --- packages/core/signal.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index c5eff43d6..8d033499c 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -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, register } from './pattern.mjs'; +import { Pattern, fastcat, pure, register, reify, silence, stack } from './pattern.mjs'; import Fraction from './fraction.mjs'; import { id, _mod } from './util.mjs'; @@ -167,7 +167,9 @@ export const run = (n) => saw.range(0, n).floor().segment(n); */ export const binary = (n) => { const binLen = 16; - return reify(n).segment(binLen).brshift(run(binLen)).band(pure(1)); + // Shift right and mask, with msb at the end/right-side + const i = run(binLen).mul(-1).add(binLen - 1) + return reify(n).segment(binLen).brshift(i).band(pure(1)); }; export const randrun = (n) => { From 0a408e464a7c9b32f0ba2c56081d5d0d919787a3 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:39:45 -0400 Subject: [PATCH 05/28] Make example more concise --- packages/core/signal.mjs | 4 +-- test/__snapshots__/examples.test.mjs.snap | 41 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 8d033499c..8340bc9f1 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -162,8 +162,8 @@ export const run = (n) => saw.range(0, n).floor().segment(n); /** * A discrete binary pattern using a decimal/hex number as input * @example - * "c a f e".struct(binary(0xA5B5)).note().piano() - * // "c a f e".struct("0 1 0 1 0 1 0 1 1 0 1 0 1").note().piano() + * "hh".s().struct(binary(55532)) + * // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0") */ export const binary = (n) => { const binLen = 16; diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 3ad6ea01e..fd7169c5a 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -967,6 +967,47 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; +exports[`runs examples > example "binary" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | s:hh ]", + "[ 1/16 → 1/8 | s:hh ]", + "[ 3/16 → 1/4 | s:hh ]", + "[ 1/4 → 5/16 | s:hh ]", + "[ 1/2 → 9/16 | s:hh ]", + "[ 9/16 → 5/8 | s:hh ]", + "[ 5/8 → 11/16 | s:hh ]", + "[ 3/4 → 13/16 | s:hh ]", + "[ 13/16 → 7/8 | s:hh ]", + "[ 1/1 → 17/16 | s:hh ]", + "[ 17/16 → 9/8 | s:hh ]", + "[ 19/16 → 5/4 | s:hh ]", + "[ 5/4 → 21/16 | s:hh ]", + "[ 3/2 → 25/16 | s:hh ]", + "[ 25/16 → 13/8 | s:hh ]", + "[ 13/8 → 27/16 | s:hh ]", + "[ 7/4 → 29/16 | s:hh ]", + "[ 29/16 → 15/8 | s:hh ]", + "[ 2/1 → 33/16 | s:hh ]", + "[ 33/16 → 17/8 | s:hh ]", + "[ 35/16 → 9/4 | s:hh ]", + "[ 9/4 → 37/16 | s:hh ]", + "[ 5/2 → 41/16 | s:hh ]", + "[ 41/16 → 21/8 | s:hh ]", + "[ 21/8 → 43/16 | s:hh ]", + "[ 11/4 → 45/16 | s:hh ]", + "[ 45/16 → 23/8 | s:hh ]", + "[ 3/1 → 49/16 | s:hh ]", + "[ 49/16 → 25/8 | s:hh ]", + "[ 51/16 → 13/4 | s:hh ]", + "[ 13/4 → 53/16 | s:hh ]", + "[ 7/2 → 57/16 | s:hh ]", + "[ 57/16 → 29/8 | s:hh ]", + "[ 29/8 → 59/16 | s:hh ]", + "[ 15/4 → 61/16 | s:hh ]", + "[ 61/16 → 31/8 | s:hh ]", +] +`; + exports[`runs examples > example "bite" example index 0 1`] = ` [ "[ 0/1 → 1/8 | note:Bb3 ]", From 947b263b9ccccf10279cfe31746c7f69b5a48e42 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:40:29 -0400 Subject: [PATCH 06/28] Add tests --- packages/core/test/pattern.test.mjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 31ec48681..03f2c60f0 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -46,6 +46,7 @@ import { rev, time, run, + binary, pick, stackLeft, stackRight, @@ -958,6 +959,18 @@ describe('Pattern', () => { expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle()); }); }); + describe('binary', () => { + it('Can make a binary pattern from a decimal', () => { + expect(binary(55532).firstCycle()).toStrictEqual( + sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(), + ); + }); + it('Can make a binary pattern from a numerical pattern', () => { + expect(binary(pure(0x1337)).firstCycle()).toStrictEqual( + sequence(0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(), + ); + }); + }); describe('ribbon', () => { it('Can ribbon', () => { expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual( From 34f4afad0158fe8de7c251f588ccd76357160855 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:41:37 -0400 Subject: [PATCH 07/28] Rework binary to binaryN --- packages/core/signal.mjs | 17 ++++++++++------- packages/core/test/pattern.test.mjs | 12 ++++++------ test/__snapshots__/examples.test.mjs.snap | 3 ++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 8340bc9f1..6c905b552 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -160,16 +160,19 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); export const run = (n) => saw.range(0, n).floor().segment(n); /** - * A discrete binary pattern using a decimal/hex number as input + * @name binaryN + * Creates a discrete pattern using binary representation. + * @param {number} n - input number to convert to binary + * @param {number} nBits - pattern length, defaults to 16 * @example - * "hh".s().struct(binary(55532)) + * "hh".s().struct(binaryN(55532, 16)) * // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0") */ -export const binary = (n) => { - const binLen = 16; - // Shift right and mask, with msb at the end/right-side - const i = run(binLen).mul(-1).add(binLen - 1) - return reify(n).segment(binLen).brshift(i).band(pure(1)); +export const binaryN = (n, nBits = 16) => { + nBits = reify(nBits); + // Shift and mask, putting msb on the right-side + const i = run(nBits).mul(-1).add(nBits.sub(1)); + return reify(n).segment(nBits).brshift(i).band(pure(1)); }; export const randrun = (n) => { diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 03f2c60f0..7e6d8fd82 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -46,7 +46,7 @@ import { rev, time, run, - binary, + binaryN, pick, stackLeft, stackRight, @@ -959,15 +959,15 @@ describe('Pattern', () => { expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle()); }); }); - describe('binary', () => { + describe('binaryN', () => { it('Can make a binary pattern from a decimal', () => { - expect(binary(55532).firstCycle()).toStrictEqual( + expect(binaryN(55532).firstCycle()).toStrictEqual( sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(), ); }); - it('Can make a binary pattern from a numerical pattern', () => { - expect(binary(pure(0x1337)).firstCycle()).toStrictEqual( - sequence(0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(), + it('Can make a binary pattern from patterned inputs', () => { + expect(binaryN(pure(0x1337), pure(14)).firstCycle()).toStrictEqual( + sequence(0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(), ); }); }); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index fd7169c5a..6dce57390 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -967,7 +967,8 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; -exports[`runs examples > example "binary" example index 0 1`] = ` +exports[`runs examples > example "binaryN +Creates a discrete pattern using binary representation." example index 0 1`] = ` [ "[ 0/1 → 1/16 | s:hh ]", "[ 1/16 → 1/8 | s:hh ]", From 4cc019f038cec10ca01bac08f5c8e4303f528404 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:48:47 -0400 Subject: [PATCH 08/28] Rename variable --- packages/core/signal.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 6c905b552..c29c0399b 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -171,8 +171,8 @@ export const run = (n) => saw.range(0, n).floor().segment(n); export const binaryN = (n, nBits = 16) => { nBits = reify(nBits); // Shift and mask, putting msb on the right-side - const i = run(nBits).mul(-1).add(nBits.sub(1)); - return reify(n).segment(nBits).brshift(i).band(pure(1)); + const bitPos = run(nBits).mul(-1).add(nBits.sub(1)); + return reify(n).segment(nBits).brshift(bitPos).band(pure(1)); }; export const randrun = (n) => { From f87f2791984057dcd8aa9da3e4931c0a204f47a0 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:43:13 -0400 Subject: [PATCH 09/28] Support those who prefer no left padding --- packages/core/pattern.mjs | 1 + packages/core/signal.mjs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index f3f19f874..35f9cb6a8 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1023,6 +1023,7 @@ function _composeOp(a, b, func) { div: [numeralArgs((a, b) => a / b)], mod: [numeralArgs(_mod)], pow: [numeralArgs(Math.pow)], + log2: [numeralArgs(Math.log2)], band: [numeralArgs((a, b) => a & b)], bor: [numeralArgs((a, b) => a | b)], bxor: [numeralArgs((a, b) => a ^ b)], diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index c29c0399b..5f61fe7ac 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -159,6 +159,19 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); */ export const run = (n) => saw.range(0, n).floor().segment(n); +/** + * @name binary + * Creates a discrete pattern using binary representation. + * @param {number} n - input number to convert to binary + * @example + * "hh".s().struct(binary(5)) + * // "hh".s().struct("1 0 1") + */ +export const binary = (n) => { + const nBits = reify(n).log2(0).floor().add(1); + return binaryN(n, nBits) +}; + /** * @name binaryN * Creates a discrete pattern using binary representation. From 111cea23819ded11232d3687d0e81f70427771ea Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:31:21 -0500 Subject: [PATCH 10/28] Fix lint --- packages/core/signal.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 5f61fe7ac..746b207d2 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -169,7 +169,7 @@ export const run = (n) => saw.range(0, n).floor().segment(n); */ export const binary = (n) => { const nBits = reify(n).log2(0).floor().add(1); - return binaryN(n, nBits) + return binaryN(n, nBits); }; /** From 7cf7575e6d5b61fcab87ab1f84499844227ca502 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Tue, 17 Dec 2024 08:49:49 -0500 Subject: [PATCH 11/28] Make the docs friendlier --- packages/core/signal.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 746b207d2..31fdbb6ab 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -161,7 +161,7 @@ export const run = (n) => saw.range(0, n).floor().segment(n); /** * @name binary - * Creates a discrete pattern using binary representation. + * Creates a pattern from a binary number. * @param {number} n - input number to convert to binary * @example * "hh".s().struct(binary(5)) @@ -174,7 +174,7 @@ export const binary = (n) => { /** * @name binaryN - * Creates a discrete pattern using binary representation. + * Creates a pattern from a binary number, padded to n bits long. * @param {number} n - input number to convert to binary * @param {number} nBits - pattern length, defaults to 16 * @example From e12dadb33fb9ec489df91fb76f87d7250deac9a1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 24 Dec 2024 00:59:03 +0100 Subject: [PATCH 12/28] write more --- website/src/pages/understand/voicings.mdx | 127 +++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/website/src/pages/understand/voicings.mdx b/website/src/pages/understand/voicings.mdx index d66e075e0..d255d8765 100644 --- a/website/src/pages/understand/voicings.mdx +++ b/website/src/pages/understand/voicings.mdx @@ -9,7 +9,7 @@ import Box from '@components/Box.astro'; # Understanding Chords and Voicings -Let's dig deeper into how chords and voicings work. +Let's dig deeper into how chords and voicings work in strudel. I'll try to keep theory jargon to a minimum, so hopefully this is approachable for anyone interested. ## What is a chord @@ -194,3 +194,128 @@ In a `voicing dictionary`, each chord symbol is assigned one or more voicings. The `voicing` function then picks the voicing that is closest to the `anchor` (defaults to `c5`). The handy thing about this approach is that a `voicing dictionary` can be used to play any chord progression with automated voice leading! + +## The default dictionary + +When using the default dictionary, you can use these chord symbols: + +``` +2 5 6 7 9 11 13 69 add9 +o h sus ^ - ^7 -7 7sus +h7 o7 ^9 ^13 ^7#11 ^9#11 +^7#5 -6 -69 -^7 -^9 -9 +-add9 -11 -7b5 h9 -b6 -#5 +7b9 7#9 7#11 7b5 7#5 9#11 +9b5 9#5 7b13 7#9#5 7#9b5 +7#9#11 7b9#11 7b9b5 7b9#5 +7b9#9 7b9b13 7alt 13#11 +13b9 13#9 7b9sus 7susadd3 +9sus 13sus 7b13sus +aug M m M7 m7 M9 M13 +M7#11 M9#11 M7#5 m6 m69 +m^7 -M7 m^9 -M9 m9 madd9 +m11 m7b5 mb6 m#5 mM7 mM9 +``` + +The available chords and the format is very much inspired by [ireal pro chords](https://technimo.helpshift.com/hc/en/3-ireal-pro/faq/88-chord-symbols-used-in-ireal-pro/). +Some symbols are synonymous: + +- "-" is the same as "m", for example C-7 = Cm7 +- "^" is the same as "M", for example C^7 = CM7 +- "+" is the same as "aug" + +You can decide which one's you prefer. There is no international standard for these symbols. +To get a full chord, the symbols have to be prefixed with a root pitch, e.g. D7#11 is the 7#11 chord relative to the pitch D. + +Here are all possible chords with root C: + +\`).voicing().room(.5)`} + punchcard +/> + +Note that the default dictionary contains multiple ways (= `voicings`) to play each chord symbol. +By default, the `voicing` function tries to minimize jumps. +You can alter the picked voicings in various ways, which are now explained in further detail: + +## anchor + +The `anchor` is a note that is used to align the voicings to: + +").chord("C").voicing().room(.5)`} punchcard /> + +By default, the anchor is the highest possible note the voicing can contain. +When deciding which voicing of the dictionary to pick for a certain chord, the voicing with a top note closest to the anchor wins. + +Note that the anchors in the above example match up with the top notes in the pianoroll. +Like `note`, anchor accepts either midi numbers or note names. + +## mode + +With `mode`, you can change the way the voicing relates to the `anchor`: + +").chord("C").anchor("c5").voicing().room(.5)`} + punchcard +/> + +The modes are: + +- `below`: the top note of the voicing is lower than or equal to the anchor (default) +- `above`: the bottom note of the voicing is higher than or equal to the anchor +- `duck`: the top note of the voicing is lower than the anchor +- `root`: the bottom note of the voicing is always the root note closest to the anchor + +The `anchor` can also be set from within the `mode` function: + +:c5").chord("C").voicing().room(.5)`} punchcard /> + +## n + +The `n` control can be used with `voicing` to select individual notes: + +>").voicing() +.clip("4 3 2 1").room(.5)`} + punchcard +/> + +## Example + +Here's an example of a Jazz Blues in F: + +\`) +$: n("7 8 [10 9] 8").set(chords).voicing().dec(.2) +$: chords.struct("- x - x").voicing().room(.5) +$: n("0 - 1 -").set(chords).mode("root:g2").voicing() +`} + punchcard +/> + +The chords are reused for melody, chords and bassline of the tune. From e4f89f072e3b1ed451d3b6133f2ef3d15c01defd Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 24 Dec 2024 01:05:01 +0100 Subject: [PATCH 13/28] delete timbre draft --- website/src/pages/understand/timbre.mdx | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 website/src/pages/understand/timbre.mdx diff --git a/website/src/pages/understand/timbre.mdx b/website/src/pages/understand/timbre.mdx deleted file mode 100644 index 47a8e2994..000000000 --- a/website/src/pages/understand/timbre.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Understanding Timbre -layout: ../../layouts/MainLayout.astro ---- - -import { MiniRepl } from '../../docs/MiniRepl'; -import { PitchSlider } from '../../components/PitchSlider'; -import Box from '@components/Box.astro'; - -# Understanding Timbre - -Let's learn what timbre is! From 49d4fb499f64eddc0a2f42a015164a6afeefff2b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 26 Dec 2024 01:29:48 +0100 Subject: [PATCH 14/28] some voicing doc edits --- website/src/pages/understand/voicings.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/website/src/pages/understand/voicings.mdx b/website/src/pages/understand/voicings.mdx index d255d8765..ba23246d6 100644 --- a/website/src/pages/understand/voicings.mdx +++ b/website/src/pages/understand/voicings.mdx @@ -92,7 +92,7 @@ For example we could add 12 to one or more notes in the chord: /> Notes that are 12 steps apart (= 1 `octave`) are considered to be equal in a harmonic sense, which is why they get the same note letter. -Here's the same example with note letterns: +Here's the same example with note letters: These voicings make the chords sound more connected and less jumpy, compared to the version without voicings. -The way chords interact is also called voice leading, reminiscent of how a choir voice would move through a sequence of chords. +The way chords interact is also called `voice leading`, reminiscent of how a choir voice would move through a sequence of chords. For example, try singing the top voice in the above example. Then try the same on the example without voice leading. Which one's easier? @@ -172,7 +173,8 @@ The `chord` and `voicing` functions can be used to automate that: ").voicing().room(.5)`} punchcard /> -Here we're also using chord symbols but the voicings will be automatically generated with smooth voice leading. +Here we're also using chord symbols but the voicings will be automatically generated with smooth `voice leading`, minimizing jumps. +It is inspired by the way a piano or guitar player would pick chords to accompany a song. ## Voicing Dictionaries From d30623dad47a36f7ab821dec873efee3bd3ebf90 Mon Sep 17 00:00:00 2001 From: Bernhard Wagner Date: Wed, 25 Dec 2024 23:01:52 +0100 Subject: [PATCH 15/28] minor amendments --- website/src/pages/understand/voicings.mdx | 42 ++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/website/src/pages/understand/voicings.mdx b/website/src/pages/understand/voicings.mdx index ba23246d6..7d9c13d90 100644 --- a/website/src/pages/understand/voicings.mdx +++ b/website/src/pages/understand/voicings.mdx @@ -14,7 +14,7 @@ I'll try to keep theory jargon to a minimum, so hopefully this is approachable f ## What is a chord -Playing more than one note at a time is generally called a chord. Here's an example: +Playing more than one note at a time is generally called a `chord`. Here's an example: ").room(.5)`} /> @@ -24,7 +24,7 @@ Here's the same with midi numbers: Here, we have two 3-note chords played in a loop. You could already stop here and write chords in this style, which is totally fine and gives you control over individual notes. -One downside is that it can be difficult to find good sounding chords and maybe you're yearning for a way to organize chords in some other way.. +One downside is that it can be difficult to find good sounding chords and maybe you're yearning for a way to organize chords in some other way. ## Labeling Chords @@ -49,16 +49,16 @@ These 4 shapes are the most common types of `triads` you will encounter: | shape | label | | ----- | ---------- | -| 0,3,6 | diminished | -| 0,3,7 | minor | | 0,4,7 | major | +| 0,3,7 | minor | +| 0,3,6 | diminished | | 0,4,8 | augmented | Here they are in succession: ".add("60")) + tune={`note("<[0,4,7] [0,3,7] [0,3,6] [0,4,8]>".add("60")) .room(.5)._pitchwheel()`} /> @@ -76,14 +76,14 @@ a e a e >\`)).room(.5)`} /> -These are the chords for "The house of the rising sun" by The Animals. -So far it doesn't sound too exiting but at least it's recognizable.. +These are the chords for "The House of the Rising Sun" by The Animals. +So far, it doesn't sound too exciting, but at least it's recognizable. ## Voicings -A `voicing` is one of many ways a certain chord shape could be played. -The term comes from choral music, where chords can be sung in different ways by changing which voice sings which note. -For example we could add 12 to one or more notes in the chord: +A `voicing` is one of many ways a certain chord shape can be arranged. +The term comes from choral music, where chords can be sung in different ways by assigning different notes to each voice. +For example we could add 12 semitones to one or more notes in the chord: -Notes that are 12 steps apart (= 1 `octave`) are considered to be equal in a harmonic sense, which is why they get the same note letter. +Notes that are 12 semitone steps apart (= 1 `octave`) are considered to be equal in a harmonic sense, which is why they get the same note letter. Here's the same example with note letters: -This type of voicings are also called `inversions`. There are many other ways we could `voice` this minor chord: +These types of voicings are also called `inversions`. There are many other ways we could `voice` this minor chord: -These voicings make the chords sound more connected and less jumpy, compared to the version without voicings. -The way chords interact is also called `voice leading`, reminiscent of how a choir voice would move through a sequence of chords. +These voicings make the chords sound more connected and less jumpy, compared to the earlier version, which didn't focus on voicing. +The way chords interact is also called `voice leading`, reminiscent of how an +individual choir voice would move through a sequence of chords. -For example, try singing the top voice in the above example. Then try the same on the example without voice leading. Which one's easier? +For example, try singing the top voice in the above example. Then try the same +on the example not focusing on voice leading. Which one's easier? -Naturally, there are many ways a progression of chords could be voiced and there is no clear right or wrong. +Naturally, there are many ways a progression of chords could be voiced and there is no definitive right or wrong. ## Chord Symbols -Musicians playing chord-based music often rely on a so called lead sheet, which is a simplified notation of a music piece. -The chords in those lead sheets are notated with symbols that allow a piece to be notated in a very concise manner. -A common way to write the chords "The House of the Rising Sun" would be: +Musicians playing chord-based music often use a `lead sheet`, which is a simplified notation for a piece of music. +These sheets condense the essential elements, such as chords, into symbols that make the music easy to read and follow. +For example, a lead sheet for "The House of the Rising Sun" might include chords written like this: ``` Am | C | D | F @@ -226,7 +228,7 @@ Some symbols are synonymous: - "^" is the same as "M", for example C^7 = CM7 - "+" is the same as "aug" -You can decide which one's you prefer. There is no international standard for these symbols. +You can decide which ones you prefer. There is no international standard for these symbols. To get a full chord, the symbols have to be prefixed with a root pitch, e.g. D7#11 is the 7#11 chord relative to the pitch D. Here are all possible chords with root C: From 426c5edf2530e781fa8ebfaf5d24b0f4885f21ca Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Fri, 27 Dec 2024 07:43:41 -0500 Subject: [PATCH 16/28] Update tests snapshot --- test/__snapshots__/examples.test.mjs.snap | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 6dce57390..88a728834 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -967,8 +967,22 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; +exports[`runs examples > example "binary +Creates a pattern from a binary number." example index 0 1`] = ` +[ + "[ 0/1 → 1/3 | s:hh ]", + "[ 2/3 → 1/1 | s:hh ]", + "[ 1/1 → 4/3 | s:hh ]", + "[ 5/3 → 2/1 | s:hh ]", + "[ 2/1 → 7/3 | s:hh ]", + "[ 8/3 → 3/1 | s:hh ]", + "[ 3/1 → 10/3 | s:hh ]", + "[ 11/3 → 4/1 | s:hh ]", +] +`; + exports[`runs examples > example "binaryN -Creates a discrete pattern using binary representation." example index 0 1`] = ` +Creates a pattern from a binary number, padded to n bits long." example index 0 1`] = ` [ "[ 0/1 → 1/16 | s:hh ]", "[ 1/16 → 1/8 | s:hh ]", From 8644b3d163fbf3006faf545dec68158a5f2c1d26 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:14:31 -0500 Subject: [PATCH 17/28] Fix accidental multi-line jsdoc name --- packages/core/signal.mjs | 6 ++++-- test/__snapshots__/examples.test.mjs.snap | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 31fdbb6ab..22f3df727 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -160,8 +160,9 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); export const run = (n) => saw.range(0, n).floor().segment(n); /** - * @name binary * Creates a pattern from a binary number. + * + * @name binary * @param {number} n - input number to convert to binary * @example * "hh".s().struct(binary(5)) @@ -173,8 +174,9 @@ export const binary = (n) => { }; /** - * @name binaryN * Creates a pattern from a binary number, padded to n bits long. + * + * @name binaryN * @param {number} n - input number to convert to binary * @param {number} nBits - pattern length, defaults to 16 * @example diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 88a728834..bff15fcc8 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -967,8 +967,7 @@ exports[`runs examples > example "begin" example index 0 1`] = ` ] `; -exports[`runs examples > example "binary -Creates a pattern from a binary number." example index 0 1`] = ` +exports[`runs examples > example "binary" example index 0 1`] = ` [ "[ 0/1 → 1/3 | s:hh ]", "[ 2/3 → 1/1 | s:hh ]", @@ -981,8 +980,7 @@ Creates a pattern from a binary number." example index 0 1`] = ` ] `; -exports[`runs examples > example "binaryN -Creates a pattern from a binary number, padded to n bits long." example index 0 1`] = ` +exports[`runs examples > example "binaryN" example index 0 1`] = ` [ "[ 0/1 → 1/16 | s:hh ]", "[ 1/16 → 1/8 | s:hh ]", From 9d029ac9145b475e7076b904dd577ee5837a31b3 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:14:58 -0500 Subject: [PATCH 18/28] Add binary and binaryN to learn pages --- website/src/pages/learn/factories.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx index 6a2e165b2..b43d3abfa 100644 --- a/website/src/pages/learn/factories.mdx +++ b/website/src/pages/learn/factories.mdx @@ -55,6 +55,14 @@ These are the equivalents used by the Mini Notation: ## run - + + +## binary + + + +## binaryN + + After Pattern Constructors, let's see what [Time Modifiers](/learn/time-modifiers) are available. From c6418789c445c50f64b198ad347bf09d10d49e33 Mon Sep 17 00:00:00 2001 From: Luke Heerman <43624284+heerman@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:49:56 -0500 Subject: [PATCH 19/28] Fix lint --- packages/core/signal.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 22f3df727..eaf2af8dc 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -161,7 +161,7 @@ export const run = (n) => saw.range(0, n).floor().segment(n); /** * Creates a pattern from a binary number. - * + * * @name binary * @param {number} n - input number to convert to binary * @example @@ -175,7 +175,7 @@ export const binary = (n) => { /** * Creates a pattern from a binary number, padded to n bits long. - * + * * @name binaryN * @param {number} n - input number to convert to binary * @param {number} nBits - pattern length, defaults to 16 From 7b914e7f2259ac21241b16be3ca0ff38c2ef52f9 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 2 Jan 2025 22:33:04 -0500 Subject: [PATCH 20/28] working --- packages/core/pattern.mjs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index f3f19f874..db1d9d0b7 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3100,3 +3100,23 @@ export let xfade = (a, pos, b) => { Pattern.prototype.xfade = function (pos, b) { return xfade(this, pos, b); }; + + +/** + * creates a structure pattern from divisions of a cycle + * especially useful for creating rhythms + * @name onSubCycle + * @synonyms onSub, os + * @example + * s("bd").os("0:7:10", 16) + * @example + * s("sd").os("4:12", 16) + */ +export const {onSubCycle, onSub, os} = register(['onSubCycle', 'onSub', 'os'], (times, div, pat) => { + if (typeof times === 'number') { + times = [times] + } + return stack( + ...times.map(t => pat.pressBy(_mod(t, div)/div).duration(1/div)) + )} +) From 1d144faed71e65d240dae549ca1d5543344414d3 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 2 Jan 2025 22:39:56 -0500 Subject: [PATCH 21/28] update snapshot --- packages/core/pattern.mjs | 11 +++------ test/__snapshots__/examples.test.mjs.snap | 30 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index db1d9d0b7..170e16335 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3101,7 +3101,6 @@ Pattern.prototype.xfade = function (pos, b) { return xfade(this, pos, b); }; - /** * creates a structure pattern from divisions of a cycle * especially useful for creating rhythms @@ -3112,11 +3111,9 @@ Pattern.prototype.xfade = function (pos, b) { * @example * s("sd").os("4:12", 16) */ -export const {onSubCycle, onSub, os} = register(['onSubCycle', 'onSub', 'os'], (times, div, pat) => { +export const { onSubCycle, onSub, os } = register(['onSubCycle', 'onSub', 'os'], (times, div, pat) => { if (typeof times === 'number') { - times = [times] + times = [times]; } - return stack( - ...times.map(t => pat.pressBy(_mod(t, div)/div).duration(1/div)) - )} -) + return stack(...times.map((t) => pat.pressBy(_mod(t, div) / div).duration(1 / div))); +}); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 7c21d4641..d258afe25 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -4902,6 +4902,36 @@ exports[`runs examples > example "often" example index 0 1`] = ` ] `; +exports[`runs examples > example "onSubCycle" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:bd duration:0.0625 ]", + "[ 7/16 → 1/1 | s:bd duration:0.0625 ]", + "[ 5/8 → 1/1 | s:bd duration:0.0625 ]", + "[ 1/1 → 2/1 | s:bd duration:0.0625 ]", + "[ 23/16 → 2/1 | s:bd duration:0.0625 ]", + "[ 13/8 → 2/1 | s:bd duration:0.0625 ]", + "[ 2/1 → 3/1 | s:bd duration:0.0625 ]", + "[ 39/16 → 3/1 | s:bd duration:0.0625 ]", + "[ 21/8 → 3/1 | s:bd duration:0.0625 ]", + "[ 3/1 → 4/1 | s:bd duration:0.0625 ]", + "[ 55/16 → 4/1 | s:bd duration:0.0625 ]", + "[ 29/8 → 4/1 | s:bd duration:0.0625 ]", +] +`; + +exports[`runs examples > example "onSubCycle" example index 1 1`] = ` +[ + "[ 1/4 → 1/1 | s:sd duration:0.0625 ]", + "[ 3/4 → 1/1 | s:sd duration:0.0625 ]", + "[ 5/4 → 2/1 | s:sd duration:0.0625 ]", + "[ 7/4 → 2/1 | s:sd duration:0.0625 ]", + "[ 9/4 → 3/1 | s:sd duration:0.0625 ]", + "[ 11/4 → 3/1 | s:sd duration:0.0625 ]", + "[ 13/4 → 4/1 | s:sd duration:0.0625 ]", + "[ 15/4 → 4/1 | s:sd duration:0.0625 ]", +] +`; + exports[`runs examples > example "orbit" example index 0 1`] = ` [ "[ 0/1 → 1/6 | s:hh delay:0.5 delaytime:0.25 orbit:1 ]", From f5127147de9947c0a742a7a6812c24e1b75cbf62 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 8 Jan 2025 00:43:02 -0500 Subject: [PATCH 22/28] channge function name --- packages/core/pattern.mjs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 170e16335..89c88a614 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3104,14 +3104,13 @@ Pattern.prototype.xfade = function (pos, b) { /** * creates a structure pattern from divisions of a cycle * especially useful for creating rhythms - * @name onSubCycle - * @synonyms onSub, os + * @name beat * @example - * s("bd").os("0:7:10", 16) + * s("bd").beat("0:7:10", 16) * @example - * s("sd").os("4:12", 16) + * s("sd").beat("4:12", 16) */ -export const { onSubCycle, onSub, os } = register(['onSubCycle', 'onSub', 'os'], (times, div, pat) => { +export const { beat } = register(['beat'], (times, div, pat) => { if (typeof times === 'number') { times = [times]; } From 7410ac5cecd58bb67da3713768fccb28a77f74c2 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 8 Jan 2025 00:44:40 -0500 Subject: [PATCH 23/28] snapshot --- test/__snapshots__/examples.test.mjs.snap | 60 +++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index d258afe25..0f649c366 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -954,6 +954,36 @@ exports[`runs examples > example "bank" example index 0 1`] = ` ] `; +exports[`runs examples > example "beat" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | s:bd duration:0.0625 ]", + "[ 7/16 → 1/1 | s:bd duration:0.0625 ]", + "[ 5/8 → 1/1 | s:bd duration:0.0625 ]", + "[ 1/1 → 2/1 | s:bd duration:0.0625 ]", + "[ 23/16 → 2/1 | s:bd duration:0.0625 ]", + "[ 13/8 → 2/1 | s:bd duration:0.0625 ]", + "[ 2/1 → 3/1 | s:bd duration:0.0625 ]", + "[ 39/16 → 3/1 | s:bd duration:0.0625 ]", + "[ 21/8 → 3/1 | s:bd duration:0.0625 ]", + "[ 3/1 → 4/1 | s:bd duration:0.0625 ]", + "[ 55/16 → 4/1 | s:bd duration:0.0625 ]", + "[ 29/8 → 4/1 | s:bd duration:0.0625 ]", +] +`; + +exports[`runs examples > example "beat" example index 1 1`] = ` +[ + "[ 1/4 → 1/1 | s:sd duration:0.0625 ]", + "[ 3/4 → 1/1 | s:sd duration:0.0625 ]", + "[ 5/4 → 2/1 | s:sd duration:0.0625 ]", + "[ 7/4 → 2/1 | s:sd duration:0.0625 ]", + "[ 9/4 → 3/1 | s:sd duration:0.0625 ]", + "[ 11/4 → 3/1 | s:sd duration:0.0625 ]", + "[ 13/4 → 4/1 | s:sd duration:0.0625 ]", + "[ 15/4 → 4/1 | s:sd duration:0.0625 ]", +] +`; + exports[`runs examples > example "begin" example index 0 1`] = ` [ "[ 0/1 → 1/2 | s:rave begin:0 ]", @@ -4902,36 +4932,6 @@ exports[`runs examples > example "often" example index 0 1`] = ` ] `; -exports[`runs examples > example "onSubCycle" example index 0 1`] = ` -[ - "[ 0/1 → 1/1 | s:bd duration:0.0625 ]", - "[ 7/16 → 1/1 | s:bd duration:0.0625 ]", - "[ 5/8 → 1/1 | s:bd duration:0.0625 ]", - "[ 1/1 → 2/1 | s:bd duration:0.0625 ]", - "[ 23/16 → 2/1 | s:bd duration:0.0625 ]", - "[ 13/8 → 2/1 | s:bd duration:0.0625 ]", - "[ 2/1 → 3/1 | s:bd duration:0.0625 ]", - "[ 39/16 → 3/1 | s:bd duration:0.0625 ]", - "[ 21/8 → 3/1 | s:bd duration:0.0625 ]", - "[ 3/1 → 4/1 | s:bd duration:0.0625 ]", - "[ 55/16 → 4/1 | s:bd duration:0.0625 ]", - "[ 29/8 → 4/1 | s:bd duration:0.0625 ]", -] -`; - -exports[`runs examples > example "onSubCycle" example index 1 1`] = ` -[ - "[ 1/4 → 1/1 | s:sd duration:0.0625 ]", - "[ 3/4 → 1/1 | s:sd duration:0.0625 ]", - "[ 5/4 → 2/1 | s:sd duration:0.0625 ]", - "[ 7/4 → 2/1 | s:sd duration:0.0625 ]", - "[ 9/4 → 3/1 | s:sd duration:0.0625 ]", - "[ 11/4 → 3/1 | s:sd duration:0.0625 ]", - "[ 13/4 → 4/1 | s:sd duration:0.0625 ]", - "[ 15/4 → 4/1 | s:sd duration:0.0625 ]", -] -`; - exports[`runs examples > example "orbit" example index 0 1`] = ` [ "[ 0/1 → 1/6 | s:hh delay:0.5 delaytime:0.25 orbit:1 ]", From 7b6074ccef28d0c63a7c210ee2bccc258ebafe3b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 15 Jan 2025 23:30:19 -0500 Subject: [PATCH 24/28] update function --- packages/core/pattern.mjs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 89c88a614..9279ec3c8 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3110,9 +3110,12 @@ Pattern.prototype.xfade = function (pos, b) { * @example * s("sd").beat("4:12", 16) */ -export const { beat } = register(['beat'], (times, div, pat) => { - if (typeof times === 'number') { - times = [times]; - } - return stack(...times.map((t) => pat.pressBy(_mod(t, div) / div).duration(1 / div))); -}); +const __beat = join => (t, div, pat) => { + t = Fraction(t).mod(div); + div = Fraction(div); + const b = t.div(div); + const e = t.add(1).div(div); + return join(pat.fmap(x => pure(x)._compress(b,e))); +} + +export const {beat, beatOut} = register(['beat', 'beatOut'], __beat(x => x.innerJoin())); \ No newline at end of file From c33c8b5469edf2af191737297eee5e5e3c0d5c90 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 15 Jan 2025 23:30:56 -0500 Subject: [PATCH 25/28] remove possibly not needed alt --- packages/core/pattern.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 9279ec3c8..9e5d2800b 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3118,4 +3118,4 @@ const __beat = join => (t, div, pat) => { return join(pat.fmap(x => pure(x)._compress(b,e))); } -export const {beat, beatOut} = register(['beat', 'beatOut'], __beat(x => x.innerJoin())); \ No newline at end of file +export const {beat} = register(['beat'], __beat(x => x.innerJoin())); \ No newline at end of file From 2348023b223f44e878ee2645133a76277d0e6434 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 15 Jan 2025 23:35:36 -0500 Subject: [PATCH 26/28] merge main --- test/__snapshots__/examples.test.mjs.snap | 28 +++++++---------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 76e05997e..6b149bc1b 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -956,31 +956,19 @@ exports[`runs examples > example "bank" example index 0 1`] = ` exports[`runs examples > example "beat" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:bd duration:0.0625 ]", - "[ 7/16 → 1/1 | s:bd duration:0.0625 ]", - "[ 5/8 → 1/1 | s:bd duration:0.0625 ]", - "[ 1/1 → 2/1 | s:bd duration:0.0625 ]", - "[ 23/16 → 2/1 | s:bd duration:0.0625 ]", - "[ 13/8 → 2/1 | s:bd duration:0.0625 ]", - "[ 2/1 → 3/1 | s:bd duration:0.0625 ]", - "[ 39/16 → 3/1 | s:bd duration:0.0625 ]", - "[ 21/8 → 3/1 | s:bd duration:0.0625 ]", - "[ 3/1 → 4/1 | s:bd duration:0.0625 ]", - "[ 55/16 → 4/1 | s:bd duration:0.0625 ]", - "[ 29/8 → 4/1 | s:bd duration:0.0625 ]", + "[ 0/1 → 1/16 | s:bd ]", + "[ 1/1 → 17/16 | s:bd ]", + "[ 2/1 → 33/16 | s:bd ]", + "[ 3/1 → 49/16 | s:bd ]", ] `; exports[`runs examples > example "beat" example index 1 1`] = ` [ - "[ 1/4 → 1/1 | s:sd duration:0.0625 ]", - "[ 3/4 → 1/1 | s:sd duration:0.0625 ]", - "[ 5/4 → 2/1 | s:sd duration:0.0625 ]", - "[ 7/4 → 2/1 | s:sd duration:0.0625 ]", - "[ 9/4 → 3/1 | s:sd duration:0.0625 ]", - "[ 11/4 → 3/1 | s:sd duration:0.0625 ]", - "[ 13/4 → 4/1 | s:sd duration:0.0625 ]", - "[ 15/4 → 4/1 | s:sd duration:0.0625 ]", + "[ 1/48 → 1/12 | s:sd ]", + "[ 49/48 → 13/12 | s:sd ]", + "[ 97/48 → 25/12 | s:sd ]", + "[ 145/48 → 37/12 | s:sd ]", ] `; From d2a9b9b750319c6bb13f603089853f6461667833 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 15 Jan 2025 23:51:57 -0500 Subject: [PATCH 27/28] format --- packages/core/pattern.mjs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 0c8e0c3f5..72596b335 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3122,12 +3122,15 @@ Pattern.prototype.xfade = function (pos, b) { * @example * s("sd").beat("4:12", 16) */ -const __beat = join => (t, div, pat) => { +const __beat = (join) => (t, div, pat) => { t = Fraction(t).mod(div); div = Fraction(div); const b = t.div(div); const e = t.add(1).div(div); - return join(pat.fmap(x => pure(x)._compress(b,e))); -} + return join(pat.fmap((x) => pure(x)._compress(b, e))); +}; -export const {beat} = register(['beat'], __beat(x => x.innerJoin())); \ No newline at end of file +export const { beat } = register( + ['beat'], + __beat((x) => x.innerJoin()), +); From 00b0341839afae84b1ca7a06a14b91721a17eadc Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Thu, 16 Jan 2025 11:18:33 +0000 Subject: [PATCH 28/28] Fix sometimes (#1243) * test and fix for sometimes bug --- packages/core/signal.mjs | 2 +- packages/core/test/pattern.test.mjs | 10 ++++++++++ test/__snapshots__/tunes.test.mjs.snap | 10 ++++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 36f07df54..fb9c0b833 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -499,7 +499,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t export const sometimesBy = register('sometimesBy', function (patx, func, pat) { return reify(patx) - .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x)))) + .fmap((x) => stack(pat._degradeBy(x), func(pat)._undegradeBy(1 - x))) .innerJoin(); }); diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 31ec48681..8e2042522 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -52,6 +52,7 @@ import { stackCentre, s_cat, calculateTactus, + sometimes, } from '../index.mjs'; import { steady } from '../signal.mjs'; @@ -1253,4 +1254,13 @@ describe('Pattern', () => { expect(s('bev').chop(8).loopAt(2).tactus).toStrictEqual(Fraction(4)); }); }); + describe('sometimes', () => { + it('works with constant functions', () => { + expect( + pure('a') + .sometimes((x) => pure('b')) + .fast(16).firstCycleValues.length, + ).toStrictEqual(16); + }); + }); }); diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index f851077a8..840f9f329 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -8,6 +8,7 @@ exports[`renders tunes > tune: amensister 1`] = ` "[ 0/1 → 1/4 | n:0 s:amencutup room:0.5 ]", "[ 1/16 → 1/8 | s:breath room:1 shape:0.6 begin:0.875 end:0.9375 ]", "[ 1/8 → 3/16 | s:breath room:1 shape:0.6 begin:0.8125 end:0.875 ]", + "[ 1/8 → 1/4 | n:0 s:amencutup room:0.5 ]", "[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]", "[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]", "[ 3/16 → 1/4 | s:breath room:1 shape:0.6 begin:0.75 end:0.8125 ]", @@ -17,22 +18,20 @@ exports[`renders tunes > tune: amensister 1`] = ` "[ 1/4 → 3/8 | note:A1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.7878869297153 ]", "[ 5/16 → 3/8 | s:breath room:1 shape:0.6 begin:0.625 end:0.6875 ]", "[ 3/8 → 7/16 | s:breath room:1 shape:0.6 begin:0.5625 end:0.625 ]", - "[ 3/8 → 1/2 | n:1 s:amencutup room:0.5 ]", "[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]", "[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]", "[ 7/16 → 1/2 | s:breath room:1 shape:0.6 begin:0.5 end:0.5625 ]", "[ 1/2 → 9/16 | s:breath room:1 shape:0.6 begin:0.4375 end:0.5 ]", + "[ 1/2 → 5/8 | n:2 s:amencutup room:0.5 ]", "[ 1/2 → 3/4 | n:2 s:amencutup room:0.5 ]", "[ 9/16 → 5/8 | s:breath room:1 shape:0.6 begin:0.375 end:0.4375 ]", "[ 5/8 → 11/16 | s:breath room:1 shape:0.6 begin:0.3125 end:0.375 ]", "[ 11/16 → 3/4 | s:breath room:1 shape:0.6 begin:0.25 end:0.3125 ]", "[ 3/4 → 13/16 | s:breath room:1 shape:0.6 begin:0.1875 end:0.25 ]", - "[ 3/4 → 7/8 | n:3 s:amencutup room:0.5 ]", "[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]", "[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]", "[ 13/16 → 7/8 | s:breath room:1 shape:0.6 begin:0.125 end:0.1875 ]", "[ 7/8 → 15/16 | s:breath room:1 shape:0.6 begin:0.0625 end:0.125 ]", - "[ 7/8 → 1/1 | n:3 s:amencutup room:0.5 ]", "[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]", "[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]", "[ 15/16 → 1/1 | s:breath room:1 shape:0.6 begin:0 end:0.0625 ]", @@ -6906,10 +6905,10 @@ exports[`renders tunes > tune: flatrave 1`] = ` "[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/8 → 1/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", - "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]", - "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/4 → 3/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", "[ 3/8 → 1/2 | s:hh n:1 end:0.020023446730265706 bank:RolandTR909 room:0.5 gain:0.4 ]", + "[ 1/2 → 5/8 | s:hh n:1 speed:0.5 delay:0.5 end:0.020048626493108724 bank:RolandTR909 room:0.5 gain:0.4 ]", + "[ 1/2 → 5/8 | s:hh n:1 speed:0.5 delay:0.5 end:0.020048626493108724 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 1/2 → 5/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", "[ 1/2 → 1/1 | s:bd bank:RolandTR909 ]", "[ 1/2 → 1/1 | s:cp bank:RolandTR909 ]", @@ -6917,7 +6916,6 @@ exports[`renders tunes > tune: flatrave 1`] = ` "[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 5/8 → 3/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", - "[ 3/4 → 7/8 | s:hh n:1 end:0.02013941880355398 bank:RolandTR909 room:0.5 gain:0.4 ]", "[ 7/8 → 1/1 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]", ] `;