Compare commits

...

10 Commits

Author SHA1 Message Date
Felix Roos cd1c4632d7 build to single file 2022-11-23 14:00:31 +01:00
Felix Roos f1a850e133 forgot tonal pkg import 2022-11-22 21:43:30 +01:00
Felix Roos 53b61b8d85 add workshop seed project 2022-11-22 21:33:47 +01:00
Alex McLean e1a532500e Tidying up core (#256)
* remove _ prefixes except for functions to be patternified
* categorise pattern methods
* experimental support for `.add.squeeze` and friends as alternative to `.addSqueeze`
* `every` is now an alias for `firstOf` with additional `lastOf` (which every will become an alias for next)
2022-11-22 08:51:25 +00:00
Felix Roos 4cf412b93d Merge pull request #266 from tidalcycles/faster-fast
fix performance bottleneck
2022-11-21 22:15:48 +01:00
Felix Roos 54c9c434e0 fix examples snapshot 2022-11-21 22:09:26 +01:00
Felix Roos de19f3e5fe fix tune snapshots 2022-11-21 22:08:17 +01:00
Felix Roos 8304993481 fix: #194 2022-11-21 21:59:08 +01:00
Felix Roos 4c838aeaca hotfix: weird audion bug 2022-11-17 11:13:49 +01:00
Felix Roos 547d925065 Merge pull request #263 from tidalcycles/fix-tutorial-bugs
fix tutorial bugs
2022-11-17 11:08:37 +01:00
26 changed files with 8750 additions and 2409 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ const fraction = (n) => {
-> those farey sequences turn out to make pattern querying ~20 times slower! always use strings!
-> still, some optimizations could be done: .mul .div .add .sub calls still use numbers
*/
n = String(n);
// n = String(n); // this is actually faster but imprecise...
}
return Fraction(n);
};
+632 -562
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -20,7 +20,7 @@ export const signal = (func) => {
};
export const isaw = signal((t) => 1 - (t % 1));
export const isaw2 = isaw._toBipolar();
export const isaw2 = isaw.toBipolar();
/**
* A sawtooth signal between 0 and 1.
@@ -33,7 +33,7 @@ export const isaw2 = isaw._toBipolar();
*
*/
export const saw = signal((t) => t % 1);
export const saw2 = saw._toBipolar();
export const saw2 = saw.toBipolar();
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
@@ -45,7 +45,7 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
*
*/
export const sine = sine2._fromBipolar();
export const sine = sine2.fromBipolar();
/**
* A cosine signal between 0 and 1.
@@ -67,7 +67,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
*
*/
export const square = signal((t) => Math.floor((t * 2) % 2));
export const square2 = square._toBipolar();
export const square2 = square.toBipolar();
/**
* A triangle signal between 0 and 1.
@@ -127,7 +127,7 @@ export const rand = signal(timeToRand);
/**
* A continuous pattern of random numbers, between -1 and 1
*/
export const rand2 = rand._toBipolar();
export const rand2 = rand.toBipolar();
export const _brandBy = (p) => rand.fmap((x) => x < p);
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
@@ -201,7 +201,7 @@ Pattern.prototype.choose = function (...xs) {
* @returns {Pattern}
*/
Pattern.prototype.choose2 = function (...xs) {
return chooseWith(this._fromBipolar(), xs);
return chooseWith(this.fromBipolar(), xs);
};
/**
@@ -259,7 +259,7 @@ export const perlinWith = (pat) => {
export const perlin = perlinWith(time.fmap((v) => Number(v)));
Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat._filterValues((v) => v > x));
return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x));
};
/**
+32 -25
View File
@@ -49,7 +49,7 @@ import { steady } from '../signal.mjs';
import controls from '../controls.mjs';
const { n } = controls;
const { n, s } = controls;
const st = (begin, end) => new State(ts(begin, end));
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
@@ -58,7 +58,7 @@ const third = Fraction(1, 3);
const twothirds = Fraction(2, 3);
const sameFirst = (a, b) => {
return expect(a._sortHapsByPart().firstCycle()).toStrictEqual(b._sortHapsByPart().firstCycle());
return expect(a.sortHapsByPart().firstCycle()).toStrictEqual(b.sortHapsByPart().firstCycle());
};
describe('TimeSpan', () => {
@@ -382,7 +382,7 @@ describe('Pattern', () => {
);
});
it('copes with breaking up events across cycles', () => {
expect(pure('a').slow(2)._fastGap(2)._setContext({}).query(st(0, 2))).toStrictEqual([
expect(pure('a').slow(2)._fastGap(2).setContext({}).query(st(0, 2))).toStrictEqual([
hap(ts(0, 1), ts(0, 0.5), 'a'),
hap(ts(0.5, 1.5), ts(1, 1.5), 'a'),
]);
@@ -446,8 +446,8 @@ describe('Pattern', () => {
});
describe('slow()', () => {
it('Supports zero-length queries', () => {
expect(steady('a').slow(1)._setContext({}).queryArc(0, 0)).toStrictEqual(
steady('a')._setContext({}).queryArc(0, 0),
expect(steady('a').slow(1).setContext({}).queryArc(0, 0)).toStrictEqual(
steady('a').setContext({}).queryArc(0, 0),
);
});
});
@@ -465,7 +465,7 @@ describe('Pattern', () => {
it('Filters true', () => {
expect(
pure(true)
._filterValues((x) => x)
.filterValues((x) => x)
.firstCycle().length,
).toBe(1);
});
@@ -490,7 +490,7 @@ describe('Pattern', () => {
pure(10)
.when(slowcat(true, false), (x) => x.add(3))
.fast(4)
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
).toStrictEqual(fastcat(13, 10, 13, 10).firstCycle());
});
@@ -577,26 +577,26 @@ describe('Pattern', () => {
});
});
describe('every()', () => {
describe('firstOf()', () => {
it('Can apply a function every 3rd time', () => {
expect(
pure('a')
.every(3, (x) => x._fast(2))
.firstOf(3, (x) => x._fast(2))
._fast(3)
.firstCycle(),
).toStrictEqual(sequence(sequence('a', 'a'), 'a', 'a').firstCycle());
});
it('works with currying', () => {
expect(pure('a').every(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
);
expect(sequence(3, 4, 5).every(3, add(3)).fast(5).firstCycle()).toStrictEqual(
expect(sequence(3, 4, 5).firstOf(3, add(3)).fast(5).firstCycle()).toStrictEqual(
sequence(6, 7, 8, 3, 4, 5, 3, 4, 5, 6, 7, 8, 3, 4, 5).firstCycle(),
);
expect(sequence(3, 4, 5).every(2, sub(1)).fast(5).firstCycle()).toStrictEqual(
expect(sequence(3, 4, 5).firstOf(2, sub(1)).fast(5).firstCycle()).toStrictEqual(
sequence(2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 2, 3, 4).firstCycle(),
);
expect(sequence(3, 4, 5).every(3, add(3)).every(2, sub(1)).fast(2).firstCycle()).toStrictEqual(
expect(sequence(3, 4, 5).firstOf(3, add(3)).firstOf(2, sub(1)).fast(2).firstCycle()).toStrictEqual(
sequence(5, 6, 7, 3, 4, 5).firstCycle(),
);
});
@@ -692,7 +692,7 @@ describe('Pattern', () => {
it('Can set the hap context', () => {
expect(
pure('a')
._setContext([
.setContext([
[
[0, 1],
[1, 2],
@@ -713,13 +713,13 @@ describe('Pattern', () => {
it('Can update the hap context', () => {
expect(
pure('a')
._setContext([
.setContext([
[
[0, 1],
[1, 2],
],
])
._withContext((c) => [
.withContext((c) => [
...c,
[
[3, 4],
@@ -743,7 +743,7 @@ describe('Pattern', () => {
});
describe('apply', () => {
it('Can apply a function', () => {
expect(sequence('a', 'b')._apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
}),
it('Can apply a pattern of functions', () => {
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
@@ -784,18 +784,18 @@ describe('Pattern', () => {
});
describe('jux', () => {
it('Can juxtapose', () => {
expect(pure({ a: 1 }).jux(fast(2))._sortHapsByPart().firstCycle()).toStrictEqual(
expect(pure({ a: 1 }).jux(fast(2)).sortHapsByPart().firstCycle()).toStrictEqual(
stack(pure({ a: 1, pan: 0 }), pure({ a: 1, pan: 1 }).fast(2))
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
);
});
});
describe('juxBy', () => {
it('Can juxtapose by half', () => {
expect(pure({ a: 1 }).juxBy(0.5, fast(2))._sortHapsByPart().firstCycle()).toStrictEqual(
expect(pure({ a: 1 }).juxBy(0.5, fast(2)).sortHapsByPart().firstCycle()).toStrictEqual(
stack(pure({ a: 1, pan: 0.25 }), pure({ a: 1, pan: 0.75 }).fast(2))
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
);
});
@@ -805,7 +805,7 @@ describe('Pattern', () => {
expect(
sequence('a', ['a', 'a'])
.fmap((a) => fastcat('b', 'c'))
._squeezeJoin()
.squeezeJoin()
.firstCycle(),
).toStrictEqual(
sequence(
@@ -820,7 +820,7 @@ describe('Pattern', () => {
it('Squeezes to the correct cycle', () => {
expect(
pure(time.struct(true))
._squeezeJoin()
.squeezeJoin()
.queryArc(3, 4)
.map((x) => x.value),
).toStrictEqual([Fraction(3.5)]);
@@ -857,7 +857,7 @@ describe('Pattern', () => {
);
});
it('Can chop(2,3)', () => {
expect(pure({ sound: 'a' }).fast(2).chop(2, 3)._sortHapsByPart().firstCycle()).toStrictEqual(
expect(pure({ sound: 'a' }).fast(2).chop(2, 3).sortHapsByPart().firstCycle()).toStrictEqual(
sequence(
[
{ sound: 'a', begin: 0, end: 0.5 },
@@ -869,7 +869,7 @@ describe('Pattern', () => {
{ sound: 'a', begin: 2 / 3, end: 1 },
],
)
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
);
});
@@ -893,4 +893,11 @@ describe('Pattern', () => {
);
});
});
describe('alignments', () => {
it('Can squeeze arguments', () => {
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(
sequence(5, 6, 6, 7).firstCycle()
);
});
});
});
+1 -1
View File
@@ -13,7 +13,7 @@ const { fastcat, evalScope } = strudel;
describe('evaluate', async () => {
await evalScope({ mini }, strudel);
const ev = async (code) => (await evaluate(code)).pattern._firstCycleValues;
const ev = async (code) => (await evaluate(code)).pattern.firstCycleValues;
it('Should evaluate strudel functions', async () => {
expect(await ev('pure("c3")')).toEqual(['c3']);
expect(await ev('cat("c3")')).toEqual(['c3']);
+2 -2
View File
@@ -9,8 +9,8 @@ import '@strudel.cycles/core/euclid.mjs';
import { describe, expect, it } from 'vitest';
describe('mini', () => {
const minV = (v) => mini(v)._firstCycleValues;
const minS = (v) => mini(v)._showFirstCycle;
const minV = (v) => mini(v).firstCycleValues;
const minS = (v) => mini(v).showFirstCycle;
it('supports single elements', () => {
expect(minV('a')).toEqual(['a']);
});
+1 -1
View File
@@ -12,6 +12,6 @@ import { describe, it, expect } from 'vitest';
describe('tonal', () => {
it('Should run tonal functions ', () => {
expect(pure('c3').scale('C major').scaleTranspose(1)._firstCycleValues).toEqual(['D3']);
expect(pure('c3').scale('C major').scaleTranspose(1).firstCycleValues).toEqual(['D3']);
});
});
+3 -3
View File
@@ -75,7 +75,7 @@ function scaleOffset(scale, offset, note) {
*/
Pattern.prototype._transpose = function (intervalOrSemitones) {
return this._withHap((hap) => {
return this.withHap((hap) => {
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones /* as number */)
: String(intervalOrSemitones);
@@ -111,7 +111,7 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
*/
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
return this._withHap((hap) => {
return this.withHap((hap) => {
if (!hap.context.scale) {
throw new Error('can only use scaleTranspose after .scale');
}
@@ -142,7 +142,7 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
*/
Pattern.prototype._scale = function (scale /* : string */) {
return this._withHap((hap) => {
return this.withHap((hap) => {
let note = hap.value;
const asNumber = Number(note);
if (!isNaN(asNumber)) {
+1 -1
View File
@@ -51,7 +51,7 @@ Pattern.prototype.voicings = function (range) {
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing)._withContext(() => ({
return stack(...lastVoicing).withContext(() => ({
locations: event.context.locations || [],
}));
});
+1 -1
View File
@@ -13,6 +13,6 @@ describe('tone', () => {
// const s = synth().chain(out()); // TODO: mock audio context?
// assert.deepStrictEqual(s, new Tone.Synth().chain(out()));
const s = {};
expect(pure('c3').tone(s)._firstCycleValues).toEqual(['c3']);
expect(pure('c3').tone(s).firstCycleValues).toEqual(['c3']);
});
});
+1 -1
View File
@@ -157,7 +157,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
if (!delays[orbit]) {
const ac = getAudioContext();
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
dly.start(t);
dly.start?.(t); // for some reason, this throws when audion extension is installed..
dly.connect(getDestination());
delays[orbit] = dly;
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,22 @@
// Vitest Snapshot v1
exports[`runs examples > example "_apply" example index 0 1`] = `
[
"0/1 -> 1/1: {\\"note\\":\\"C3\\"}",
"0/1 -> 1/1: {\\"note\\":\\"Eb3\\"}",
"0/1 -> 1/1: {\\"note\\":\\"G3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"Eb3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"G3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"Bb3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"G3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"Bb3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"D4\\"}",
"3/1 -> 4/1: {\\"note\\":\\"C3\\"}",
"3/1 -> 4/1: {\\"note\\":\\"Eb3\\"}",
"3/1 -> 4/1: {\\"note\\":\\"G3\\"}",
]
`;
exports[`runs examples > example "accelerate" example index 0 1`] = `
[
"0/1 -> 2/1: {\\"s\\":\\"sax\\",\\"accelerate\\":0}",
@@ -807,36 +824,36 @@ exports[`runs examples > example "echo" example index 0 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\"}",
"1/2 -> 1/1: {\\"s\\":\\"sd\\"}",
"-4166666666666667/12500000000000000 -> 8333333333333333/50000000000000000: {\\"s\\":\\"sd\\"}",
"8333333333333333/50000000000000000 -> 8333333333333333/12500000000000000: {\\"s\\":\\"bd\\"}",
"8333333333333333/12500000000000000 -> 7291666666666667/6250000000000000: {\\"s\\":\\"sd\\"}",
"-4166666666666667/25000000000000000 -> 8333333333333333/25000000000000000: {\\"s\\":\\"sd\\"}",
"8333333333333333/25000000000000000 -> 5208333333333333/6250000000000000: {\\"s\\":\\"bd\\"}",
"5208333333333333/6250000000000000 -> 8333333333333333/6250000000000000: {\\"s\\":\\"sd\\"}",
"-1/3 -> 1/6: {\\"s\\":\\"sd\\"}",
"1/6 -> 2/3: {\\"s\\":\\"bd\\"}",
"2/3 -> 7/6: {\\"s\\":\\"sd\\"}",
"-1/6 -> 1/3: {\\"s\\":\\"sd\\"}",
"1/3 -> 5/6: {\\"s\\":\\"bd\\"}",
"5/6 -> 4/3: {\\"s\\":\\"sd\\"}",
"1/1 -> 3/2: {\\"s\\":\\"bd\\"}",
"3/2 -> 2/1: {\\"s\\":\\"sd\\"}",
"8333333333333333/12500000000000000 -> 7291666666666667/6250000000000000: {\\"s\\":\\"sd\\"}",
"7291666666666667/6250000000000000 -> 5208333333333333/3125000000000000: {\\"s\\":\\"bd\\"}",
"5208333333333333/3125000000000000 -> 6770833333333333/3125000000000000: {\\"s\\":\\"sd\\"}",
"5208333333333333/6250000000000000 -> 8333333333333333/6250000000000000: {\\"s\\":\\"sd\\"}",
"8333333333333333/6250000000000000 -> 5729166666666667/3125000000000000: {\\"s\\":\\"bd\\"}",
"5729166666666667/3125000000000000 -> 7291666666666667/3125000000000000: {\\"s\\":\\"sd\\"}",
"2/3 -> 7/6: {\\"s\\":\\"sd\\"}",
"7/6 -> 5/3: {\\"s\\":\\"bd\\"}",
"5/3 -> 13/6: {\\"s\\":\\"sd\\"}",
"5/6 -> 4/3: {\\"s\\":\\"sd\\"}",
"4/3 -> 11/6: {\\"s\\":\\"bd\\"}",
"11/6 -> 7/3: {\\"s\\":\\"sd\\"}",
"2/1 -> 5/2: {\\"s\\":\\"bd\\"}",
"5/2 -> 3/1: {\\"s\\":\\"sd\\"}",
"5208333333333333/3125000000000000 -> 6770833333333333/3125000000000000: {\\"s\\":\\"sd\\"}",
"6770833333333333/3125000000000000 -> 8333333333333333/3125000000000000: {\\"s\\":\\"bd\\"}",
"8333333333333333/3125000000000000 -> 4947916666666667/1562500000000000: {\\"s\\":\\"sd\\"}",
"5729166666666667/3125000000000000 -> 7291666666666667/3125000000000000: {\\"s\\":\\"sd\\"}",
"7291666666666667/3125000000000000 -> 8854166666666667/3125000000000000: {\\"s\\":\\"bd\\"}",
"8854166666666667/3125000000000000 -> 5208333333333333/1562500000000000: {\\"s\\":\\"sd\\"}",
"5/3 -> 13/6: {\\"s\\":\\"sd\\"}",
"13/6 -> 8/3: {\\"s\\":\\"bd\\"}",
"8/3 -> 19/6: {\\"s\\":\\"sd\\"}",
"11/6 -> 7/3: {\\"s\\":\\"sd\\"}",
"7/3 -> 17/6: {\\"s\\":\\"bd\\"}",
"17/6 -> 10/3: {\\"s\\":\\"sd\\"}",
"3/1 -> 7/2: {\\"s\\":\\"bd\\"}",
"7/2 -> 4/1: {\\"s\\":\\"sd\\"}",
"8333333333333333/3125000000000000 -> 4947916666666667/1562500000000000: {\\"s\\":\\"sd\\"}",
"4947916666666667/1562500000000000 -> 5729166666666667/1562500000000000: {\\"s\\":\\"bd\\"}",
"5729166666666667/1562500000000000 -> 6510416666666667/1562500000000000: {\\"s\\":\\"sd\\"}",
"8854166666666667/3125000000000000 -> 5208333333333333/1562500000000000: {\\"s\\":\\"sd\\"}",
"5208333333333333/1562500000000000 -> 5989583333333333/1562500000000000: {\\"s\\":\\"bd\\"}",
"5989583333333333/1562500000000000 -> 6770833333333333/1562500000000000: {\\"s\\":\\"sd\\"}",
"8/3 -> 19/6: {\\"s\\":\\"sd\\"}",
"19/6 -> 11/3: {\\"s\\":\\"bd\\"}",
"11/3 -> 25/6: {\\"s\\":\\"sd\\"}",
"17/6 -> 10/3: {\\"s\\":\\"sd\\"}",
"10/3 -> 23/6: {\\"s\\":\\"bd\\"}",
"23/6 -> 13/3: {\\"s\\":\\"sd\\"}",
]
`;
@@ -1593,6 +1610,27 @@ exports[`runs examples > example "fastcat" example index 0 1`] = `
]
`;
exports[`runs examples > example "firstOf" example index 0 1`] = `
[
"3/4 -> 1/1: {\\"note\\":\\"c3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"d3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"e3\\"}",
"0/1 -> 1/4: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
"7/4 -> 2/1: {\\"note\\":\\"g3\\"}",
"2/1 -> 9/4: {\\"note\\":\\"c3\\"}",
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"c3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"d3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"e3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"g3\\"}",
]
`;
exports[`runs examples > example "freq" example index 0 1`] = `
[
"0/1 -> 1/4: {\\"freq\\":220,\\"s\\":\\"superzow\\"}",
@@ -1793,6 +1831,27 @@ exports[`runs examples > example "iterBack" example index 0 1`] = `
]
`;
exports[`runs examples > example "lastOf" example index 0 1`] = `
[
"0/1 -> 1/4: {\\"note\\":\\"c3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"d3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"e3\\"}",
"3/4 -> 1/1: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
"7/4 -> 2/1: {\\"note\\":\\"g3\\"}",
"2/1 -> 9/4: {\\"note\\":\\"c3\\"}",
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"c3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"d3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"e3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"g3\\"}",
]
`;
exports[`runs examples > example "late" example index 0 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\"}",
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+33
View File
@@ -0,0 +1,33 @@
# Strudel Workshop
This may become a resource to teach strudel in workshop format.
## Install
From the root of the strudel repo:
```sh
npm i # install strudel project dependencies
cd workshop && npm i # install workshop dependencies
npm run dev # run dev server
```
This assumes you have node 16+ installed.
## How to write
While running the dev server, go to `workshop.mdx` to write!
When the file is saved, the browser will hot reload the file and reveal the changes instantly.
You can use normal markdown + JSX, which enables using the MiniRepl component.
You could also add any react component and import it to the mdx file.
## Build
Running
```sh
npm run build
```
..will output a single `index.html` file to `dist`. This file can either be opened directly in the browser (locally) or uploaded to some server.
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Strudel Workshop</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+5982
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "workshop",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --open",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@mdx-js/mdx": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@tailwindcss/typography": "^0.5.8",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@vitejs/plugin-react": "^2.2.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"tailwindcss": "^3.2.4",
"vite": "^3.2.3",
"vite-plugin-mdx": "^3.5.11",
"vite-plugin-singlefile": "^0.13.1"
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+16
View File
@@ -0,0 +1,16 @@
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import Workshop from './workshop.mdx';
initAudioOnFirstClick();
function App() {
return (
<div className="bg-slate-900 w-screen h-screen overflow-auto">
<main className="p-4 pl-6 max-w-3xl prose prose-invert">
<Workshop />
</main>
</div>
);
}
export default App;
+22
View File
@@ -0,0 +1,22 @@
import { evalScope, controls } from '@strudel.cycles/core';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import { samples } from '@strudel.cycles/webaudio';
import '@strudel.cycles/react/dist/style.css';
samples('github:tidalcycles/Dirt-Samples/master')
evalScope(
controls,
import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
// import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
);
export function MiniRepl({ tune }) {
return <_MiniRepl tune={tune} hideOutsideView={true} />;
}
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+11
View File
@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);
+49
View File
@@ -0,0 +1,49 @@
import { MiniRepl } from './MiniRepl';
# Workshop
> This is a starter project for any type of teaching material using strudel!
## What is Strudel?
1. A pastry made with fruit or cheese rolled up in layers of thin sheets of dough and then baked.
2. a pastry made from multiple, thin layers of dough rolled up and filled with fruit, etc.
3. thin sheet of filled dough rolled and baked
## Some REPLs:
Small REPL:
<MiniRepl tune={`s("bd sd,hh*8")`} />
Big REPL:
<MiniRepl
tune={`await samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
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/');
stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation
,"<a1 b1\*2 a1(3,8) e2>" // bassline
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
.add(perlin.range(0,.5)) // random pitch variation
.superimpose(add(.05)) // add second, slightly detuned voice
.n() // wrap in "n"
.decay(.15).sustain(0) // make each note of equal length
.s('sawtooth') // waveform
.gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n"
.s('sawtooth') // waveform
.gain(.16) // turn down
.cutoff(500) // fixed cutoff
.attack(1) // slowly fade in
)
.slow(3/2)`}
/>
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,mdx}'],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/typography')],
};
+25
View File
@@ -0,0 +1,25 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import _mdx from 'vite-plugin-mdx';
import { viteSingleFile } from 'vite-plugin-singlefile';
// import remarkToc from 'remark-toc';
// import rehypeSlug from 'rehype-slug';
// import rehypeAutolinkHeadings from 'rehype-autolink-headings';
const mdx = _mdx.default || _mdx;
// `options` are passed to `@mdx-js/mdx`
const options = {
// See https://mdxjs.com/advanced/plugins
remarkPlugins: [
// remarkToc,
// E.g. `remark-frontmatter`
],
rehypePlugins: [
// rehypeSlug, rehypeAutolinkHeadings
],
};
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), mdx(options), viteSingleFile()],
});