diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index cd8c5871a..3c21bd1b2 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3644,35 +3644,51 @@ export const parray = (pats) => { * Can also be used to create a new synth via `s('user').partials(...)` * * @name partials - * @param {number[] | Pattern} partials List of [0, 1] magnitudes for partials. 0th entry is the first harmonic (i.e. DC offset is skipped) + * @param {number[] | Pattern} magnitudes List of [0, 1] magnitudes for partials. 0th entry is the fundamental harmonic (i.e. DC offset is skipped) * @example * s("user").seg(16).n(irand(8)).scale("A:major") * .partials([1, 0, 1, 0, 0, 1]) * @example * s("saw").seg(8).n(irand(12)).scale("G#:minor") - * .partials(binaryL(256)) + * .partials(randL(16)) */ -export const { partials } = register('partials', (list, pat) => { - debugger; +register('partials', (list, pat) => { if (Array.isArray(list)) { list = parray(list); } - return pat.withValue((v) => (l) => ({...v, partials: l})).appLeft(list); + return pat.withValue((v) => (l) => ({ ...v, partials: l })).appLeft(list); }); +// Also create a top-level function +export const partials = (list) => { + if (Array.isArray(list)) { + list = parray(list); + } + return list.as('partials'); +}; + /** * Rotates the harmonics of one of the core synths ('sine', 'tri', 'saw', 'user', ..) by a list of phases * * @name phases - * @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the first phase (i.e. DC offset is skipped) + * @param {number[] | Pattern} phases List of [0, 1) phases for partials. 0th entry is the fundamental phase (i.e. DC offset is skipped) * @example * s("saw").seg(8).n(irand(12)).scale("G#:minor") - * .partials(binaryL(256)) - * .phases(randL(20)) + * .partials(randL(16)) + * .phases(randL(16)) */ -export const { phases } = register('phases', (list, pat) => { +register('phases', (list, pat) => { if (Array.isArray(list)) { list = parray(list); } - return pat.withValue((v) => (l) => ({...v, phases: l})).appLeft(list); + pat = pat ?? pure({}); + return pat.withValue((v) => (l) => ({ ...v, phases: l })).appLeft(list); }); + +// Also create a top-level function +export const phases = (list) => { + if (Array.isArray(list)) { + list = parray(list); + } + return list.as('phases'); +}; diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 625f40756..1df5c8776 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -796,7 +796,7 @@ describe('Pattern', () => { }); }); describe('apply', () => { - (it('Can apply a function', () => { + it('Can apply a function', () => { expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle()); }), it('Can apply a pattern of functions', () => { @@ -804,7 +804,7 @@ describe('Pattern', () => { expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual( sequence('a', 'b').fast(2, 3).firstCycle(), ); - })); + }); }); describe('layer', () => { it('Can layer up multiple functions', () => { diff --git a/website/src/components/Header/Search.css b/website/src/components/Header/Search.css index b14146cbd..456ef9f6b 100644 --- a/website/src/components/Header/Search.css +++ b/website/src/components/Header/Search.css @@ -18,8 +18,8 @@ --docsearch-modal-background: var(--background); --docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%); --docsearch-key-gradient: var(--foreground); - --docsearch-key-shadow: - inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), 0 1px 2px 1px var(--gutterBackground); + --docsearch-key-shadow: inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), + 0 1px 2px 1px var(--gutterBackground); } .dark { --docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%); diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index b209dc9c0..6fc5b6925 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -48,7 +48,7 @@ You can also use the `crackle` type to play some subtle noise crackles. You can ### Additive Synthesis -Waveforms are often composed of several [harmonics](https://en.wikipedia.org/wiki/Harmonic) above a fundamental frequency, lying at integer multiples. These overtones combine to give a sound its unique timbral quality. +Periodic waveforms are composed of several [harmonics](https://en.wikipedia.org/wiki/Harmonic) above a fundamental frequency, lying at integer multiples. These overtones combine to give a sound its unique timbral quality. For the basic waveforms, we offer you control over these harmonics with the `partials` and `phases` functions. @@ -60,7 +60,7 @@ For the basic waveforms, we offer you control over these harmonics with the `par client:idle tune={`note("c2 >".fast(2)) .sound("sawtooth") -.partials([1, 1, 0, 1]) +.partials([1, 1, "<1 0>", "<1 0>", "<1 0>", "<1 0>", "<1 0>"]) ._scope()`} /> @@ -74,25 +74,38 @@ For the basic waveforms, we offer you control over these harmonics with the `par ._scope()`} /> -We may algorithmically construct lists of partials with Javascript code like: +We may algorithmically construct lists of magnitudes with Javascript code like: >".fast(2)) -.sound("user") +.sound("saw") .partials(new Array(numHarmonics).fill(1)) ._scope()`} /> -This approach can act as a form of bandlimiting. `partials` is also compatible with pattern functions designed to produce lists, like `randL` or `binaryL`: +which acts as a spectral filter or >".fast(2)) + tune={`note("c2 >").fast(2) .sound("user") -.partials(randL(8)) +.partials(new Array(50).fill(0) + .map((_, idx) => ((-1) ** (idx + 1)) / (idx + 1)) +) +._scope()`} +/> + +which recovers a familiar waveform. + +`partials` is also compatible with pattern functions designed to produce lists, like `randL` or `binaryL`: + +>").fast(2) +.sound("user") +.partials(randL(10)) ._scope()`} /> @@ -110,17 +123,16 @@ Note that the first value in the `partials` array controls the magnitude of the #### Phases -We mentioned that our sounds can be broken into a constituent set of harmonics above a fundamental frequency. These are defined by two values: their magnitude (how loud they are) and their [phase](https://en.wikipedia.org/wiki/Phase_(waves)), which can be thought of as which point in its cycle each sine wave is initialized at when we begin adding them. +Earlier, we mentioned that periodic waveforms can be broken into a set of harmonics above a fundamental frequency. Each harmonic has two defining properties: its magnitude (how loud it is) and its phase, which determines where in its cycle that sine wave starts when the waveform is built. These phases too can be declared in Strudel and can give your sounds interesting depth. >".fast(2)) -.sound("user") -.partials(randL(8)) -.phases(randL(8).late(0.3)) + tune={`note("c2 >".fast(2)) +.sound("saw") +.partials(randL(100)) +.phases(randL(100)) ._scope()`} />