mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-24 03:58:53 +00:00
Merge remote-tracking branch 'origin/main' into embed
This commit is contained in:
commit
d752d0eb5e
@ -5,7 +5,7 @@
|
|||||||
"description": "Port of tidalcycles to javascript",
|
"description": "Port of tidalcycles to javascript",
|
||||||
"main": "strudel.mjs",
|
"main": "strudel.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run test --workspaces --if-present",
|
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"setup": "npm i && npm run bootstrap && cd repl && npm i",
|
"setup": "npm i && npm run bootstrap && cd repl && npm i",
|
||||||
"repl": "cd repl && npm run start",
|
"repl": "cd repl && npm run start",
|
||||||
|
|||||||
@ -83,6 +83,10 @@ export class Hap {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showWhole() {
|
||||||
|
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
combineContext(b) {
|
combineContext(b) {
|
||||||
const a = this;
|
const a = this;
|
||||||
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
|
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
|
||||||
|
|||||||
8735
repl/package-lock.json
generated
8735
repl/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "BUILD_PATH='../docs' react-scripts build && npm run build-tutorial",
|
"build": "BUILD_PATH='../docs' react-scripts build && npm run build-tutorial",
|
||||||
"test": "react-scripts test",
|
"test": "mocha ./src/test --colors",
|
||||||
|
"snapshot": "cd ./src/ && rm -f ./tunes.snapshot.mjs && node ./shoot.mjs > ./tunes.snapshot.mjs",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"tutorial": "parcel src/tutorial/index.html --no-cache",
|
"tutorial": "parcel src/tutorial/index.html --no-cache",
|
||||||
"build-tutorial": "rm -rf ../docs/tutorial && parcel build src/tutorial/index.html --dist-dir ../docs/tutorial --public-url /tutorial --no-scope-hoist --no-cache",
|
"build-tutorial": "rm -rf ../docs/tutorial && parcel build src/tutorial/index.html --dist-dir ../docs/tutorial --public-url /tutorial --no-scope-hoist --no-cache",
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
App.test.js - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.test.js>
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
185
repl/src/runtime.mjs
Normal file
185
repl/src/runtime.mjs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
// this file contains a runtime scope for testing all the tunes
|
||||||
|
// it mocks all the functions that won't work in node (who are not important for testing values / structure)
|
||||||
|
// it might require mocking more stuff when tunes added that use other functions
|
||||||
|
|
||||||
|
// import * as tunes from './tunes.mjs';
|
||||||
|
import { evaluate } from '@strudel.cycles/eval';
|
||||||
|
import { extend } from '@strudel.cycles/eval';
|
||||||
|
import * as strudel from '@strudel.cycles/core';
|
||||||
|
// import gist from '@strudel.cycles/core/gist.js';
|
||||||
|
import { mini } from '@strudel.cycles/mini/mini.mjs';
|
||||||
|
// import { Tone } from '@strudel.cycles/tone';
|
||||||
|
// import * as toneHelpers from '@strudel.cycles/tone/tone.mjs';
|
||||||
|
// import * as voicingHelpers from '@strudel.cycles/tonal/voicings.mjs';
|
||||||
|
// import * as uiHelpers from '@strudel.cycles/tone/ui.mjs';
|
||||||
|
// import * as drawHelpers from '@strudel.cycles/tone/draw.mjs';
|
||||||
|
// import euclid from '@strudel.cycles/core/euclid.mjs';
|
||||||
|
// import '@strudel.cycles/tone/tone.mjs';
|
||||||
|
// import '@strudel.cycles/midi/midi.mjs';
|
||||||
|
import '@strudel.cycles/tonal/voicings.mjs';
|
||||||
|
import '@strudel.cycles/tonal/tonal.mjs';
|
||||||
|
import '@strudel.cycles/xen/xen.mjs';
|
||||||
|
// import '@strudel.cycles/xen/tune.mjs';
|
||||||
|
// import '@strudel.cycles/core/euclid.mjs';
|
||||||
|
// import '@strudel.cycles/core/speak.mjs'; // window is not defined
|
||||||
|
// import '@strudel.cycles/tone/pianoroll.mjs';
|
||||||
|
// import '@strudel.cycles/tone/draw.mjs';
|
||||||
|
// import '@strudel.cycles/osc/osc.mjs';
|
||||||
|
// import '@strudel.cycles/webaudio/webaudio.mjs';
|
||||||
|
// import '@strudel.cycles/serial/serial.mjs';
|
||||||
|
// import controls from '@strudel.cycles/core/controls.mjs';
|
||||||
|
|
||||||
|
class MockedNode {
|
||||||
|
chain() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
connect() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
toDestination() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
set() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockNode = () => new MockedNode();
|
||||||
|
|
||||||
|
const id = (x) => x;
|
||||||
|
|
||||||
|
const toneHelpersMocked = {
|
||||||
|
FeedbackDelay: MockedNode,
|
||||||
|
MembraneSynth: MockedNode,
|
||||||
|
NoiseSynth: MockedNode,
|
||||||
|
MetalSynth: MockedNode,
|
||||||
|
Synth: MockedNode,
|
||||||
|
PolySynth: MockedNode,
|
||||||
|
Chorus: MockedNode,
|
||||||
|
Freeverb: MockedNode,
|
||||||
|
Gain: MockedNode,
|
||||||
|
vol: mockNode,
|
||||||
|
out: id,
|
||||||
|
osc: id,
|
||||||
|
adsr: id,
|
||||||
|
getDestination: id,
|
||||||
|
players: mockNode,
|
||||||
|
sampler: mockNode,
|
||||||
|
synth: mockNode,
|
||||||
|
piano: mockNode,
|
||||||
|
polysynth: mockNode,
|
||||||
|
fmsynth: mockNode,
|
||||||
|
membrane: mockNode,
|
||||||
|
noise: mockNode,
|
||||||
|
metal: mockNode,
|
||||||
|
lowpass: mockNode,
|
||||||
|
highpass: mockNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
// tone mock
|
||||||
|
strudel.Pattern.prototype.tone = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// draw mock
|
||||||
|
strudel.Pattern.prototype.pianoroll = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// speak mock
|
||||||
|
strudel.Pattern.prototype.speak = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// webaudio mock
|
||||||
|
strudel.Pattern.prototype.wave = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
strudel.Pattern.prototype.filter = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
strudel.Pattern.prototype.adsr = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
strudel.Pattern.prototype.out = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
// tune mock
|
||||||
|
strudel.Pattern.prototype.tune = function () {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uiHelpersMocked = {
|
||||||
|
backgroundImage: id,
|
||||||
|
};
|
||||||
|
|
||||||
|
extend(
|
||||||
|
// Tone,
|
||||||
|
strudel,
|
||||||
|
strudel.Pattern.prototype.bootstrap(),
|
||||||
|
toneHelpersMocked,
|
||||||
|
uiHelpersMocked,
|
||||||
|
/* controls,
|
||||||
|
toneHelpers,
|
||||||
|
voicingHelpers,
|
||||||
|
drawHelpers,
|
||||||
|
uiHelpers,
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
// gist,
|
||||||
|
// euclid,
|
||||||
|
mini,
|
||||||
|
// Tone,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const queryCode = async (code, cycles = 1) => {
|
||||||
|
const { pattern } = await evaluate(code);
|
||||||
|
const haps = pattern.queryArc(0, cycles);
|
||||||
|
return haps.map((h) => h.showWhole());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testCycles = {
|
||||||
|
timeCatMini: 16,
|
||||||
|
timeCat: 8,
|
||||||
|
shapeShifted: 16,
|
||||||
|
tetrisMini: 16,
|
||||||
|
whirlyStrudel: 16,
|
||||||
|
swimming: 51,
|
||||||
|
giantSteps: 20,
|
||||||
|
giantStepsReggae: 25,
|
||||||
|
transposedChordsHacked: 8,
|
||||||
|
scaleTranspose: 16,
|
||||||
|
struct: 4,
|
||||||
|
magicSofa: 8,
|
||||||
|
confusedPhone: 8,
|
||||||
|
zeldasRescue: 48,
|
||||||
|
technoDrums: 4,
|
||||||
|
caverave: 60,
|
||||||
|
callcenterhero: 22,
|
||||||
|
primalEnemy: 4,
|
||||||
|
synthDrums: 4,
|
||||||
|
sampleDrums: 4,
|
||||||
|
xylophoneCalling: 60,
|
||||||
|
sowhatelse: 60,
|
||||||
|
barryHarris: 64,
|
||||||
|
wavyKalimba: 64,
|
||||||
|
jemblung: 12,
|
||||||
|
risingEnemy: 12,
|
||||||
|
festivalOfFingers: 16,
|
||||||
|
festivalOfFingers2: 22,
|
||||||
|
undergroundPlumber: 20,
|
||||||
|
bridgeIsOver: 16,
|
||||||
|
goodTimes: 16,
|
||||||
|
echoPiano: 8,
|
||||||
|
sml1: 48,
|
||||||
|
speakerman: 48,
|
||||||
|
randomBells: 24,
|
||||||
|
waa: 16,
|
||||||
|
waar: 16,
|
||||||
|
hyperpop: 60,
|
||||||
|
festivalOfFingers3: 16,
|
||||||
|
};
|
||||||
11
repl/src/shoot.mjs
Normal file
11
repl/src/shoot.mjs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// this script will render all example tunes and log them to the console.
|
||||||
|
// it is intended to be written to tunes.snapshot.mjs using `npm run snapshot`
|
||||||
|
|
||||||
|
import * as tunes from './tunes.mjs';
|
||||||
|
import { queryCode, testCycles } from './runtime.mjs';
|
||||||
|
|
||||||
|
Object.entries(tunes).forEach(([key, code]) => {
|
||||||
|
queryCode(code, testCycles[key] || 1).then((haps) => {
|
||||||
|
console.log(`export const ${key} = ${JSON.stringify(haps)}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
18
repl/src/test/tunes.test.mjs
Normal file
18
repl/src/test/tunes.test.mjs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { queryCode, testCycles } from '../runtime.mjs';
|
||||||
|
import * as snaps from '../tunes.snapshot.mjs';
|
||||||
|
import * as tunes from '../tunes.mjs';
|
||||||
|
import { strict as assert } from 'assert';
|
||||||
|
|
||||||
|
async function testTune(key) {
|
||||||
|
// console.log('test tune', key);
|
||||||
|
const haps = await queryCode(tunes[key], testCycles[key] || 1);
|
||||||
|
assert.deepStrictEqual(haps, snaps[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('renders tunes', () => {
|
||||||
|
Object.keys(tunes).forEach((key) => {
|
||||||
|
it(`tune: ${key}`, async () => {
|
||||||
|
await testTune(key);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -63,7 +63,7 @@ export const shapeShifted = `stack(
|
|||||||
)
|
)
|
||||||
).slow(16)`; */
|
).slow(16)`; */
|
||||||
|
|
||||||
export const tetris = `stack(
|
/* export const tetris = `stack(
|
||||||
seq(
|
seq(
|
||||||
"e5 [b4 c5] d5 [c5 b4]",
|
"e5 [b4 c5] d5 [c5 b4]",
|
||||||
"a4 [a4 c5] e5 [d5 c5]",
|
"a4 [a4 c5] e5 [d5 c5]",
|
||||||
@ -85,7 +85,7 @@ export const tetris = `stack(
|
|||||||
"a1 a2 a1 a2 a1 a2 a1 a2",
|
"a1 a2 a1 a2 a1 a2 a1 a2",
|
||||||
)
|
)
|
||||||
).slow(16)`;
|
).slow(16)`;
|
||||||
|
*/
|
||||||
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
|
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
|
||||||
[a4 [a4 c5] e5 [d5 c5]]
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
[b4 [~ c5] d5 e5]
|
[b4 [~ c5] d5 e5]
|
||||||
@ -226,13 +226,13 @@ export const transposedChordsHacked = `stack(
|
|||||||
"c2 eb2 g2",
|
"c2 eb2 g2",
|
||||||
"Cm7".voicings(['g2','c4']).slow(2)
|
"Cm7".voicings(['g2','c4']).slow(2)
|
||||||
).transpose(
|
).transpose(
|
||||||
cat(1, 2, 3, 2).slow(2)
|
"<1 2 3 2>".slow(2)
|
||||||
).transpose(5)`;
|
).transpose(5)`;
|
||||||
|
|
||||||
export const scaleTranspose = `stack(f2, f3, c4, ab4)
|
export const scaleTranspose = `"f2,f3,c4,ab4"
|
||||||
.scale(seq('F minor', 'F harmonic minor').slow(4))
|
.scale(seq('F minor', 'F harmonic minor').slow(4))
|
||||||
.scaleTranspose(seq(0, -1, -2, -3).slow(4))
|
.scaleTranspose("<0 -1 -2 -3>")
|
||||||
.transpose(seq(0, 1).slow(16))`;
|
.transpose("0 1".slow(16))`;
|
||||||
|
|
||||||
export const struct = `stack(
|
export const struct = `stack(
|
||||||
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
|
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
|
||||||
@ -245,7 +245,7 @@ export const magicSofa = `stack(
|
|||||||
.every(2, fast(2))
|
.every(2, fast(2))
|
||||||
.voicings(),
|
.voicings(),
|
||||||
"<c2 f2 g2> <d2 g2 a2 e2>"
|
"<c2 f2 g2> <d2 g2 a2 e2>"
|
||||||
).slow(1).transpose(cat(0, 2, 3, 4))`;
|
).transpose("<0 2 3 4>")`;
|
||||||
// below doesn't work anymore due to constructor cleanup
|
// below doesn't work anymore due to constructor cleanup
|
||||||
// ).slow(1).transpose.cat(0, 2, 3, 4)`;
|
// ).slow(1).transpose.cat(0, 2, 3, 4)`;
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
|
|||||||
transpose(24).late(0.4)
|
transpose(24).late(0.4)
|
||||||
)
|
)
|
||||||
.scale(cat('C dorian', 'C mixolydian'))
|
.scale(cat('C dorian', 'C mixolydian'))
|
||||||
.scaleTranspose(cat(0,1,2,1))
|
.scaleTranspose("<0 1 2 1>")
|
||||||
.slow(2)`;
|
.slow(2)`;
|
||||||
|
|
||||||
export const zeldasRescue = `stack(
|
export const zeldasRescue = `stack(
|
||||||
@ -285,32 +285,9 @@ export const zeldasRescue = `stack(
|
|||||||
)`;
|
)`;
|
||||||
|
|
||||||
export const technoDrums = `stack(
|
export const technoDrums = `stack(
|
||||||
"c1*2".tone(new Tone.MembraneSynth().toDestination()),
|
"c1*2".tone(new MembraneSynth().toDestination()),
|
||||||
"~ x".tone(new Tone.NoiseSynth().toDestination()),
|
"~ x".tone(new NoiseSynth().toDestination()),
|
||||||
"[~ c4]*2".tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),getDestination()))
|
"[~ c4]*2".tone(new MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),getDestination()))
|
||||||
)`;
|
|
||||||
|
|
||||||
export const loungerave = `const delay = new FeedbackDelay(1/8, .2).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).mask("<x@7 ~>/8"),
|
|
||||||
"~ <x!7 [x@3 x]>".tone(snare).mask("<x@7 ~>/4"),
|
|
||||||
"[~ c4]*2".tone(hihat)
|
|
||||||
);
|
|
||||||
|
|
||||||
const thru = (x) => x.transpose("<0 1>/8").transpose(1);
|
|
||||||
const synths = stack(
|
|
||||||
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2").edit(thru).tone(bass),
|
|
||||||
"<Cm7 Bb7 Fm7 G7b9>/2".struct("~ [x@0.1 ~]").voicings().edit(thru).every(2, early(1/4)).tone(keys).mask("<x@7 ~>/8".early(1/4))
|
|
||||||
)
|
|
||||||
stack(
|
|
||||||
drums,
|
|
||||||
synths
|
|
||||||
)`;
|
)`;
|
||||||
|
|
||||||
export const caverave = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());
|
export const caverave = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());
|
||||||
|
|||||||
40
repl/src/tunes.snapshot.mjs
Normal file
40
repl/src/tunes.snapshot.mjs
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user