migrate missing files + fix tests
This commit is contained in:
parent
645ea30b53
commit
92c17d6376
13 changed files with 416 additions and 2 deletions
|
|
@ -1,2 +1,4 @@
|
||||||
- use react 17
|
- use react 17
|
||||||
- make sure @codemirror/state is installed once (single version)
|
- make sure @codemirror/state is installed once (single version)
|
||||||
|
|
||||||
|
"build": "BUILD_PATH='../docs' react-scripts build && npm run build-tutorial && npm run add-license",
|
||||||
0
repl/.nojekyll
Normal file
0
repl/.nojekyll
Normal file
1
repl/CNAME
Normal file
1
repl/CNAME
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
strudel.tidalcycles.org
|
||||||
30
repl/README.md
Normal file
30
repl/README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Strudel REPL
|
||||||
|
|
||||||
|
This is the REPL for Strudel. REPL stands for
|
||||||
|
|
||||||
|
- Read
|
||||||
|
- Evaluate
|
||||||
|
- Play!
|
||||||
|
- Loop
|
||||||
|
|
||||||
|
The REPL is deployed at [strudel.tidalcycles.org](https://strudel.tidalcycles.org/).
|
||||||
|
|
||||||
|
## Run REPL locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from project root
|
||||||
|
npm run setup
|
||||||
|
npm run repl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build REPL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd repl
|
||||||
|
npm run build # <- builds repl + tutorial to ../docs
|
||||||
|
npm run static # <- test static build
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO vite
|
||||||
|
|
||||||
|
- "build": "BUILD_PATH='../docs' react-scripts build && npm run build-tutorial && npm run add-license",
|
||||||
23
repl/etc/agpl-header.txt
Normal file
23
repl/etc/agpl-header.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Strudel - javascript-based environment for live coding algorithmic (musical) patterns
|
||||||
|
https://strudel.tidalcycles.org / https://github.com/tidalcycles/strudel/
|
||||||
|
|
||||||
|
Copyright (C) Strudel contributors
|
||||||
|
https://github.com/tidalcycles/strudel/graphs/contributors
|
||||||
|
|
||||||
|
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/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
15
repl/manifest.json
Normal file
15
repl/manifest.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"short_name": "Strudel REPL",
|
||||||
|
"name": "Strudel REPL - Tidal Patterns in JavaScript",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,18 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
"start": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test": "mocha ./src/test --colors",
|
||||||
|
"snapshot": "cd ./src/ && rm -f ./tunes.snapshot.mjs && node ./shoot.mjs > ./tunes.snapshot.mjs",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"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",
|
||||||
|
"add-license": "cat etc/agpl-header.txt ../docs/static/js/*LICENSE.txt > /tmp/strudel-license.txt && cp /tmp/strudel-license.txt ../docs/static/js/*LICENSE.txt",
|
||||||
|
"predeploy": "npm run build",
|
||||||
|
"deploy": "gh-pages -d ../docs",
|
||||||
|
"static": "npx serve ../docs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
|
|
||||||
3
repl/robots.txt
Normal file
3
repl/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
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)}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
75
repl/src/static.mjs
Normal file
75
repl/src/static.mjs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
static.mjs - <short description TODO>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/static.mjs>
|
||||||
|
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 { 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();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// after the querying is done, the events are scheduled
|
||||||
|
// after the scheduling is done, the transport is started
|
||||||
|
// nothing happens while tone.js runs except the schedule callback, which is a thin wrapper around triggerAttackRelease calls
|
||||||
|
// so all glitches that appear here should have nothing to do with strudel and or the repl
|
||||||
|
|
||||||
|
async function playStatic(code) {
|
||||||
|
Tone.getTransport().cancel();
|
||||||
|
Tone.getTransport().stop();
|
||||||
|
let start, took;
|
||||||
|
const seconds = Number(prompt('How many seconds to run?')) || 60;
|
||||||
|
start = performance.now();
|
||||||
|
console.log('evaluating..');
|
||||||
|
const { pattern: pat } = await evaluate(code);
|
||||||
|
took = performance.now() - start;
|
||||||
|
console.log('evaluate took', took, 'ms');
|
||||||
|
console.log('querying..');
|
||||||
|
start = performance.now();
|
||||||
|
const events = pat
|
||||||
|
?.query(new State(new TimeSpan(0, seconds)))
|
||||||
|
?.filter((event) => event.part.begin.equals(event.whole.begin))
|
||||||
|
?.map((event) => ({
|
||||||
|
time: event.whole.begin.valueOf(),
|
||||||
|
duration: event.whole.end.sub(event.whole.begin).valueOf(),
|
||||||
|
value: event.value,
|
||||||
|
context: event.context,
|
||||||
|
}));
|
||||||
|
took = performance.now() - start;
|
||||||
|
console.log('query took', took, 'ms');
|
||||||
|
console.log('scheduling..');
|
||||||
|
start = performance.now();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
err.message = 'unplayable event: ' + err?.message;
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}, event.time);
|
||||||
|
});
|
||||||
|
took = performance.now() - start;
|
||||||
|
console.log('scheduling took', took, 'ms');
|
||||||
|
console.log('now starting!');
|
||||||
|
|
||||||
|
Tone.getTransport().start('+0.5');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default playStatic;
|
||||||
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
41
repl/src/tunes.snapshot.mjs
Normal file
41
repl/src/tunes.snapshot.mjs
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue