Merge pull request #104 from tidalcycles/tune-tests

Tune tests
This commit is contained in:
Felix Roos 2022-05-02 22:05:33 +02:00 committed by GitHub
commit 65fbdba047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 8302 additions and 755 deletions

View File

@ -5,7 +5,7 @@
"description": "Port of tidalcycles to javascript",
"main": "strudel.mjs",
"scripts": {
"test": "npm run test --workspaces --if-present",
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
"bootstrap": "lerna bootstrap",
"setup": "npm i && npm run bootstrap && cd repl && npm i",
"repl": "cd repl && npm run start",

View File

@ -82,6 +82,10 @@ export class Hap {
'(' + (this.whole == undefined ? '~' : this.whole.show()) + ', ' + this.part.show() + ', ' + this.value + ')'
);
}
showWhole() {
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
}
combineContext(b) {
const a = this;

8735
repl/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,8 @@
"scripts": {
"start": "react-scripts start",
"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",
"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",

View File

@ -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
View 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
View 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)}`);
});
});

View 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);
});
});
});

View File

@ -63,7 +63,7 @@ export const shapeShifted = `stack(
)
).slow(16)`; */
export const tetris = `stack(
/* export const tetris = `stack(
seq(
"e5 [b4 c5] d5 [c5 b4]",
"a4 [a4 c5] e5 [d5 c5]",
@ -85,7 +85,7 @@ export const tetris = `stack(
"a1 a2 a1 a2 a1 a2 a1 a2",
)
).slow(16)`;
*/
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
@ -226,13 +226,13 @@ export const transposedChordsHacked = `stack(
"c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2)
).transpose(
cat(1, 2, 3, 2).slow(2)
"<1 2 3 2>".slow(2)
).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))
.scaleTranspose(seq(0, -1, -2, -3).slow(4))
.transpose(seq(0, 1).slow(16))`;
.scaleTranspose("<0 -1 -2 -3>")
.transpose("0 1".slow(16))`;
export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
@ -245,7 +245,7 @@ export const magicSofa = `stack(
.every(2, fast(2))
.voicings(),
"<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
// ).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)
)
.scale(cat('C dorian', 'C mixolydian'))
.scaleTranspose(cat(0,1,2,1))
.scaleTranspose("<0 1 2 1>")
.slow(2)`;
export const zeldasRescue = `stack(
@ -285,32 +285,9 @@ export const zeldasRescue = `stack(
)`;
export const technoDrums = `stack(
"c1*2".tone(new Tone.MembraneSynth().toDestination()),
"~ x".tone(new Tone.NoiseSynth().toDestination()),
"[~ c4]*2".tone(new Tone.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
"c1*2".tone(new MembraneSynth().toDestination()),
"~ x".tone(new NoiseSynth().toDestination()),
"[~ c4]*2".tone(new MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),getDestination()))
)`;
export const caverave = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out());

File diff suppressed because one or more lines are too long