mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 21:58:37 +00:00
commit
d18116fcbe
@ -23,7 +23,7 @@ const generic_params = [
|
||||
* @name s
|
||||
* @param {string | Pattern} sound The sound / pattern of sounds to pick
|
||||
* @example
|
||||
* s("bd hh").out()
|
||||
* s("bd hh")
|
||||
*
|
||||
*/
|
||||
['s', 's', 'sound'],
|
||||
@ -67,7 +67,7 @@ const generic_params = [
|
||||
* @name gain
|
||||
* @param {number | Pattern} amount gain.
|
||||
* @example
|
||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1").out()
|
||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -129,7 +129,7 @@ const generic_params = [
|
||||
* @name bandf
|
||||
* @param {number | Pattern} frequency center frequency
|
||||
* @example
|
||||
* s("bd sd,hh*3").bandf("<1000 2000 4000 8000>").out()
|
||||
* s("bd sd,hh*3").bandf("<1000 2000 4000 8000>")
|
||||
*
|
||||
*/
|
||||
['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'],
|
||||
@ -140,7 +140,7 @@ const generic_params = [
|
||||
* @name bandq
|
||||
* @param {number | Pattern} q q factor
|
||||
* @example
|
||||
* s("bd sd").bandf(500).bandq("<0 1 2 3>").out()
|
||||
* s("bd sd").bandf(500).bandq("<0 1 2 3>")
|
||||
*
|
||||
*/
|
||||
['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'],
|
||||
@ -152,7 +152,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
|
||||
* @example
|
||||
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/')
|
||||
* s("rave").begin("<0 .25 .5 .75>").out()
|
||||
* s("rave").begin("<0 .25 .5 .75>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -167,7 +167,7 @@ const generic_params = [
|
||||
* @name end
|
||||
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
|
||||
* @example
|
||||
* s("bd*2,ho*4").end("<.1 .2 .5 1>").out()
|
||||
* s("bd*2,oh*4").end("<.1 .2 .5 1>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -205,7 +205,7 @@ const generic_params = [
|
||||
* @name crush
|
||||
* @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
|
||||
* @example
|
||||
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>").out()
|
||||
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -214,12 +214,12 @@ const generic_params = [
|
||||
'bit crushing, a pattern of numbers from 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).',
|
||||
],
|
||||
/**
|
||||
* fake-resampling for lowering the sample rate
|
||||
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
|
||||
*
|
||||
* @name coarse
|
||||
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
|
||||
* @example
|
||||
* s("bd sd,hh*4").coarse("<1 4 8 16 32>").out()
|
||||
* s("bd sd,hh*4").coarse("<1 4 8 16 32>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -256,7 +256,7 @@ const generic_params = [
|
||||
* @name cutoff
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @example
|
||||
* s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>").out()
|
||||
* s("bd sd,hh*3").cutoff("<4000 2000 1000 500 200 100>")
|
||||
*
|
||||
*/
|
||||
// TODO: add lpf synonym
|
||||
@ -267,7 +267,7 @@ const generic_params = [
|
||||
* @name hcutoff
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @example
|
||||
* s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>").out()
|
||||
* s("bd sd,hh*4").hcutoff("<4000 2000 1000 500 200 100>")
|
||||
*
|
||||
*/
|
||||
// TODO: add hpf synonym
|
||||
@ -280,9 +280,9 @@ const generic_params = [
|
||||
* Applies the resonance of the high-pass filter.
|
||||
*
|
||||
* @name hresonance
|
||||
* @param {number | Pattern} q resonance factor between 0 and 1
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @example
|
||||
* s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>").out()
|
||||
* s("bd sd,hh*4").hcutoff(2000).hresonance("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -295,9 +295,9 @@ const generic_params = [
|
||||
* Applies the cutoff frequency of the low-pass filter.
|
||||
*
|
||||
* @name resonance
|
||||
* @param {number | Pattern} q resonance factor between 0 and 1
|
||||
* @param {number | Pattern} q resonance factor between 0 and 50
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>").out()
|
||||
* s("bd sd,hh*4").cutoff(2000).resonance("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['f', 'resonance', 'a pattern of numbers from 0 to 1. Specifies the resonance of the low-pass filter.'],
|
||||
@ -371,7 +371,7 @@ const generic_params = [
|
||||
* @name fadeTime
|
||||
* @param {number | Pattern} time between 0 and 1
|
||||
* @example
|
||||
* s("ho*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
|
||||
* s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -496,7 +496,7 @@ const generic_params = [
|
||||
* @name pan
|
||||
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
|
||||
* @example
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>").out()
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -599,7 +599,7 @@ const generic_params = [
|
||||
* @name shape
|
||||
* @param {number | Pattern} distortion between 0 and 1
|
||||
* @example
|
||||
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>").out()
|
||||
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
@ -665,7 +665,7 @@ const generic_params = [
|
||||
* @param {string | Pattern} vowel You can use a e i o u.
|
||||
* @example
|
||||
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
|
||||
* .vowel("<a e i <o u>>").out()
|
||||
* .vowel("<a e i <o u>>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
|
||||
@ -32,52 +32,52 @@ const euclid = (pulses, steps, rotation = 0) => {
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* // The Cuban tresillo pattern.
|
||||
* n("c3").euclid(3,8).out()
|
||||
* note("c3").euclid(3,8)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example // A thirteenth century Persian rhythm called Khafif-e-ramal.
|
||||
* n("c3").euclid(2,5)
|
||||
* note("c3").euclid(2,5)
|
||||
* @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
|
||||
* "c3".euclid(3,4)
|
||||
* note("c3").euclid(3,4)
|
||||
* @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm.
|
||||
* "c3".euclid(3,5,2)
|
||||
* note("c3").euclid(3,5,2)
|
||||
* @example // A Ruchenitza rhythm used in a Bulgarian folk-dance.
|
||||
* "c3".euclid(3,7)
|
||||
* note("c3").euclid(3,7)
|
||||
* @example // The Cuban tresillo pattern.
|
||||
* "c3".euclid(3,8)
|
||||
* note("c3").euclid(3,8)
|
||||
* @example // Another Ruchenitza Bulgarian folk-dance rhythm.
|
||||
* "c3".euclid(4,7)
|
||||
* note("c3").euclid(4,7)
|
||||
* @example // The Aksak rhythm of Turkey.
|
||||
* "c3".euclid(4,9)
|
||||
* note("c3").euclid(4,9)
|
||||
* @example // The metric pattern used by Frank Zappa in his piece titled Outside Now.
|
||||
* "c3".euclid(4,11)
|
||||
* note("c3").euclid(4,11)
|
||||
* @example // Yields the York-Samai pattern, a popular Arab rhythm.
|
||||
* "c3".euclid(5,6)
|
||||
* note("c3").euclid(5,6)
|
||||
* @example // The Nawakhat pattern, another popular Arab rhythm.
|
||||
* "c3".euclid(5,7)
|
||||
* note("c3").euclid(5,7)
|
||||
* @example // The Cuban cinquillo pattern.
|
||||
* "c3".euclid(5,8)
|
||||
* note("c3").euclid(5,8)
|
||||
* @example // A popular Arab rhythm called Agsag-Samai.
|
||||
* "c3".euclid(5,9)
|
||||
* note("c3").euclid(5,9)
|
||||
* @example // The metric pattern used by Moussorgsky in Pictures at an Exhibition.
|
||||
* "c3".euclid(5,11)
|
||||
* note("c3").euclid(5,11)
|
||||
* @example // The Venda clapping pattern of a South African children’s song.
|
||||
* "c3".euclid(5,12)
|
||||
* note("c3").euclid(5,12)
|
||||
* @example // The Bossa-Nova rhythm necklace of Brazil.
|
||||
* "c3".euclid(5,16)
|
||||
* note("c3").euclid(5,16)
|
||||
* @example // A typical rhythm played on the Bendir (frame drum).
|
||||
* "c3".euclid(7,8)
|
||||
* note("c3").euclid(7,8)
|
||||
* @example // A common West African bell pattern.
|
||||
* "c3".euclid(7,12)
|
||||
* note("c3").euclid(7,12)
|
||||
* @example // A Samba rhythm necklace from Brazil.
|
||||
* "c3".euclid(7,16,14)
|
||||
* note("c3").euclid(7,16,14)
|
||||
* @example // A rhythm necklace used in the Central African Republic.
|
||||
* "c3".euclid(9,16)
|
||||
* note("c3").euclid(9,16)
|
||||
* @example // A rhythm necklace of the Aka Pygmies of Central Africa.
|
||||
* "c3".euclid(11,24,14)
|
||||
* note("c3").euclid(11,24,14)
|
||||
* @example // Another rhythm necklace of the Aka Pygmies of the upper Sangha.
|
||||
* "c3".euclid(13,24,5)
|
||||
* note("c3").euclid(13,24,5)
|
||||
*/
|
||||
Pattern.prototype.euclid = function (pulses, steps, rotation = 0) {
|
||||
return this.struct(euclid(pulses, steps, rotation));
|
||||
@ -88,7 +88,7 @@ Pattern.prototype.euclid = function (pulses, steps, rotation = 0) {
|
||||
* @name euclidLegato
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8).out()
|
||||
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
|
||||
*/
|
||||
Pattern.prototype.euclidLegato = function (pulses, steps, rotation = 0) {
|
||||
const bin_pat = euclid(pulses, steps, rotation);
|
||||
|
||||
@ -472,7 +472,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0.5 1.5 2.5".round().scale('C major')
|
||||
* "0.5 1.5 2.5".round().scale('C major').note()
|
||||
*/
|
||||
round() {
|
||||
return this._asNumber().fmap((v) => Math.round(v));
|
||||
@ -524,7 +524,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4)).out()
|
||||
* s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4))
|
||||
*/
|
||||
_range(min, max) {
|
||||
return this.mul(max - min).add(min);
|
||||
@ -704,7 +704,7 @@ export class Pattern {
|
||||
* @name apply
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4"))
|
||||
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
|
||||
*/
|
||||
_apply(func) {
|
||||
return func(this);
|
||||
@ -718,7 +718,7 @@ export class Pattern {
|
||||
* @example
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
|
||||
* .layer(x=>x.add("0,2"))
|
||||
* .scale('C minor').note().out()
|
||||
* .scale('C minor').note()
|
||||
*/
|
||||
layer(...funcs) {
|
||||
return stack(...funcs.map((func) => func(this)));
|
||||
@ -756,11 +756,13 @@ export class Pattern {
|
||||
const cycle = begin.sam();
|
||||
const beginPos = begin.sub(cycle).div(factor).min(1);
|
||||
const endPos = end.sub(cycle).div(factor).min(1);
|
||||
const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos))
|
||||
const newWhole = !hap.whole ? undefined : new TimeSpan(
|
||||
newPart.begin.sub(begin.sub(hap.whole.begin).div(factor)),
|
||||
newPart.end.add(hap.whole.end.sub(end).div(factor))
|
||||
);
|
||||
const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos));
|
||||
const newWhole = !hap.whole
|
||||
? undefined
|
||||
: new TimeSpan(
|
||||
newPart.begin.sub(begin.sub(hap.whole.begin).div(factor)),
|
||||
newPart.end.add(hap.whole.end.sub(end).div(factor)),
|
||||
);
|
||||
return new Hap(newWhole, newPart, hap.value, hap.context);
|
||||
};
|
||||
return this.withQuerySpan(qf)._withHap(ef)._splitQueries();
|
||||
@ -796,7 +798,7 @@ export class Pattern {
|
||||
* @param {number | Pattern} factor speed up factor
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("<bd sd> hh").fast(2).out() // s("[<bd sd> hh]*2").out()
|
||||
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
|
||||
*/
|
||||
_fast(factor) {
|
||||
const fastQuery = this.withQueryTime((t) => t.mul(factor));
|
||||
@ -811,7 +813,7 @@ export class Pattern {
|
||||
* @param {number | Pattern} factor slow down factor
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("<bd sd> hh").slow(2).out() // s("[<bd sd> hh]/2").out()
|
||||
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
|
||||
*/
|
||||
_slow(factor) {
|
||||
return this._fast(Fraction(1).div(factor));
|
||||
@ -841,7 +843,7 @@ export class Pattern {
|
||||
* .chop(4)
|
||||
* .rev() // reverse order of chops
|
||||
* .loopAt(4,1) // fit sample into 4 cycles
|
||||
* .out()
|
||||
*
|
||||
*/
|
||||
_chop(n) {
|
||||
const slices = Array.from({ length: n }, (x, i) => i);
|
||||
@ -872,7 +874,7 @@ export class Pattern {
|
||||
* @param {number | Pattern} cycles number of cycles to nudge left
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "bd ~".stack("hh ~".early(.1)).s().out()
|
||||
* "bd ~".stack("hh ~".early(.1)).s()
|
||||
*/
|
||||
_early(offset) {
|
||||
offset = Fraction(offset);
|
||||
@ -887,7 +889,7 @@ export class Pattern {
|
||||
* @param {number | Pattern} cycles number of cycles to nudge right
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "bd ~".stack("hh ~".late(.1)).s().out()
|
||||
* "bd ~".stack("hh ~".late(.1)).s()
|
||||
*/
|
||||
_late(offset) {
|
||||
offset = Fraction(offset);
|
||||
@ -923,9 +925,9 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "c3,eb3,g3"
|
||||
* note("c3,eb3,g3")
|
||||
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
|
||||
* .slow(4).note().out()
|
||||
* .slow(4)
|
||||
*/
|
||||
// struct(...binary_pats) {
|
||||
// // Re structure the pattern according to a binary pattern (false values are dropped)
|
||||
@ -982,7 +984,7 @@ export class Pattern {
|
||||
* @param {function} func
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5))
|
||||
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note()
|
||||
*/
|
||||
when(binary_pat, func) {
|
||||
//binary_pat = sequence(binary_pat)
|
||||
@ -1001,7 +1003,7 @@ export class Pattern {
|
||||
* @param {function} func function to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "c3 eb3 g3".off(1/8, x=>x.add(7))
|
||||
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
|
||||
*/
|
||||
off(time_pat, func) {
|
||||
return stack(this, func(this.late(time_pat)));
|
||||
@ -1015,7 +1017,7 @@ export class Pattern {
|
||||
* @param {function} func function to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("c3 d3 e3 g3").every(4, x=>x.rev()).out()
|
||||
* note("c3 d3 e3 g3").every(4, x=>x.rev())
|
||||
*/
|
||||
every(n, func) {
|
||||
const pat = this;
|
||||
@ -1032,7 +1034,7 @@ export class Pattern {
|
||||
* @param {function} func function to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("c3 d3 e3 g3").every(4, x=>x.rev()).out()
|
||||
* note("c3 d3 e3 g3").every(4, x=>x.rev())
|
||||
*/
|
||||
every(n, func) {
|
||||
const pat = this;
|
||||
@ -1049,7 +1051,7 @@ export class Pattern {
|
||||
* @param {function} func function to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("c3 d3 e3 g3").every(4, x=>x.rev()).out()
|
||||
* note("c3 d3 e3 g3").every(4, x=>x.rev())
|
||||
*/
|
||||
each(n, func) {
|
||||
const pat = this;
|
||||
@ -1075,7 +1077,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "c3 d3 e3 g3".rev()
|
||||
* note("c3 d3 e3 g3").rev()
|
||||
*/
|
||||
rev() {
|
||||
const pat = this;
|
||||
@ -1126,7 +1128,7 @@ export class Pattern {
|
||||
* @example
|
||||
* s("hh*2").stack(
|
||||
* n("c2(3,8)")
|
||||
* ).out()
|
||||
* )
|
||||
*/
|
||||
stack(...pats) {
|
||||
return stack(this, ...pats);
|
||||
@ -1143,7 +1145,7 @@ export class Pattern {
|
||||
* @example
|
||||
* s("hh*2").seq(
|
||||
* n("c2(3,8)")
|
||||
* ).out()
|
||||
* )
|
||||
*/
|
||||
seq(...pats) {
|
||||
return sequence(this, ...pats);
|
||||
@ -1156,7 +1158,7 @@ export class Pattern {
|
||||
* @example
|
||||
* s("hh*2").cat(
|
||||
* n("c2(3,8)")
|
||||
* ).out()
|
||||
* )
|
||||
*/
|
||||
cat(...pats) {
|
||||
return cat(this, ...pats);
|
||||
@ -1178,7 +1180,7 @@ export class Pattern {
|
||||
* @example
|
||||
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
|
||||
* .superimpose(x=>x.add(2))
|
||||
* .scale('C minor').note().out()
|
||||
* .scale('C minor').note()
|
||||
*/
|
||||
superimpose(...funcs) {
|
||||
return this.stack(...funcs.map((func) => func(this)));
|
||||
@ -1203,7 +1205,7 @@ export class Pattern {
|
||||
* @example
|
||||
* "<0 [2 4]>"
|
||||
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
|
||||
* .scale('C minor').note().legato(.2).out()
|
||||
* .scale('C minor').note().legato(.2)
|
||||
*/
|
||||
_echoWith(times, time, func) {
|
||||
return stack(...listRange(0, times - 1).map((i) => func(this.late(Fraction(time).mul(i)), i)));
|
||||
@ -1218,7 +1220,7 @@ export class Pattern {
|
||||
* @param {number} time cycle offset between iterations
|
||||
* @param {number} feedback velocity multiplicator for each iteration
|
||||
* @example
|
||||
* s("bd sd").echo(3, 1/6, .8).out()
|
||||
* s("bd sd").echo(3, 1/6, .8)
|
||||
*/
|
||||
_echo(times, time, feedback) {
|
||||
return this._echoWith(times, time, (pat, i) => pat.velocity(Math.pow(feedback, i)));
|
||||
@ -1230,7 +1232,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("0 1 2 3".scale('A minor')).iter(4).out()
|
||||
* note("0 1 2 3".scale('A minor')).iter(4)
|
||||
*/
|
||||
iter(times, back = false) {
|
||||
return slowcat(...listRange(0, times - 1).map((i) => (back ? this.late(i / times) : this.early(i / times))));
|
||||
@ -1242,7 +1244,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* note("0 1 2 3".scale('A minor')).iterBack(4).out()
|
||||
* note("0 1 2 3".scale('A minor')).iterBack(4)
|
||||
*/
|
||||
iterBack(times) {
|
||||
return this.iter(times, true);
|
||||
@ -1254,7 +1256,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note().out()
|
||||
* "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note()
|
||||
*/
|
||||
_chunk(n, func, back = false) {
|
||||
const binary = Array(n - 1).fill(false);
|
||||
@ -1269,7 +1271,7 @@ export class Pattern {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note().out()
|
||||
* "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note()
|
||||
*/
|
||||
_chunkBack(n, func) {
|
||||
return this._chunk(n, func, true);
|
||||
@ -1295,7 +1297,7 @@ export class Pattern {
|
||||
* @name legato
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* n("c3 eb3 g3 c4").legato("<.25 .5 1 2>").out()
|
||||
* note("c3 eb3 g3 c4").legato("<.25 .5 1 2>")
|
||||
*/
|
||||
_legato(value) {
|
||||
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||
@ -1308,7 +1310,7 @@ export class Pattern {
|
||||
* @example
|
||||
* s("hh*8")
|
||||
* .gain(".4!2 1 .4!2 1 .4 1")
|
||||
* .velocity(".4 1").out()
|
||||
* .velocity(".4 1")
|
||||
*/
|
||||
_velocity(velocity) {
|
||||
return this._withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
|
||||
@ -1321,7 +1323,7 @@ export class Pattern {
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
|
||||
* s("rhodes").loopAt(4,1).out()
|
||||
* s("rhodes").loopAt(4,1)
|
||||
*/
|
||||
_loopAt(factor, cps = 1) {
|
||||
return this.speed((1 / factor) * cps)
|
||||
@ -1385,14 +1387,14 @@ function _composeOp(a, b, func) {
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* // Here, the triad 0, 2, 4 is shifted by different amounts
|
||||
* "0 2 4".add("<0 3 4 0>").scale('C major')
|
||||
* "0 2 4".add("<0 3 4 0>").scale('C major').note()
|
||||
* // Without add, the equivalent would be:
|
||||
* // "<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>".scale('C major')
|
||||
* // "<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>".scale('C major').note()
|
||||
* @example
|
||||
* // You can also use add with notes:
|
||||
* "c3 e3 g3".add("<0 5 7 0>")
|
||||
* "c3 e3 g3".add("<0 5 7 0>").note()
|
||||
* // Behind the scenes, the notes are converted to midi numbers:
|
||||
* // "48 52 55".add("<0 5 7 0>")
|
||||
* // "48 52 55".add("<0 5 7 0>").note()
|
||||
*/
|
||||
add: [(a, b) => a + b, numOrString], // support string concatenation
|
||||
/**
|
||||
@ -1401,7 +1403,7 @@ function _composeOp(a, b, func) {
|
||||
* @name sub
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor')
|
||||
* "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
|
||||
* // See add for more information.
|
||||
*/
|
||||
sub: [(a, b) => a - b, num],
|
||||
@ -1411,7 +1413,7 @@ function _composeOp(a, b, func) {
|
||||
* @name mul
|
||||
* @memberof Pattern
|
||||
* @example
|
||||
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq().out()
|
||||
* "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
|
||||
*/
|
||||
mul: [(a, b) => a * b, num],
|
||||
/**
|
||||
@ -1577,7 +1579,7 @@ export function reify(thing) {
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* stack(g3, b3, [e4, d4]) // "g3,b3,[e4,d4]"
|
||||
* stack(g3, b3, [e4, d4]).note() // "g3,b3,[e4,d4]".note()
|
||||
*/
|
||||
export function stack(...pats) {
|
||||
// Array test here is to avoid infinite recursions..
|
||||
@ -1650,7 +1652,7 @@ export function fastcat(...pats) {
|
||||
* @param {...any} items - The items to concatenate
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* cat(e5, b4, [d5, c5]) // "<e5 b4 [d5 c5]>"
|
||||
* cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
|
||||
*
|
||||
*/
|
||||
export function cat(...pats) {
|
||||
@ -1660,7 +1662,7 @@ export function cat(...pats) {
|
||||
/** Like {@link seq}, but each step has a length, relative to the whole.
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* timeCat([3,e3],[1, g3]) // "e3@3 g3"
|
||||
* timeCat([3,e3],[1, g3]).note() // "e3@3 g3".note()
|
||||
*/
|
||||
export function timeCat(...timepats) {
|
||||
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
||||
@ -1681,7 +1683,7 @@ export function sequence(...pats) {
|
||||
|
||||
/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence
|
||||
* @example
|
||||
* seq(e5, b4, [d5, c5]) // "e5 b4 [d5 c5]"
|
||||
* seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
|
||||
*
|
||||
*/
|
||||
export function seq(...pats) {
|
||||
|
||||
@ -27,9 +27,9 @@ export const isaw2 = isaw._toBipolar();
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4))
|
||||
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4)).note()
|
||||
* @example
|
||||
* saw.range(0,8).segment(8).scale('C major').slow(4)
|
||||
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
|
||||
*
|
||||
*/
|
||||
export const saw = signal((t) => t % 1);
|
||||
@ -42,7 +42,7 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* sine.segment(16).range(0,15).slow(2).scale('C minor')
|
||||
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const sine = sine2._fromBipolar();
|
||||
@ -52,7 +52,7 @@ export const sine = sine2._fromBipolar();
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor')
|
||||
* stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const cosine = sine._early(Fraction(1).div(4));
|
||||
@ -63,7 +63,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* square.segment(2).range(0,7).scale('C minor')
|
||||
* square.segment(2).range(0,7).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
@ -74,7 +74,7 @@ export const square2 = square._toBipolar();
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* tri.segment(8).range(0,7).scale('C minor')
|
||||
* tri.segment(8).range(0,7).scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const tri = fastcat(isaw, saw);
|
||||
@ -120,7 +120,7 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||
* @name rand
|
||||
* @example
|
||||
* // randomly change the cutoff
|
||||
* s("bd sd,hh*4").cutoff(rand.range(500,2000)).out()
|
||||
* s("bd sd,hh*4").cutoff(rand.range(500,2000))
|
||||
*
|
||||
*/
|
||||
export const rand = signal(timeToRand);
|
||||
@ -142,7 +142,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
|
||||
* @param {number} n max value (exclusive)
|
||||
* @example
|
||||
* // randomly select scale notes from 0 - 7 (= C to C)
|
||||
* irand(8).struct("x(3,8)").scale('C minor').note().out()
|
||||
* irand(8).struct("x(3,8)").scale('C minor').note()
|
||||
*
|
||||
*/
|
||||
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
|
||||
@ -208,9 +208,9 @@ Pattern.prototype.choose2 = function (...xs) {
|
||||
* Picks one of the elements at random each cycle.
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* chooseCycles("bd", "hh", "sd").s().fast(4).out()
|
||||
* chooseCycles("bd", "hh", "sd").s().fast(4)
|
||||
* @example
|
||||
* "bd | hh | sd".s().fast(4).out()
|
||||
* "bd | hh | sd".s().fast(4)
|
||||
*/
|
||||
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
|
||||
@ -252,7 +252,7 @@ export const perlinWith = (pat) => {
|
||||
* @name perlin
|
||||
* @example
|
||||
* // randomly change the cutoff
|
||||
* s("bd sd,hh*4").cutoff(perlin.range(500,2000)).out()
|
||||
* s("bd sd,hh*4").cutoff(perlin.range(500,2000))
|
||||
*
|
||||
*/
|
||||
export const perlin = perlinWith(time);
|
||||
@ -271,9 +271,9 @@ Pattern.prototype._degradeByWith = function (withPat, x) {
|
||||
* @param {number} amount - a number between 0 and 1
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").degradeBy(0.2).out()
|
||||
* s("hh*8").degradeBy(0.2)
|
||||
* @example
|
||||
* s("[hh?0.2]*8").out()
|
||||
* s("[hh?0.2]*8")
|
||||
*/
|
||||
Pattern.prototype._degradeBy = function (x) {
|
||||
return this._degradeByWith(rand, x);
|
||||
@ -287,9 +287,9 @@ Pattern.prototype._degradeBy = function (x) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").degrade().out()
|
||||
* s("hh*8").degrade()
|
||||
* @example
|
||||
* s("[hh?]*8").out()
|
||||
* s("[hh?]*8")
|
||||
*/
|
||||
Pattern.prototype.degrade = function () {
|
||||
return this._degradeBy(0.5);
|
||||
@ -306,7 +306,7 @@ Pattern.prototype.degrade = function () {
|
||||
* @param {number} amount - a number between 0 and 1
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").undegradeBy(0.2).out()
|
||||
* s("hh*8").undegradeBy(0.2)
|
||||
*/
|
||||
Pattern.prototype._undegradeBy = function (x) {
|
||||
return this._degradeByWith(
|
||||
@ -340,7 +340,7 @@ Pattern.prototype._sometimesBy = function (x, func) {
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5")).out()
|
||||
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.sometimesBy = function (patx, func) {
|
||||
const pat = this;
|
||||
@ -370,7 +370,7 @@ Pattern.prototype.sometimesByPre = function (patx, func) {
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*4").sometimes(x=>x.speed("0.5")).out()
|
||||
* s("hh*4").sometimes(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.sometimes = function (func) {
|
||||
return this._sometimesBy(0.5, func);
|
||||
@ -398,7 +398,7 @@ Pattern.prototype._someCyclesBy = function (x, func) {
|
||||
* @param {function} function - the transformation to apply
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5")).out()
|
||||
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.someCyclesBy = function (patx, func) {
|
||||
const pat = this;
|
||||
@ -415,7 +415,7 @@ Pattern.prototype.someCyclesBy = function (patx, func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh(3,8)").someCycles(x=>x.speed("0.5")).out()
|
||||
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.someCycles = function (func) {
|
||||
return this._someCyclesBy(0.5, func);
|
||||
@ -429,7 +429,7 @@ Pattern.prototype.someCycles = function (func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").often(x=>x.speed("0.5")).out()
|
||||
* s("hh*8").often(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.often = function (func) {
|
||||
return this.sometimesBy(0.75, func);
|
||||
@ -443,7 +443,7 @@ Pattern.prototype.often = function (func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").rarely(x=>x.speed("0.5")).out()
|
||||
* s("hh*8").rarely(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.rarely = function (func) {
|
||||
return this.sometimesBy(0.25, func);
|
||||
@ -457,7 +457,7 @@ Pattern.prototype.rarely = function (func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").almostNever(x=>x.speed("0.5")).out()
|
||||
* s("hh*8").almostNever(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.almostNever = function (func) {
|
||||
return this.sometimesBy(0.1, func);
|
||||
@ -471,7 +471,7 @@ Pattern.prototype.almostNever = function (func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").almostAlways(x=>x.speed("0.5")).out()
|
||||
* s("hh*8").almostAlways(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.almostAlways = function (func) {
|
||||
return this.sometimesBy(0.9, func);
|
||||
@ -485,7 +485,7 @@ Pattern.prototype.almostAlways = function (func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").never(x=>x.speed("0.5")).out()
|
||||
* s("hh*8").never(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.never = function (func) {
|
||||
return this;
|
||||
@ -499,7 +499,7 @@ Pattern.prototype.never = function (func) {
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("hh*8").always(x=>x.speed("0.5")).out()
|
||||
* s("hh*8").always(x=>x.speed("0.5"))
|
||||
*/
|
||||
Pattern.prototype.always = function (func) {
|
||||
return func(this);
|
||||
|
||||
@ -18,7 +18,6 @@ Either install with `npm i @strudel.cycles/embed` or just use a cdn to import th
|
||||
.legato(sine.range(0.3, 2).slow(28))
|
||||
.wave("sawtooth square".fast(2))
|
||||
.filter('lowpass', cosine.range(500,4000).slow(16))
|
||||
.out()
|
||||
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})
|
||||
-->
|
||||
</strudel-repl>
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
.legato(sine.range(0.3, 2).slow(28))
|
||||
.wave("sawtooth square".fast(2))
|
||||
.filter('lowpass', cosine.range(500,4000).slow(16))
|
||||
.out()
|
||||
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})
|
||||
-->
|
||||
</strudel-repl>
|
||||
|
||||
4
packages/react/dist/index.cjs.js
vendored
4
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
24
packages/react/dist/index.es.js
vendored
24
packages/react/dist/index.es.js
vendored
@ -7,9 +7,9 @@ import { tags } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { useInView } from 'react-hook-inview';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
|
||||
import { Tone } from '@strudel.cycles/tone';
|
||||
import { TimeSpan, State } from '@strudel.cycles/core';
|
||||
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
|
||||
import { WebMidi, enableWebMidi } from '@strudel.cycles/midi';
|
||||
|
||||
var strudelTheme = createTheme({
|
||||
@ -251,7 +251,7 @@ let s4 = () => {
|
||||
};
|
||||
const generateHash = (code) => encodeURIComponent(btoa(code));
|
||||
|
||||
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawProp }) {
|
||||
function useRepl({ tune, autolink = true, onEvent, onDraw: onDrawProp }) {
|
||||
const id = useMemo(() => s4(), []);
|
||||
const [code, setCode] = useState(tune);
|
||||
const [activeCode, setActiveCode] = useState();
|
||||
@ -282,26 +282,15 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
if (event.context.logs?.length) {
|
||||
event.context.logs.forEach(pushLog);
|
||||
}
|
||||
const { onTrigger, velocity } = event.context;
|
||||
if (!onTrigger) {
|
||||
if (defaultSynth) {
|
||||
const note = getPlayableNoteValue(event);
|
||||
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
} else {
|
||||
throw new Error('no defaultSynth passed to useRepl.');
|
||||
}
|
||||
/* console.warn('no instrument chosen', event);
|
||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
||||
} else {
|
||||
onTrigger(time, event, currentTime, 1 /* cps */);
|
||||
}
|
||||
const { onTrigger = webaudioOutputTrigger } = event.context;
|
||||
onTrigger(time, event, currentTime, 1 /* cps */);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
err.message = 'unplayable event: ' + err?.message;
|
||||
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
|
||||
}
|
||||
},
|
||||
[onEvent, pushLog, defaultSynth],
|
||||
[onEvent, pushLog],
|
||||
),
|
||||
onQuery: useCallback(
|
||||
(state) => {
|
||||
@ -484,10 +473,9 @@ function Icon({ type }) {
|
||||
}[type]);
|
||||
}
|
||||
|
||||
function MiniRepl({ tune, defaultSynth, hideOutsideView = false, theme, init, onEvent, enableKeyboard }) {
|
||||
function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
|
||||
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } = useRepl({
|
||||
tune,
|
||||
defaultSynth,
|
||||
autolink: false,
|
||||
onEvent
|
||||
});
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import React from 'react';
|
||||
import { MiniRepl } from './components/MiniRepl';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import { Tone, getDefaultSynth } from '@strudel.cycles/tone';
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
|
||||
const defaultSynth = getDefaultSynth();
|
||||
|
||||
evalScope(
|
||||
Tone,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tone'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
@ -20,7 +16,7 @@ evalScope(
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<MiniRepl tune={`"c3"`} defaultSynth={defaultSynth} />
|
||||
<MiniRepl tune={`"c3"`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,11 +9,10 @@ import './style.css';
|
||||
import styles from './MiniRepl.module.css';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
export function MiniRepl({ tune, defaultSynth, hideOutsideView = false, theme, init, onEvent, enableKeyboard }) {
|
||||
export function MiniRepl({ tune, hideOutsideView = false, init, onEvent, enableKeyboard }) {
|
||||
const { code, setCode, pattern, activeCode, activateCode, evaluateOnly, error, cycle, dirty, togglePlay, stop } =
|
||||
useRepl({
|
||||
tune,
|
||||
defaultSynth,
|
||||
autolink: false,
|
||||
onEvent,
|
||||
});
|
||||
|
||||
@ -6,9 +6,9 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
|
||||
import useCycle from './useCycle.mjs';
|
||||
import usePostMessage from './usePostMessage.mjs';
|
||||
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
|
||||
|
||||
let s4 = () => {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
@ -17,7 +17,7 @@ let s4 = () => {
|
||||
};
|
||||
const generateHash = (code) => encodeURIComponent(btoa(code));
|
||||
|
||||
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawProp }) {
|
||||
function useRepl({ tune, autolink = true, onEvent, onDraw: onDrawProp }) {
|
||||
const id = useMemo(() => s4(), []);
|
||||
const [code, setCode] = useState(tune);
|
||||
const [activeCode, setActiveCode] = useState();
|
||||
@ -48,26 +48,15 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
if (event.context.logs?.length) {
|
||||
event.context.logs.forEach(pushLog);
|
||||
}
|
||||
const { onTrigger, velocity } = event.context;
|
||||
if (!onTrigger) {
|
||||
if (defaultSynth) {
|
||||
const note = getPlayableNoteValue(event);
|
||||
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
} else {
|
||||
throw new Error('no defaultSynth passed to useRepl.');
|
||||
}
|
||||
/* console.warn('no instrument chosen', event);
|
||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
||||
} else {
|
||||
onTrigger(time, event, currentTime, 1 /* cps */);
|
||||
}
|
||||
const { onTrigger = webaudioOutputTrigger } = event.context;
|
||||
onTrigger(time, event, currentTime, 1 /* cps */);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
err.message = 'unplayable event: ' + err?.message;
|
||||
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
|
||||
}
|
||||
},
|
||||
[onEvent, pushLog, defaultSynth],
|
||||
[onEvent, pushLog],
|
||||
),
|
||||
onQuery: useCallback(
|
||||
(state) => {
|
||||
|
||||
@ -69,9 +69,9 @@ function scaleOffset(scale, offset, note) {
|
||||
* @memberof Pattern
|
||||
* @name transpose
|
||||
* @example
|
||||
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).transpose(0)
|
||||
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()
|
||||
* @example
|
||||
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).transpose(0)
|
||||
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
||||
*/
|
||||
|
||||
Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||
@ -107,6 +107,7 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||
* "-8 [2,4,6]"
|
||||
* .scale('C4 bebop major')
|
||||
* .scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>")
|
||||
* .note()
|
||||
*/
|
||||
|
||||
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||
@ -134,9 +135,10 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||
* @name scale
|
||||
* @param {string} scale Name of scale
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* @example
|
||||
* "0 2 4 6 4 2"
|
||||
* .scale(seq('C2 major', 'C2 minor').slow(2))
|
||||
* .note()
|
||||
*/
|
||||
|
||||
Pattern.prototype._scale = function (scale /* : string */) {
|
||||
|
||||
@ -40,7 +40,7 @@ Pattern.prototype.fmapNested = function (func) {
|
||||
* @param {range} range note range for possible voicings (optional, defaults to `['F3', 'A4']`)
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")
|
||||
* stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()
|
||||
*/
|
||||
|
||||
Pattern.prototype.voicings = function (range) {
|
||||
|
||||
@ -15,13 +15,13 @@ npm i @strudel.cycles/webaudio --save
|
||||
import { Scheduler, getAudioContext } from '@strudel.cycles/webaudio';
|
||||
|
||||
const scheduler = new Scheduler({
|
||||
audioContext: getAudioContext(),
|
||||
interval: 0.1,
|
||||
onEvent: (e) => e.context?.createAudioNode?.(e),
|
||||
});
|
||||
const pattern = sequence([55, 99], 110).osc('sawtooth').out()
|
||||
audioContext: getAudioContext(),
|
||||
interval: 0.1,
|
||||
onEvent: (e) => e.context?.createAudioNode?.(e),
|
||||
});
|
||||
const pattern = sequence([55, 99], 110).osc('sawtooth');
|
||||
scheduler.setPattern(pattern);
|
||||
scheduler.start()
|
||||
scheduler.start();
|
||||
//scheduler.stop()
|
||||
```
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ export const loadGithubSamples = async (path, nameFn) => {
|
||||
* bd: '808bd/BD0000.WAV',
|
||||
* sd: '808sd/SD0010.WAV'
|
||||
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
||||
* s("[bd ~]*2, [~ hh]*2, ~ sd").out()
|
||||
* s("[bd ~]*2, [~ hh]*2, ~ sd")
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
import { fromMidi, toMidi } from '@strudel.cycles/core';
|
||||
import { fromMidi, isNote, toMidi } from '@strudel.cycles/core';
|
||||
import './feedbackdelay.mjs';
|
||||
import './reverb.mjs';
|
||||
import { loadBuffer, reverseBuffer } from './sampler.mjs';
|
||||
@ -115,7 +115,11 @@ const getSampleBufferSource = async (s, n, note, speed) => {
|
||||
}
|
||||
const bank = samples?.[s];
|
||||
if (!bank) {
|
||||
throw new Error(`sample not found: "${s}", try one of ${Object.keys(samples).join(', ')}`);
|
||||
throw new Error(
|
||||
`sample not found: "${s}", try one of ${Object.keys(samples)
|
||||
.map((s) => `"${s}"`)
|
||||
.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
if (typeof bank !== 'object') {
|
||||
throw new Error('wrong format for sample bank:', s);
|
||||
@ -234,6 +238,10 @@ function effectSend(input, effect, wet) {
|
||||
export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
try {
|
||||
const ac = getAudioContext();
|
||||
/* if (isNote(hap.value)) {
|
||||
// supports primitive hap values that look like notes
|
||||
hap.value = { note: hap.value };
|
||||
} */
|
||||
if (typeof hap.value !== 'object') {
|
||||
throw new Error(
|
||||
`hap.value ${hap.value} is not supported by webaudio output. Hint: append .note() or .s() to the end`,
|
||||
@ -249,7 +257,7 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
clip = 0, // if 1, samples will be cut off when the hap ends
|
||||
n = 0,
|
||||
note,
|
||||
gain = 1,
|
||||
gain = 0.8,
|
||||
cutoff,
|
||||
resonance = 1,
|
||||
hcutoff,
|
||||
@ -260,10 +268,6 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
crush,
|
||||
shape,
|
||||
pan,
|
||||
attack = 0.001,
|
||||
decay = 0.001,
|
||||
sustain = 1,
|
||||
release = 0.001,
|
||||
speed = 1, // sample playback speed
|
||||
begin = 0,
|
||||
end = 1,
|
||||
@ -291,6 +295,8 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
[note, n] = splitSN(note, n);
|
||||
}
|
||||
if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) {
|
||||
// destructure adsr here, because the default should be different for synths and samples
|
||||
const { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = hap.value;
|
||||
// with synths, n and note are the same thing
|
||||
n = note || n || 36;
|
||||
if (typeof n === 'string') {
|
||||
@ -310,6 +316,8 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
const adsr = getADSR(attack, decay, sustain, release, 1, t, t + hapDuration);
|
||||
chain.push(adsr);
|
||||
} else {
|
||||
// destructure adsr here, because the default should be different for synths and samples
|
||||
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = hap.value;
|
||||
// load sample
|
||||
if (speed === 0) {
|
||||
// no playback
|
||||
@ -422,7 +430,9 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps);
|
||||
|
||||
Pattern.prototype.out = function () {
|
||||
// TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ?
|
||||
return this.onTrigger((t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps));
|
||||
return this.onTrigger(webaudioOutputTrigger);
|
||||
};
|
||||
|
||||
@ -7,14 +7,13 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
import { evalScope, evaluate } from '@strudel.cycles/eval';
|
||||
import { CodeMirror, cx, flash, useHighlighting, useRepl, useWebMidi } from '@strudel.cycles/react';
|
||||
import { getDefaultSynth, cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
||||
import { cleanupDraw, cleanupUi, Tone } from '@strudel.cycles/tone';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import './App.css';
|
||||
import logo from './logo.svg';
|
||||
import * as tunes from './tunes.mjs';
|
||||
import { prebake } from './prebake.mjs';
|
||||
import * as WebDirt from 'WebDirt';
|
||||
import { loadWebDirt } from '@strudel.cycles/webdirt';
|
||||
import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { nanoid } from 'nanoid';
|
||||
@ -37,16 +36,10 @@ evalScope(
|
||||
import('@strudel.cycles/xen'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/osc'),
|
||||
import('@strudel.cycles/webdirt'),
|
||||
import('@strudel.cycles/serial'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
);
|
||||
|
||||
loadWebDirt({
|
||||
sampleMapUrl: 'EmuSP12.json',
|
||||
sampleFolder: 'EmuSP12',
|
||||
});
|
||||
|
||||
prebake();
|
||||
|
||||
async function initCode() {
|
||||
@ -87,7 +80,6 @@ function getRandomTune() {
|
||||
}
|
||||
|
||||
const randomTune = getRandomTune();
|
||||
const defaultSynth = getDefaultSynth();
|
||||
const isEmbedded = window.location !== window.parent.location;
|
||||
function App() {
|
||||
// const [editor, setEditor] = useState();
|
||||
@ -112,7 +104,6 @@ function App() {
|
||||
hideConsole,
|
||||
} = useRepl({
|
||||
tune: '// LOADING...',
|
||||
defaultSynth,
|
||||
});
|
||||
useEffect(() => {
|
||||
initCode().then((decoded) => setCode(decoded || randomTune));
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Pattern, toMidi } from '@strudel.cycles/core';
|
||||
import { samples } from '@strudel.cycles/webaudio';
|
||||
|
||||
export async function prebake(isMock = false) {
|
||||
export async function prebake({ isMock = false, baseDir = '.' } = {}) {
|
||||
samples(
|
||||
{
|
||||
piano: {
|
||||
@ -39,12 +39,12 @@ export async function prebake(isMock = false) {
|
||||
},
|
||||
// https://archive.org/details/SalamanderGrandPianoV3
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
'./piano/',
|
||||
`${baseDir}/piano/`,
|
||||
);
|
||||
if (!isMock) {
|
||||
await fetch('EmuSP12.json')
|
||||
.then((res) => res.json())
|
||||
.then((json) => samples(json, './EmuSP12/'));
|
||||
.then((json) => samples(json, `${baseDir}/EmuSP12/`));
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ const panwidth = (pan, width) => pan * width + (1 - width) / 2;
|
||||
Pattern.prototype.piano = function () {
|
||||
return this.clip(1)
|
||||
.s('piano')
|
||||
.release(.1)
|
||||
.release(0.1)
|
||||
.fmap((value) => {
|
||||
const midi = typeof value.note === 'string' ? toMidi(value.note) : value.note;
|
||||
// pan by pitch
|
||||
|
||||
@ -123,7 +123,7 @@ const uiHelpersMocked = {
|
||||
backgroundImage: id,
|
||||
};
|
||||
|
||||
prebake(true);
|
||||
prebake({ isMock: true });
|
||||
|
||||
// TODO: refactor to evalScope
|
||||
evalScope(
|
||||
|
||||
@ -6,11 +6,8 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { Tone } from '@strudel.cycles/tone';
|
||||
import { State, TimeSpan } from '@strudel.cycles/core';
|
||||
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { getDefaultSynth } from '@strudel.cycles/tone';
|
||||
|
||||
const defaultSynth = getDefaultSynth();
|
||||
import { webaudioOutputTrigger } from '@strudel.cycles/webaudio';
|
||||
|
||||
// this is a test to play back events with as less runtime code as possible..
|
||||
// the code asks for the number of seconds to prequery
|
||||
@ -47,17 +44,8 @@ async function playStatic(code) {
|
||||
events.forEach((event) => {
|
||||
Tone.getTransport().schedule((time) => {
|
||||
try {
|
||||
const { onTrigger, velocity } = event.context;
|
||||
if (!onTrigger) {
|
||||
if (defaultSynth) {
|
||||
const note = getPlayableNoteValue(event);
|
||||
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
|
||||
} else {
|
||||
throw new Error('no defaultSynth passed to useRepl.');
|
||||
}
|
||||
} else {
|
||||
onTrigger(time, event);
|
||||
}
|
||||
const { onTrigger = webaudioOutputTrigger } = event.context;
|
||||
onTrigger(time, event);
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
err.message = 'unplayable event: ' + err?.message;
|
||||
|
||||
@ -155,7 +155,7 @@ export const zeldasRescue = `stack(
|
||||
.gain(.1)
|
||||
.s('triangle')
|
||||
.room(1)
|
||||
.out()`;
|
||||
`;
|
||||
|
||||
export const caverave = `const keys = x => x.s('sawtooth').cutoff(1200).gain(.5).attack(0).decay(.16).sustain(.3).release(.1);
|
||||
|
||||
@ -186,7 +186,7 @@ const synths = stack(
|
||||
stack(
|
||||
drums.fast(2),
|
||||
synths
|
||||
).slow(2).out()`;
|
||||
).slow(2)`;
|
||||
|
||||
export const sampleDrums = `samples({
|
||||
bd: 'bd/BT0A0D0.wav',
|
||||
@ -198,7 +198,7 @@ stack(
|
||||
"<bd!3 bd(3,4,2)>",
|
||||
"hh*4",
|
||||
"~ <sn!3 sn(3,4,1)>"
|
||||
).s().out()
|
||||
).s()
|
||||
`;
|
||||
|
||||
// TODO:
|
||||
@ -290,7 +290,7 @@ export const barryHarris = `backgroundImage(
|
||||
.scale('C bebop major')
|
||||
.transpose("<0 1 2 1>/8")
|
||||
.slow(2)
|
||||
.note().piano().out()
|
||||
.note().piano()
|
||||
`;
|
||||
|
||||
export const blippyRhodes = `samples({
|
||||
@ -331,7 +331,7 @@ stack(
|
||||
.slow(2).superimpose(x=>x.add(.02))
|
||||
.note().gain(.3)
|
||||
.s('sawtooth').cutoff(600),
|
||||
).fast(3/2).out()`;
|
||||
).fast(3/2)`;
|
||||
|
||||
export const wavyKalimba = `samples({
|
||||
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
|
||||
@ -359,7 +359,7 @@ stack(
|
||||
.clip(1)
|
||||
.s('kalimba')
|
||||
.delay(.2)
|
||||
.out()`;
|
||||
`;
|
||||
|
||||
// TODO: rework tune to use freq
|
||||
/*
|
||||
@ -401,7 +401,7 @@ stack(
|
||||
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4"))
|
||||
).slow(2)
|
||||
.velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8))
|
||||
.note().piano().out()`;
|
||||
.note().piano()`;
|
||||
|
||||
// iter, echo, echoWith
|
||||
export const undergroundPlumber = `backgroundImage('https://images.nintendolife.com/news/2016/08/video_exploring_the_funky_inspiration_for_the_super_mario_bros_underground_theme/large.jpg',{ className:'darken' })
|
||||
@ -425,7 +425,7 @@ stack(
|
||||
.echoWith(4, 1/8, (x,n)=>x.transpose(n*12).velocity(Math.pow(.4,n)))
|
||||
.legato(.1)
|
||||
.layer(h).note()
|
||||
).out()
|
||||
)
|
||||
.fast(2/3)
|
||||
.pianoroll({})`;
|
||||
|
||||
@ -444,7 +444,7 @@ stack(
|
||||
).transpose(-1).note().piano(),
|
||||
s("mad").slow(2)
|
||||
).cpm(78).slow(4)
|
||||
.out()
|
||||
|
||||
.pianoroll()
|
||||
`;
|
||||
|
||||
@ -463,7 +463,7 @@ stack(
|
||||
.scale(scale)
|
||||
.scaleTranspose("<0>".slow(4))
|
||||
.transpose(5)
|
||||
.note().piano().out()
|
||||
.note().piano()
|
||||
.velocity(.8)
|
||||
.slow(2)
|
||||
.pianoroll({maxMidi:100,minMidi:20})`;
|
||||
@ -475,7 +475,7 @@ export const echoPiano = `"<0 2 [4 6](3,4,1) 3*2>"
|
||||
.off(1/2, x=>x.scaleTranspose(6).color('steelblue'))
|
||||
.legato(.5)
|
||||
.echo(4, 1/8, .5)
|
||||
.note().piano().out()
|
||||
.note().piano()
|
||||
.pianoroll()`;
|
||||
|
||||
export const sml1 = `
|
||||
@ -511,7 +511,7 @@ stack(
|
||||
f3!2 e3!2 ab3!2 ~!2
|
||||
>\`
|
||||
.legato(.5)
|
||||
).fast(2) // .note().piano().out()`;
|
||||
).fast(2) // .note().piano()`;
|
||||
|
||||
/*
|
||||
// TODO: does not work on linux (at least for me..)
|
||||
@ -554,7 +554,7 @@ stack(
|
||||
"<D2 A2 G2 F2>".euclidLegato(6,8,1).note().s('bass').clip(1).gain(.8)
|
||||
)
|
||||
.slow(6)
|
||||
.out()
|
||||
|
||||
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
|
||||
`;
|
||||
|
||||
@ -570,7 +570,7 @@ export const waa2 = `n(
|
||||
.cutoff(cosine.range(500,4000).slow(16))
|
||||
.gain(.5)
|
||||
.room(.5)
|
||||
.out()`;
|
||||
`;
|
||||
|
||||
export const hyperpop = `const lfo = cosine.slow(15);
|
||||
const lfo2 = sine.slow(16);
|
||||
@ -618,7 +618,7 @@ stack(
|
||||
"~ sn",
|
||||
"[~ hh3]*2"
|
||||
).s().fast(2).gain(.7)
|
||||
).slow(2).out()
|
||||
).slow(2)
|
||||
// strudel disable-highlighting`;
|
||||
|
||||
export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
|
||||
@ -634,7 +634,7 @@ export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
|
||||
.scale(cat('D dorian','G mixolydian','C dorian','F mixolydian'))
|
||||
.legato(1)
|
||||
.slow(2)
|
||||
.note().piano().out()
|
||||
.note().piano()
|
||||
//.pianoroll({maxMidi:160})`;
|
||||
|
||||
export const bossa = `
|
||||
@ -642,7 +642,7 @@ const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C
|
||||
stack(
|
||||
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
|
||||
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
|
||||
).note().piano().out().slow(2)`;
|
||||
).note().piano().slow(2)`;
|
||||
/*
|
||||
export const customTrigger = `stack(
|
||||
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
|
||||
@ -701,7 +701,7 @@ stack(
|
||||
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
|
||||
//.hush()
|
||||
)
|
||||
.out()
|
||||
|
||||
.slow(3/2)`;
|
||||
|
||||
export const swimmingWithSoundfonts = `stack(
|
||||
@ -763,7 +763,7 @@ export const swimmingWithSoundfonts = `stack(
|
||||
"[G2 C2 F2 F2]"
|
||||
).s('Acoustic Bass: Bass')
|
||||
).slow(51)
|
||||
.out()`;
|
||||
`;
|
||||
|
||||
export const outroMusic = `samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||
@ -790,7 +790,7 @@ export const outroMusic = `samples({
|
||||
.n(3).color('gray')
|
||||
).slow(3/2)
|
||||
//.pianoroll({autorange:1,vertical:1,fold:0})
|
||||
.out()`;
|
||||
`;
|
||||
|
||||
export const bassFuge = `samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
|
||||
'github:cleary/samples-flbass/main/')
|
||||
@ -814,7 +814,7 @@ x=>x.add(7).color('steelblue')
|
||||
//.hcutoff(400)
|
||||
.clip(1)
|
||||
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
|
||||
.out()
|
||||
|
||||
.pianoroll({vertical:1})`;
|
||||
|
||||
export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
|
||||
@ -826,7 +826,7 @@ stack(
|
||||
x? ~ ~ x@3 ~ x |
|
||||
x? ~ ~ x ~ x@3\`),
|
||||
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
|
||||
).slow(2).pianoroll().note().piano().out()`;
|
||||
).slow(2).pianoroll().note().piano()`;
|
||||
|
||||
export const chop = `samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
|
||||
|
||||
@ -837,14 +837,14 @@ s("p")
|
||||
.shape(.4)
|
||||
.decay(.1)
|
||||
.sustain(.6)
|
||||
.out()`;
|
||||
`;
|
||||
|
||||
export const delay = `stack(
|
||||
s("bd <sd cp>")
|
||||
.delay("<0 .5>")
|
||||
.delaytime(".16 | .33")
|
||||
.delayfeedback(".6 | .8")
|
||||
).sometimes(x=>x.speed("-1")).out()`;
|
||||
).sometimes(x=>x.speed("-1"))`;
|
||||
|
||||
export const orbit = `stack(
|
||||
s("bd <sd cp>")
|
||||
@ -856,4 +856,4 @@ export const orbit = `stack(
|
||||
.delaytime(.08)
|
||||
.delayfeedback(.7)
|
||||
.orbit(2)
|
||||
).sometimes(x=>x.speed("-1")).out()`;
|
||||
).sometimes(x=>x.speed("-1"))`;
|
||||
|
||||
@ -1,24 +1,14 @@
|
||||
import { Tone } from '@strudel.cycles/tone';
|
||||
import { evalScope } from '@strudel.cycles/eval';
|
||||
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
import { loadWebDirt } from '@strudel.cycles/webdirt';
|
||||
import { samples } from '@strudel.cycles/webaudio';
|
||||
|
||||
export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
|
||||
oscillator: { type: 'triangle' },
|
||||
envelope: {
|
||||
release: 0.01,
|
||||
},
|
||||
});
|
||||
import { prebake } from '../repl/src/prebake.mjs';
|
||||
|
||||
fetch('https://strudel.tidalcycles.org/EmuSP12.json')
|
||||
.then((res) => res.json())
|
||||
.then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/'));
|
||||
|
||||
|
||||
evalScope(
|
||||
Tone,
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tone'),
|
||||
@ -28,14 +18,10 @@ evalScope(
|
||||
import('@strudel.cycles/xen'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/osc'),
|
||||
import('@strudel.cycles/webdirt'),
|
||||
);
|
||||
|
||||
loadWebDirt({
|
||||
sampleMapUrl: '../EmuSP12.json',
|
||||
sampleFolder: '../EmuSP12',
|
||||
});
|
||||
prebake();
|
||||
|
||||
export function MiniRepl({ tune }) {
|
||||
return <_MiniRepl tune={tune} defaultSynth={defaultSynth} hideOutsideView={true} />;
|
||||
return <_MiniRepl tune={tune} hideOutsideView={true} />;
|
||||
}
|
||||
|
||||
152
tutorial/old.mdx
Normal file
152
tutorial/old.mdx
Normal file
@ -0,0 +1,152 @@
|
||||
# Old APIs
|
||||
|
||||
These APIs are outdated and might break in the future.
|
||||
|
||||
## Webdirt API (deprecated)
|
||||
|
||||
You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel.
|
||||
|
||||
{{ 'Pattern.webdirt' | jsdoc }}
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
## Tone API (deprecated)
|
||||
|
||||
The Tone API uses Tone.js instruments ands effects to create sounds.
|
||||
|
||||
<MiniRepl
|
||||
tune={`stack(
|
||||
"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~"
|
||||
.tone(synth(adsr(0,.1,0,0)).chain(out())),
|
||||
"[c2 c3]*8"
|
||||
.tone(synth({
|
||||
...osc('sawtooth'),
|
||||
...adsr(0,.1,0.4,0)
|
||||
}).chain(lowpass(300), out()))
|
||||
).slow(4)`}
|
||||
/>
|
||||
|
||||
### tone(instrument)
|
||||
|
||||
To change the instrument of a pattern, you can pass any [Tone.js Source](https://tonejs.github.io/docs/14.7.77/index.html) to .tone:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(new FMSynth().toDestination())`}
|
||||
/>
|
||||
|
||||
While this works, it is a little bit verbose. To simplify things, all Tone Synths have a shortcut:
|
||||
|
||||
```js
|
||||
const amsynth = (options) => new AMSynth(options);
|
||||
const duosynth = (options) => new DuoSynth(options);
|
||||
const fmsynth = (options) => new FMSynth(options);
|
||||
const membrane = (options) => new MembraneSynth(options);
|
||||
const metal = (options) => new MetalSynth(options);
|
||||
const monosynth = (options) => new MonoSynth(options);
|
||||
const noise = (options) => new NoiseSynth(options);
|
||||
const pluck = (options) => new PluckSynth(options);
|
||||
const polysynth = (options) => new PolySynth(options);
|
||||
const synth = (options) => new Synth(options);
|
||||
const sampler = (options, baseUrl?) => new Sampler(options); // promisified, see below
|
||||
const players = (options, baseUrl?) => new Sampler(options); // promisified, see below
|
||||
```
|
||||
|
||||
### sampler
|
||||
|
||||
With sampler, you can create tonal instruments from samples:
|
||||
|
||||
<MiniRepl
|
||||
tune={`sampler({
|
||||
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
|
||||
}).then(kalimba =>
|
||||
saw.struct("x*8").mul(16).round()
|
||||
.legato(4).scale('D dorian').slow(2)
|
||||
.tone(kalimba.toDestination())
|
||||
)`}
|
||||
/>
|
||||
|
||||
The sampler function promisifies [Tone.js Sampler](https://tonejs.github.io/docs/14.7.77/Sampler).
|
||||
|
||||
Note that this function currently only works with this promise notation, but in the future,
|
||||
it will be possible to use async instruments in a synchronous fashion.
|
||||
|
||||
### players
|
||||
|
||||
With players, you can create sound banks:
|
||||
|
||||
<MiniRepl
|
||||
tune={`players({
|
||||
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
||||
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
||||
hh: 'samples/tidal/hh/000_hh3closedhh.wav'
|
||||
}, 'https://loophole-letters.vercel.app/')
|
||||
.then(drums=>
|
||||
"bd hh sn hh".tone(drums.toDestination())
|
||||
)
|
||||
`}
|
||||
/>
|
||||
|
||||
The sampler function promisifies [Tone.js Players](https://tonejs.github.io/docs/14.7.77/Players).
|
||||
|
||||
Note that this function currently only works with this promise notation, but in the future,
|
||||
it will be possible to use async instruments in a synchronous fashion.
|
||||
|
||||
### out
|
||||
|
||||
Shortcut for Tone.Destination. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(membrane().chain(out()))`}
|
||||
/>
|
||||
|
||||
This alone is not really useful, so read on..
|
||||
|
||||
### vol(volume)
|
||||
|
||||
Helper that returns a Gain Node with the given volume. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(noise().chain(vol(0.5), out()))`}
|
||||
/>
|
||||
|
||||
### osc(type)
|
||||
|
||||
Helper to set the waveform of a synth, monosynth or polysynth:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(osc('sawtooth4')).chain(out()))`}
|
||||
/>
|
||||
|
||||
The base types are `sine`, `square`, `sawtooth`, `triangle`. You can also append a number between 1 and 32 to reduce the harmonic partials.
|
||||
|
||||
### lowpass(cutoff)
|
||||
|
||||
Helper that returns a Filter Node of type lowpass with the given cutoff. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(osc('sawtooth')).chain(lowpass(800), out()))`}
|
||||
/>
|
||||
|
||||
### highpass(cutoff)
|
||||
|
||||
Helper that returns a Filter Node of type highpass with the given cutoff. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(osc('sawtooth')).chain(highpass(2000), out()))`}
|
||||
/>
|
||||
|
||||
### adsr
|
||||
|
||||
Helper to set the envelope of a Tone.js instrument. Intended to be used with Tone's .set:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(adsr(0,.1,0,0)).chain(out()))`}
|
||||
/>
|
||||
@ -17,182 +17,6 @@ The best place to actually make music with Strudel is the [Strudel REPL](https:/
|
||||
|
||||
To get a taste of what Strudel can do, check out this track:
|
||||
|
||||
<MiniRepl
|
||||
tune={`const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());
|
||||
const kick = new MembraneSynth().chain(vol(.8), out());
|
||||
const snare = new NoiseSynth().chain(vol(.8), out());
|
||||
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out());
|
||||
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out());
|
||||
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out());
|
||||
const drums = stack(
|
||||
"c1*2".tone(kick).bypass("<0@7 1>/8"),
|
||||
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
|
||||
"[~ c4]*2".tone(hihat)
|
||||
);
|
||||
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
|
||||
const synths = stack(
|
||||
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).struct("[~ x]\*2")
|
||||
.layer(
|
||||
scaleTranspose(0).early(0),
|
||||
scaleTranspose(2).early(1/8),
|
||||
scaleTranspose(7).early(1/4),
|
||||
scaleTranspose(8).early(3/8)
|
||||
).layer(thru).tone(keys).bypass("<1 0>/16"),
|
||||
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).layer(thru).tone(bass),
|
||||
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().layer(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4))
|
||||
)
|
||||
stack(
|
||||
drums.fast(2),
|
||||
synths
|
||||
).slow(2);
|
||||
`}
|
||||
/>
|
||||
|
||||
[Open this track in the REPL](https://strudel.tidalcycles.org/#KCkgPT4gewogIGNvbnN0IGRlbGF5ID0gbmV3IEZlZWRiYWNrRGVsYXkoMS84LCAuNCkuY2hhaW4odm9sKDAuNSksIG91dCk7CiAgY29uc3Qga2ljayA9IG5ldyBNZW1icmFuZVN5bnRoKCkuY2hhaW4odm9sKC44KSwgb3V0KTsKICBjb25zdCBzbmFyZSA9IG5ldyBOb2lzZVN5bnRoKCkuY2hhaW4odm9sKC44KSwgb3V0KTsKICBjb25zdCBoaWhhdCA9IG5ldyBNZXRhbFN5bnRoKCkuc2V0KGFkc3IoMCwgLjA4LCAwLCAuMSkpLmNoYWluKHZvbCguMykuY29ubmVjdChkZWxheSksb3V0KTsKICBjb25zdCBiYXNzID0gbmV3IFN5bnRoKCkuc2V0KHsgLi4ub3NjKCdzYXd0b290aCcpLCAuLi5hZHNyKDAsIC4xLCAuNCkgfSkuY2hhaW4obG93cGFzcyg5MDApLCB2b2woLjUpLCBvdXQpOwogIGNvbnN0IGtleXMgPSBuZXcgUG9seVN5bnRoKCkuc2V0KHsgLi4ub3NjKCdzYXd0b290aCcpLCAuLi5hZHNyKDAsIC41LCAuMiwgLjcpIH0pLmNoYWluKGxvd3Bhc3MoMTIwMCksIHZvbCguNSksIG91dCk7CiAgCiAgY29uc3QgZHJ1bXMgPSBzdGFjaygKICAgICdjMSoyJy5tLnRvbmUoa2ljaykuYnlwYXNzKCc8MEA3IDE%2BLzgnLm0pLAogICAgJ34gPHghNyBbeEAzIHhdPicubS50b25lKHNuYXJlKS5ieXBhc3MoJzwwQDcgMT4vNCcubSksCiAgICAnW34gYzRdKjInLm0udG9uZShoaWhhdCkKICApOwogIAogIGNvbnN0IHRocnUgPSAoeCkgPT4geC50cmFuc3Bvc2UoJzwwIDE%2BLzgnLm0pLnRyYW5zcG9zZSgtMSk7CiAgY29uc3Qgc3ludGhzID0gc3RhY2soCiAgICAnPGViNCBkNCBjNCBiMz4vMicubS5zY2FsZSh0aW1lQ2F0KFszLCdDIG1pbm9yJ10sWzEsJ0MgbWVsb2RpYyBtaW5vciddKS5zbG93KDgpKS5ncm9vdmUoJ1t%2BIHhdKjInLm0pCiAgICAuZWRpdCgKICAgICAgc2NhbGVUcmFuc3Bvc2UoMCkuZWFybHkoMCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDIpLmVhcmx5KDEvOCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDcpLmVhcmx5KDEvNCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDgpLmVhcmx5KDMvOCkKICAgICkuZWRpdCh0aHJ1KS50b25lKGtleXMpLmJ5cGFzcygnPDEgMD4vMTYnLm0pLAogICAgJzxDMiBCYjEgQWIxIFtHMSBbRzIgRzFdXT4vMicubS5ncm9vdmUoJ1t4IFt%2BIHhdIDxbfiBbfiB4XV0hMyBbeCB4XT5AMl0vMicubS5mYXN0KDIpKS5lZGl0KHRocnUpLnRvbmUoYmFzcyksCiAgICAnPENtNyBCYjcgRm03IEc3YjEzPi8yJy5tLmdyb292ZSgnfiBbeEAwLjEgfl0nLm0uZmFzdCgyKSkudm9pY2luZ3MoKS5lZGl0KHRocnUpLmV2ZXJ5KDIsIGVhcmx5KDEvOCkpLnRvbmUoa2V5cykuYnlwYXNzKCc8MEA3IDE%2BLzgnLm0uZWFybHkoMS80KSkKICApCiAgcmV0dXJuIHN0YWNrKAogICAgZHJ1bXMuZmFzdCgyKSwgCiAgICBzeW50aHMKICApLnNsb3coMik7Cn0%3D)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
- This project is still in its experimental state. In the future, parts of it might change significantly.
|
||||
- This tutorial is far from complete.
|
||||
|
||||
<br />
|
||||
|
||||
# Mini Notation
|
||||
|
||||
Similar to Tidal Cycles, Strudel has an embedded mini language that is designed to write rhythmic patterns in a short manner.
|
||||
Before diving deeper into the details, here is a flavor of how the mini language looks like:
|
||||
|
||||
<MiniRepl
|
||||
tune={`\`[
|
||||
[
|
||||
[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
],[
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]
|
||||
]
|
||||
]/16\``}
|
||||
/>
|
||||
|
||||
The snippet above is enclosed in backticks (`), which allows you to write multi-line strings.
|
||||
You can also use double quotes (") for single line mini notation.
|
||||
|
||||
## Notes
|
||||
|
||||
Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
|
||||
|
||||
<MiniRepl tune={`"e5"`} />
|
||||
|
||||
Here, the same note is played over and over again, once a second. This one second is the default length of one so called "cycle".
|
||||
|
||||
By the way, you can edit the contents of the player, and press "update" to hear your change!
|
||||
You can also press "play" on the next player without needing to stop the last one.
|
||||
|
||||
## Sequences
|
||||
|
||||
We can play more notes by separating them with spaces:
|
||||
|
||||
<MiniRepl tune={`"e5 b4 d5 c5"`} />
|
||||
|
||||
Here, those four notes are squashed into one cycle, so each note is a quarter second long.
|
||||
|
||||
## Division
|
||||
|
||||
We can slow the sequence down by enclosing it in brackets and dividing it by a number:
|
||||
|
||||
<MiniRepl tune={`"[e5 b4 d5 c5]/2"`} />
|
||||
|
||||
The division by two means that the sequence will be played over the course of two cycles.
|
||||
You can also use decimal numbers for any tempo you like.
|
||||
|
||||
## Angle Brackets
|
||||
|
||||
Using angle brackets, we can define the sequence length based on the number of children:
|
||||
|
||||
<MiniRepl tune={`"<e5 b4 d5 c5>"`} />
|
||||
|
||||
The above snippet is the same as:
|
||||
|
||||
<MiniRepl tune={`"[e5 b4 d5 c5]/4"`} />
|
||||
|
||||
The advantage of the angle brackets, is that we can add more children without needing to change the number at the end.
|
||||
|
||||
## Multiplication
|
||||
|
||||
Contrary to division, a sequence can be sped up by multiplying it by a number:
|
||||
|
||||
<MiniRepl tune={`"[e5 b4 d5 c5]*2"`} />
|
||||
|
||||
The multiplication by 2 here means that the sequence will play twice a cycle.
|
||||
|
||||
## Bracket Nesting
|
||||
|
||||
To create more interesting rhythms, you can nest sequences with brackets, like this:
|
||||
|
||||
<MiniRepl tune={`"e5 [b4 c5] d5 [c5 b4]"`} />
|
||||
|
||||
## Rests
|
||||
|
||||
The "~" represents a rest:
|
||||
|
||||
<MiniRepl tune={`"[b4 [~ c5] d5 e5]"`} />
|
||||
|
||||
## Parallel
|
||||
|
||||
Using commas, we can play chords:
|
||||
|
||||
<MiniRepl tune={`"g3,b3,e4"`} />
|
||||
|
||||
To play multiple chords in a sequence, we have to wrap them in brackets:
|
||||
|
||||
<MiniRepl tune={`"<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>"`} />
|
||||
|
||||
## Elongation
|
||||
|
||||
With the "@" symbol, we can specify temporal "weight" of a sequence child:
|
||||
|
||||
<MiniRepl tune={`"<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>"`} />
|
||||
|
||||
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
|
||||
|
||||
## Replication
|
||||
|
||||
Using "!" we can repeat without speeding up:
|
||||
|
||||
<MiniRepl tune={`"<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>"`} />
|
||||
|
||||
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
||||
|
||||
## Euclidian
|
||||
|
||||
Using round brackets, we can create rhythmical sub-divisions based on three parameters: beats, segments and offset.
|
||||
The first parameter controls how may beats will be played.
|
||||
The second parameter controls the total amount of segments the beats will be distributed over.
|
||||
The third (optional) parameter controls the starting position for distributing the beats.
|
||||
One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,1)" or simply "(3,8)",
|
||||
resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1).
|
||||
|
||||
<MiniRepl tune={`"e5(2,8) b4(3,8) d5(2,8) c5(3,8)".slow(4)`} />
|
||||
|
||||
<br />
|
||||
|
||||
# Web Audio Output
|
||||
|
||||
The default way to output sound is by using the Web Audio Output.
|
||||
Here is a little beat to show some of the possibilities:
|
||||
|
||||
<MiniRepl
|
||||
tune={`samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||
@ -220,23 +44,239 @@ s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
||||
.cutoff(500) // fixed cutoff
|
||||
.attack(1) // slowly fade in
|
||||
)
|
||||
.slow(3/2)
|
||||
.out()`}
|
||||
.slow(3/2)`}
|
||||
/>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
- This project is still in its experimental state. In the future, parts of it might change significantly.
|
||||
- This tutorial is far from complete.
|
||||
|
||||
<br />
|
||||
|
||||
# Playing Pitches
|
||||
|
||||
Pitches are an essential building block for music. In Strudel, there are 3 different options to express a pitch:
|
||||
|
||||
- `note`: letter notation
|
||||
- `n`: number notation
|
||||
- `freq`: frequency notation
|
||||
|
||||
## note
|
||||
|
||||
Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
|
||||
|
||||
<MiniRepl tune={`note("a3 c#4 e4 a4")`} />
|
||||
|
||||
By the way, you can edit the contents of the player, and press "update" to hear your change!
|
||||
You can also press "play" on the next player without needing to stop the last one.
|
||||
|
||||
## n
|
||||
|
||||
If you don't like notes, you can also use numbers with `n` instead:
|
||||
|
||||
<MiniRepl tune={`n("57 61 64 69")`} />
|
||||
|
||||
These numbers are interpreted as so called midi numbers, where adjacent whole numbers are 1 semitone apart.
|
||||
You could also write decimal numbers to get microtonal pitches:
|
||||
|
||||
<MiniRepl tune={`n("74.5 75 75.5 76")`} />
|
||||
|
||||
## freq
|
||||
|
||||
To get maximum freedom, you can also use `freq` to directly control the frequency:
|
||||
|
||||
<MiniRepl tune={`freq("220 275 330 440")`} />
|
||||
|
||||
In this example, we play A3 (220Hz), C#4 natural (275Hz), E4 (330Hz) and A4 (440Hz).
|
||||
|
||||
<br />
|
||||
|
||||
# Playing Sounds
|
||||
|
||||
Instead of pitches, we can also play sounds with `s`:
|
||||
|
||||
<MiniRepl tune={`s("bd hh sd hh")`} />
|
||||
|
||||
Similarly, we can also use `s` to change the sound of our pitches:
|
||||
|
||||
<MiniRepl tune={`note("a3 c#4 e4 a4").s("sawtooth")`} />
|
||||
|
||||
Try changing the sound to `square`, `triangle` or `sine`!
|
||||
|
||||
We will go into the defails of sounds and synths [later](http://localhost:3000/tutorial/#web-audio-output).
|
||||
|
||||
<br />
|
||||
|
||||
# Syntax
|
||||
|
||||
So far, we've seen the following syntax:
|
||||
|
||||
```
|
||||
xxx("foo").yyy("bar")
|
||||
```
|
||||
|
||||
Generally, `xxx` and `yyy` are called functions, while `foo` and `bar` are called function arguments.
|
||||
So far, we've used the functions to declare which aspect of the sound we want to control, and their arguments for the actual data.
|
||||
The `yyy` function is called a chained function, because it is appended with a dot.
|
||||
|
||||
Strudel makes heavy use of chained functions. Here is a more extreme example:
|
||||
|
||||
<MiniRepl
|
||||
tune={`note("a3 c#4 e4 a4")
|
||||
.s("sawtooth")
|
||||
.cutoff(500)
|
||||
//.delay(0.5)
|
||||
.room(0.5)`}
|
||||
/>
|
||||
|
||||
The `//` is a line comment, resulting in the `delay` function being ignored.
|
||||
It is a handy way to quickly turn stuff on and off. Try uncommenting this line by deleting `//`!
|
||||
|
||||
The good news is, that this covers 99% of the JavaScript syntax needed for Strudel!
|
||||
|
||||
Let's now look at the way we can express rhythms..
|
||||
|
||||
<br />
|
||||
|
||||
# Mini Notation
|
||||
|
||||
Similar to Tidal Cycles, Strudel has an embedded mini language that is designed to write rhythmic patterns in a short manner.
|
||||
Before diving deeper into the details, here is a flavor of how the mini language looks like:
|
||||
|
||||
<MiniRepl
|
||||
tune={`note(\`[
|
||||
[
|
||||
[e5 [b4 c5] d5 [c5 b4]]
|
||||
[a4 [a4 c5] e5 [d5 c5]]
|
||||
[b4 [~ c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||
[e5 [~ c5] e5 [d5 c5]]
|
||||
[b4 [b4 c5] d5 e5]
|
||||
[c5 a4 a4 ~]
|
||||
],[
|
||||
[[e2 e3]*4]
|
||||
[[a2 a3]*4]
|
||||
[[g#2 g#3]*2 [e2 e3]*2]
|
||||
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||
[[d2 d3]*4]
|
||||
[[c2 c3]*4]
|
||||
[[b1 b2]*2 [e2 e3]*2]
|
||||
[[a1 a2]*4]
|
||||
]
|
||||
]/16\`)`}
|
||||
/>
|
||||
|
||||
The snippet above is enclosed in backticks (`), which allows you to write multi-line strings.
|
||||
You can also use double quotes (") for single line mini notation.
|
||||
|
||||
## Sequences
|
||||
|
||||
We can play more notes by separating them with spaces:
|
||||
|
||||
<MiniRepl tune={`note("e5 b4 d5 c5")`} />
|
||||
|
||||
Here, those four notes are squashed into one cycle, so each note is a quarter second long.
|
||||
Try adding or removing notes and notice how the tempo changes!
|
||||
|
||||
## Division
|
||||
|
||||
We can slow the sequence down by enclosing it in brackets and dividing it by a number:
|
||||
|
||||
<MiniRepl tune={`note("[e5 b4 d5 c5]/2")`} />
|
||||
|
||||
The division by two means that the sequence will be played over the course of two cycles.
|
||||
You can also use decimal numbers for any tempo you like.
|
||||
|
||||
## Angle Brackets
|
||||
|
||||
Using angle brackets, we can define the sequence length based on the number of children:
|
||||
|
||||
<MiniRepl tune={`note("<e5 b4 d5 c5>")`} />
|
||||
|
||||
The above snippet is the same as:
|
||||
|
||||
<MiniRepl tune={`note("[e5 b4 d5 c5]/4")`} />
|
||||
|
||||
The advantage of the angle brackets, is that we can add more children without needing to change the number at the end.
|
||||
|
||||
## Multiplication
|
||||
|
||||
Contrary to division, a sequence can be sped up by multiplying it by a number:
|
||||
|
||||
<MiniRepl tune={`note("[e5 b4 d5 c5]*2")`} />
|
||||
|
||||
The multiplication by 2 here means that the sequence will play twice a cycle.
|
||||
|
||||
## Bracket Nesting
|
||||
|
||||
To create more interesting rhythms, you can nest sequences with brackets, like this:
|
||||
|
||||
<MiniRepl tune={`note("e5 [b4 c5] d5 [c5 b4]")`} />
|
||||
|
||||
## Rests
|
||||
|
||||
The "~" represents a rest:
|
||||
|
||||
<MiniRepl tune={`note("[b4 [~ c5] d5 e5]")`} />
|
||||
|
||||
## Parallel
|
||||
|
||||
Using commas, we can play chords:
|
||||
|
||||
<MiniRepl tune={`note("g3,b3,e4")`} />
|
||||
|
||||
To play multiple chords in a sequence, we have to wrap them in brackets:
|
||||
|
||||
<MiniRepl tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} />
|
||||
|
||||
## Elongation
|
||||
|
||||
With the "@" symbol, we can specify temporal "weight" of a sequence child:
|
||||
|
||||
<MiniRepl tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
|
||||
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
|
||||
|
||||
## Replication
|
||||
|
||||
Using "!" we can repeat without speeding up:
|
||||
|
||||
<MiniRepl tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
||||
|
||||
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
||||
|
||||
## Euclidian
|
||||
|
||||
Using round brackets, we can create rhythmical sub-divisions based on three parameters: beats, segments and offset.
|
||||
The first parameter controls how may beats will be played.
|
||||
The second parameter controls the total amount of segments the beats will be distributed over.
|
||||
The third (optional) parameter controls the starting position for distributing the beats.
|
||||
One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,1)" or simply "(3,8)",
|
||||
resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1).
|
||||
|
||||
<MiniRepl tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(4)`} />
|
||||
|
||||
<br />
|
||||
|
||||
# Synths, Samples & Effects
|
||||
|
||||
Let's take a closer look at how we can control synths, sounds and effects.
|
||||
|
||||
## Synths
|
||||
|
||||
So far, all the mini notation examples all used the same sound, which is kind of boring.
|
||||
We can change the sound, using the `s` function:
|
||||
|
||||
<MiniRepl tune={`note("c2 <eb2 <g2 g1>>").s('sawtooth').out()`} />
|
||||
<MiniRepl tune={`note("c2 <eb2 <g2 g1>>").s('sawtooth')`} />
|
||||
|
||||
Here, we are wrapping our notes inside `note` and set the sound using `s`, connected by a dot.
|
||||
|
||||
Those functions are only 2 of many ways to alter the properties, or _params_ of a sound.
|
||||
The power of patterns allows us to sequence any _param_ independently:
|
||||
|
||||
<MiniRepl tune={`note("c2 <eb2 <g2 g1>>").s("<sawtooth square triangle>").out()`} />
|
||||
<MiniRepl tune={`note("c2 <eb2 <g2 g1>>").s("<sawtooth square triangle>")`} />
|
||||
|
||||
Now we not only pattern the notes, but the sound as well!
|
||||
`sawtooth` `square` and `triangle` are the basic waveforms available in `s`.
|
||||
@ -247,14 +287,14 @@ You can control the envelope of a synth using the `attack`, `decay`, `sustain` a
|
||||
|
||||
<MiniRepl
|
||||
tune={`note("c2 <eb2 <g2 g1>>").s('sawtooth')
|
||||
.attack(.1).decay(.1).sustain(.2).release(.1).out()`}
|
||||
.attack(.1).decay(.1).sustain(.2).release(.1)`}
|
||||
/>
|
||||
|
||||
## Samples
|
||||
|
||||
Besides Synths, `s` can also play back samples:
|
||||
|
||||
<MiniRepl tune={`s("bd sd,hh*8,misc/2").out()`} />
|
||||
<MiniRepl tune={`s("bd sd,hh*8,misc/2")`} />
|
||||
|
||||
To know which sounds are available, open the [default sample map](https://strudel.tidalcycles.org/EmuSP12.json)
|
||||
|
||||
@ -268,7 +308,7 @@ You can load your own sample map like this:
|
||||
sd: 'sd/rytm-01-classic.wav',
|
||||
hh: 'hh27/000_hh27closedhh.wav',
|
||||
}, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
||||
s("bd sd,hh*8").out()`}
|
||||
s("bd sd,hh*8")`}
|
||||
/>
|
||||
|
||||
The `samples` function takes an object that maps sound names to audio file paths.
|
||||
@ -282,7 +322,7 @@ Because github is a popular choice to dump samples, there is a shortcut for that
|
||||
sd: 'sd/rytm-01-classic.wav',
|
||||
hh: 'hh27/000_hh27closedhh.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
s("bd sd,hh*8").out()`}
|
||||
s("bd sd,hh*8")`}
|
||||
/>
|
||||
|
||||
The format is `github:user/repo/branch/`.
|
||||
@ -297,7 +337,7 @@ It is also possible, to declare multiple files for one sound, using the array no
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
s("<bd:0 bd:1>,~ <sd:0 sd:1>,[hh:0 hh:1]*2").out()`}
|
||||
s("<bd:0 bd:1>,~ <sd:0 sd:1>,[hh:0 hh:1]*2")`}
|
||||
/>
|
||||
|
||||
The `:0` `:1` etc. are the indices of the array.
|
||||
@ -309,7 +349,7 @@ The sample number can also be set using `n`:
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
s("bd,~ sd,hh*4").n("<0 1>").out()`}
|
||||
s("bd,~ sd,hh*4").n("<0 1>")`}
|
||||
/>
|
||||
|
||||
### Pitched Sounds
|
||||
@ -320,7 +360,7 @@ For pitched sounds, you can use `note`, just like with synths:
|
||||
tune={`samples({
|
||||
"gtr": 'gtr/0001_cleanC.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').gain(.5).out()`}
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').gain(.5)`}
|
||||
/>
|
||||
|
||||
Here, the guitar samples will overlap, because they always play till the end.
|
||||
@ -331,7 +371,7 @@ If we want them to behave more like a synth, we can add `clip(1)`:
|
||||
"gtr": 'gtr/0001_cleanC.wav',
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').clip(1)
|
||||
.gain(.5).out()`}
|
||||
.gain(.5)`}
|
||||
/>
|
||||
|
||||
### Base Pitch
|
||||
@ -344,7 +384,7 @@ If we have 2 samples with different base pitches, we can make them in tune by sp
|
||||
"moog": { 'g3': 'moog/005_Mighty%20Moog%20G3.wav' },
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s("gtr,moog").clip(1)
|
||||
.gain(.5).out()`}
|
||||
.gain(.5)`}
|
||||
/>
|
||||
|
||||
If a sample has no pitch set, `c3` is the default.
|
||||
@ -360,7 +400,7 @@ We can also declare different samples for different regions of the keyboard:
|
||||
}}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
note("g2!2 <bb2 c3>!2, <c4@3 [<eb4 bb3> g4 f4]>")
|
||||
.s('moog').clip(1)
|
||||
.gain(.5).out()`}
|
||||
.gain(.5)`}
|
||||
/>
|
||||
|
||||
The sampler will always pick the closest matching sample for the current note!
|
||||
@ -407,33 +447,71 @@ Wether you're using a synth or a sample, you can apply these effects:
|
||||
|
||||
<br />
|
||||
|
||||
# Core API
|
||||
# JavaScript API
|
||||
|
||||
While the mini notation is powerful on its own, there is much more to discover.
|
||||
Internally, the mini notation will expand to use the actual functional JavaScript API.
|
||||
|
||||
For example, this Pattern in Mini Notation:
|
||||
|
||||
<MiniRepl tune={`note("c3 eb3 g3")`} />
|
||||
|
||||
is equivalent to this Pattern without Mini Notation:
|
||||
|
||||
<MiniRepl tune={`note(seq(c3, eb3, g3))`} />
|
||||
|
||||
Similarly, there is an equivalent function for every aspect of the mini notation.
|
||||
|
||||
Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API
|
||||
to fit better for the larger context, while mini notation is more practical for individiual rhythms.
|
||||
|
||||
## Limits of Mini Notation
|
||||
|
||||
While the Mini Notation is a powerful way to write rhythms shortly, it also has its limits. Take this example:
|
||||
|
||||
<MiniRepl
|
||||
tune={`stack(
|
||||
note("c2 eb2(3,8)").s('sawtooth').cutoff(800),
|
||||
s("bd,~ sd,hh*4")
|
||||
)`}
|
||||
/>
|
||||
|
||||
Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them.
|
||||
While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds.
|
||||
|
||||
## Notes
|
||||
|
||||
Notes are automatically available as variables:
|
||||
|
||||
<MiniRepl tune={`seq(d4, fs4, a4)`} />
|
||||
<MiniRepl tune={`note(seq(d4, fs4, a4)) // note("d4 f#4 a4")`} />
|
||||
|
||||
An important difference to the mini notation:
|
||||
For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
|
||||
|
||||
The above is the same as:
|
||||
|
||||
<MiniRepl tune={`seq('d4', 'f#4', 'a4')`} />
|
||||
<MiniRepl tune={`note(seq('d4', 'f#4', 'a4'))`} />
|
||||
|
||||
Using strings, you can also use "#".
|
||||
|
||||
## Alternative Syntax
|
||||
|
||||
In the above example, we are nesting a function inside a function, which makes reading the parens a little more difficult.
|
||||
To avoid getting to many nested parens, there is an alternative syntax to add a type to a pattern:
|
||||
|
||||
<MiniRepl tune={`seq(d4, fs4, a4).note()`} />
|
||||
|
||||
You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty!
|
||||
|
||||
## Pattern Factories
|
||||
|
||||
The following functions will return a pattern.
|
||||
|
||||
<!--
|
||||
{{ 'pure' | jsdoc }}
|
||||
|
||||
Most of the time, you won't need that function as input values of pattern creating functions are purified by default.
|
||||
-->
|
||||
|
||||
{{ 'cat' | jsdoc }}
|
||||
|
||||
@ -477,7 +555,7 @@ You can freely mix JS patterns, mini patterns and values! For example, this patt
|
||||
stack(a3,c3,e4),
|
||||
stack(b3,d3,fs4),
|
||||
stack(b3,e4,g4)
|
||||
)`}
|
||||
).note()`}
|
||||
/>
|
||||
|
||||
...is equivalent to:
|
||||
@ -488,12 +566,12 @@ You can freely mix JS patterns, mini patterns and values! For example, this patt
|
||||
"a3,c3,e4",
|
||||
"b3,d3,f#4",
|
||||
"b3,e4,g4"
|
||||
)`}
|
||||
).note()`}
|
||||
/>
|
||||
|
||||
... as well as:
|
||||
|
||||
<MiniRepl tune={`"<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>"`} />
|
||||
<MiniRepl tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} />
|
||||
|
||||
While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @.
|
||||
When using JS patterns, there is a lot more you can do.
|
||||
@ -640,13 +718,13 @@ The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpe
|
||||
|
||||
Transposes all notes to the given number of semitones:
|
||||
|
||||
<MiniRepl tune={`"c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).transpose(0)`} />
|
||||
<MiniRepl tune={`"c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()`} />
|
||||
|
||||
This method gets really exciting when we use it with a pattern as above.
|
||||
|
||||
Instead of numbers, scientific interval notation can be used as well:
|
||||
|
||||
<MiniRepl tune={`"c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).transpose(1)`} />
|
||||
<MiniRepl tune={`"c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()`} />
|
||||
|
||||
### scale(name)
|
||||
|
||||
@ -654,7 +732,8 @@ Turns numbers into notes in the scale (zero indexed). Also sets scale for other
|
||||
|
||||
<MiniRepl
|
||||
tune={`"0 2 4 6 4 2"
|
||||
.scale(seq('C2 major', 'C2 minor').slow(2))`}
|
||||
.scale(seq('C2 major', 'C2 minor').slow(2))
|
||||
.note()`}
|
||||
/>
|
||||
|
||||
Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3.
|
||||
@ -668,14 +747,15 @@ Transposes notes inside the scale by the number of steps:
|
||||
<MiniRepl
|
||||
tune={`"-8 [2,4,6]"
|
||||
.scale('C4 bebop major')
|
||||
.scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>")`}
|
||||
.scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>")
|
||||
.note()`}
|
||||
/>
|
||||
|
||||
### voicings(range?)
|
||||
|
||||
Turns chord symbols into voicings, using the smoothest voice leading possible:
|
||||
|
||||
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")`} />
|
||||
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()`} />
|
||||
|
||||
<!-- TODO: use voicing collection as first param + patternify. -->
|
||||
|
||||
@ -683,7 +763,7 @@ Turns chord symbols into voicings, using the smoothest voice leading possible:
|
||||
|
||||
Turns chord symbols into root notes of chords in given octave.
|
||||
|
||||
<MiniRepl tune={`"<C^7 A7b13 Dm7 G7>".rootNotes(3)`} />
|
||||
<MiniRepl tune={`"<C^7 A7b13 Dm7 G7>".rootNotes(3).note()`} />
|
||||
|
||||
Together with layer, struct and voicings, this can be used to create a basic backing track:
|
||||
|
||||
@ -691,7 +771,7 @@ Together with layer, struct and voicings, this can be used to create a basic bac
|
||||
tune={`"<C^7 A7b13 Dm7 G7>".layer(
|
||||
x => x.voicings(['d3','g4']).struct("~ x"),
|
||||
x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out()))
|
||||
)`}
|
||||
).note()`}
|
||||
/>
|
||||
|
||||
<!-- TODO: use range instead of octave. -->
|
||||
@ -755,152 +835,3 @@ The following functions can be used with superdirt:
|
||||
`s n note freq channel orbit cutoff resonance hcutoff hresonance bandf bandq djf vowel cut begin end loop fadeTime speed unitA gain amp accelerate crush coarse delay lock leslie lrate lsize pan panspan pansplay room size dry shape squiz waveloss attack decay octave detune tremolodepth`
|
||||
|
||||
Please refer to [Tidal Docs](https://tidalcycles.org/) for more info.
|
||||
|
||||
# Webdirt API (deprecated)
|
||||
|
||||
You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel.
|
||||
|
||||
{{ 'Pattern.webdirt' | jsdoc }}
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
# Tone API (deprecated)
|
||||
|
||||
The Tone API uses Tone.js instruments ands effects to create sounds.
|
||||
|
||||
<MiniRepl
|
||||
tune={`stack(
|
||||
"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~"
|
||||
.tone(synth(adsr(0,.1,0,0)).chain(out())),
|
||||
"[c2 c3]*8"
|
||||
.tone(synth({
|
||||
...osc('sawtooth'),
|
||||
...adsr(0,.1,0.4,0)
|
||||
}).chain(lowpass(300), out()))
|
||||
).slow(4)`}
|
||||
/>
|
||||
|
||||
### tone(instrument)
|
||||
|
||||
To change the instrument of a pattern, you can pass any [Tone.js Source](https://tonejs.github.io/docs/14.7.77/index.html) to .tone:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(new FMSynth().toDestination())`}
|
||||
/>
|
||||
|
||||
While this works, it is a little bit verbose. To simplify things, all Tone Synths have a shortcut:
|
||||
|
||||
```js
|
||||
const amsynth = (options) => new AMSynth(options);
|
||||
const duosynth = (options) => new DuoSynth(options);
|
||||
const fmsynth = (options) => new FMSynth(options);
|
||||
const membrane = (options) => new MembraneSynth(options);
|
||||
const metal = (options) => new MetalSynth(options);
|
||||
const monosynth = (options) => new MonoSynth(options);
|
||||
const noise = (options) => new NoiseSynth(options);
|
||||
const pluck = (options) => new PluckSynth(options);
|
||||
const polysynth = (options) => new PolySynth(options);
|
||||
const synth = (options) => new Synth(options);
|
||||
const sampler = (options, baseUrl?) => new Sampler(options); // promisified, see below
|
||||
const players = (options, baseUrl?) => new Sampler(options); // promisified, see below
|
||||
```
|
||||
|
||||
### sampler
|
||||
|
||||
With sampler, you can create tonal instruments from samples:
|
||||
|
||||
<MiniRepl
|
||||
tune={`sampler({
|
||||
C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3'
|
||||
}).then(kalimba =>
|
||||
saw.struct("x*8").mul(16).round()
|
||||
.legato(4).scale('D dorian').slow(2)
|
||||
.tone(kalimba.toDestination())
|
||||
)`}
|
||||
/>
|
||||
|
||||
The sampler function promisifies [Tone.js Sampler](https://tonejs.github.io/docs/14.7.77/Sampler).
|
||||
|
||||
Note that this function currently only works with this promise notation, but in the future,
|
||||
it will be possible to use async instruments in a synchronous fashion.
|
||||
|
||||
### players
|
||||
|
||||
With players, you can create sound banks:
|
||||
|
||||
<MiniRepl
|
||||
tune={`players({
|
||||
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
||||
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
||||
hh: 'samples/tidal/hh/000_hh3closedhh.wav'
|
||||
}, 'https://loophole-letters.vercel.app/')
|
||||
.then(drums=>
|
||||
"bd hh sn hh".tone(drums.toDestination())
|
||||
)
|
||||
`}
|
||||
/>
|
||||
|
||||
The sampler function promisifies [Tone.js Players](https://tonejs.github.io/docs/14.7.77/Players).
|
||||
|
||||
Note that this function currently only works with this promise notation, but in the future,
|
||||
it will be possible to use async instruments in a synchronous fashion.
|
||||
|
||||
### out
|
||||
|
||||
Shortcut for Tone.Destination. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(membrane().chain(out()))`}
|
||||
/>
|
||||
|
||||
This alone is not really useful, so read on..
|
||||
|
||||
### vol(volume)
|
||||
|
||||
Helper that returns a Gain Node with the given volume. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(noise().chain(vol(0.5), out()))`}
|
||||
/>
|
||||
|
||||
### osc(type)
|
||||
|
||||
Helper to set the waveform of a synth, monosynth or polysynth:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(osc('sawtooth4')).chain(out()))`}
|
||||
/>
|
||||
|
||||
The base types are `sine`, `square`, `sawtooth`, `triangle`. You can also append a number between 1 and 32 to reduce the harmonic partials.
|
||||
|
||||
### lowpass(cutoff)
|
||||
|
||||
Helper that returns a Filter Node of type lowpass with the given cutoff. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(osc('sawtooth')).chain(lowpass(800), out()))`}
|
||||
/>
|
||||
|
||||
### highpass(cutoff)
|
||||
|
||||
Helper that returns a Filter Node of type highpass with the given cutoff. Intended to be used with Tone's .chain:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(osc('sawtooth')).chain(highpass(2000), out()))`}
|
||||
/>
|
||||
|
||||
### adsr
|
||||
|
||||
Helper to set the envelope of a Tone.js instrument. Intended to be used with Tone's .set:
|
||||
|
||||
<MiniRepl
|
||||
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
|
||||
.tone(synth(adsr(0,.1,0,0)).chain(out()))`}
|
||||
/>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user