mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 13:48:40 +00:00
migrate missing files + fix tests
This commit is contained in:
parent
645ea30b53
commit
92c17d6376
@ -1,2 +1,4 @@
|
||||
- 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",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"start": "vite",
|
||||
"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": {
|
||||
"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…
x
Reference in New Issue
Block a user