Merge branch 'main' into obj-support-scale

This commit is contained in:
Felix Roos 2022-12-15 17:49:24 +01:00
commit 7f9ef59bea
52 changed files with 358 additions and 198 deletions

View File

@ -10,10 +10,12 @@ jobs:
node-version: [18]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm install
- run: npm test
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install
- run: npm run format-check
- run: npm run lint
- run: npm test

View File

@ -5,3 +5,4 @@
**/out
**/dist
packages/mini/krill-parser.js
packages/xen/tunejs.js

View File

@ -1,16 +1,15 @@
{
"printWidth": 120,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}
"printWidth": 120,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}

110
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@strudel.cycles/monorepo",
"version": "0.0.4",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@strudel.cycles/monorepo",
"version": "0.0.4",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"workspaces": [
"packages/*"
@ -12044,7 +12044,7 @@
},
"packages/core": {
"name": "@strudel.cycles/core",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"bjork": "^0.0.1",
@ -12053,7 +12053,7 @@
},
"packages/csound": {
"name": "@strudel.cycles/csound",
"version": "0.3.0",
"version": "0.5.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@csound/browser": "^6.18.3"
@ -12066,10 +12066,10 @@
},
"packages/eval": {
"name": "@strudel.cycles/eval",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"estraverse": "^5.3.0",
"shift-ast": "^7.0.0",
"shift-codegen": "^8.1.0",
@ -12080,10 +12080,10 @@
},
"packages/midi": {
"name": "@strudel.cycles/midi",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/tone": "^0.5.0",
"tone": "^14.7.77",
"webmidi": "^3.0.21"
}
@ -12103,12 +12103,12 @@
},
"packages/mini": {
"name": "@strudel.cycles/mini",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/eval": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1"
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/eval": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0"
},
"devDependencies": {
"peggy": "^2.0.1"
@ -12116,7 +12116,7 @@
},
"packages/osc": {
"name": "@strudel.cycles/osc",
"version": "0.3.1",
"version": "0.4.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"osc-js": "^2.4.0"
@ -12127,14 +12127,14 @@
},
"packages/react": {
"name": "@strudel.cycles/react",
"version": "0.4.2",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/transpiler": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"@uiw/codemirror-themes": "^4.12.4",
"@uiw/react-codemirror": "^4.12.4",
"react-hook-inview": "^4.5.0"
@ -12201,16 +12201,16 @@
},
"packages/serial": {
"name": "@strudel.cycles/serial",
"version": "0.2.0",
"version": "0.3.0",
"license": "AGPL-3.0-or-later"
},
"packages/soundfonts": {
"name": "@strudel.cycles/soundfonts",
"version": "0.4.2",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"sfumato": "^0.1.2",
"soundfont2": "^0.4.0"
},
@ -12237,10 +12237,10 @@
},
"packages/tonal": {
"name": "@strudel.cycles/tonal",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1",
"webmidi": "^3.0.21"
@ -12261,19 +12261,19 @@
},
"packages/tone": {
"name": "@strudel.cycles/tone",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"tone": "^14.7.77"
}
},
"packages/transpiler": {
"name": "@strudel.cycles/transpiler",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"acorn": "^8.8.1",
"escodegen": "^2.0.0",
"estree-walker": "^3.0.1"
@ -12291,27 +12291,27 @@
},
"packages/webaudio": {
"name": "@strudel.cycles/webaudio",
"version": "0.4.2",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1"
"@strudel.cycles/core": "^0.5.0"
}
},
"packages/webdirt": {
"name": "@strudel.cycles/webdirt",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"WebDirt": "github:dktr0/WebDirt"
}
},
"packages/xen": {
"name": "@strudel.cycles/xen",
"version": "0.4.1",
"version": "0.5.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.4.1"
"@strudel.cycles/core": "^0.5.0"
}
}
},
@ -13740,7 +13740,7 @@
"@strudel.cycles/eval": {
"version": "file:packages/eval",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"estraverse": "^5.3.0",
"shift-ast": "^7.0.0",
"shift-codegen": "^8.1.0",
@ -13752,7 +13752,7 @@
"@strudel.cycles/midi": {
"version": "file:packages/midi",
"requires": {
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/tone": "^0.5.0",
"tone": "^14.7.77",
"webmidi": "^3.0.21"
},
@ -13769,9 +13769,9 @@
"@strudel.cycles/mini": {
"version": "file:packages/mini",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/eval": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/eval": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0",
"peggy": "^2.0.1"
}
},
@ -13786,10 +13786,10 @@
"version": "file:packages/react",
"requires": {
"@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/transpiler": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@uiw/codemirror-themes": "^4.12.4",
@ -13838,8 +13838,8 @@
"@strudel.cycles/soundfonts": {
"version": "file:packages/soundfonts",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"node-fetch": "^3.2.6",
"sfumato": "^0.1.2",
"soundfont2": "^0.4.0"
@ -13859,7 +13859,7 @@
"@strudel.cycles/tonal": {
"version": "file:packages/tonal",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1",
"webmidi": "^3.0.21"
@ -13877,14 +13877,14 @@
"@strudel.cycles/tone": {
"version": "file:packages/tone",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"tone": "^14.7.77"
}
},
"@strudel.cycles/transpiler": {
"version": "file:packages/transpiler",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"acorn": "^8.8.1",
"escodegen": "^2.0.0",
"estree-walker": "^3.0.1"
@ -13898,20 +13898,20 @@
"@strudel.cycles/webaudio": {
"version": "file:packages/webaudio",
"requires": {
"@strudel.cycles/core": "^0.4.1"
"@strudel.cycles/core": "^0.5.0"
}
},
"@strudel.cycles/webdirt": {
"version": "file:packages/webdirt",
"requires": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"WebDirt": "github:dktr0/WebDirt"
}
},
"@strudel.cycles/xen": {
"version": "file:packages/xen",
"requires": {
"@strudel.cycles/core": "^0.4.1"
"@strudel.cycles/core": "^0.5.0"
}
},
"@tonaljs/abc-notation": {
@ -18656,10 +18656,10 @@
"version": "file:packages/react",
"requires": {
"@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/transpiler": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@uiw/codemirror-themes": "^4.12.4",

View File

@ -1,11 +1,11 @@
{
"name": "@strudel.cycles/monorepo",
"version": "0.0.4",
"version": "0.5.0",
"private": true,
"description": "Port of tidalcycles to javascript",
"scripts": {
"pretest": "cd tutorial && npm run jsdoc-json",
"test": "npm run lint && vitest run --version",
"test": "vitest run --version",
"test-ui": "vitest --ui",
"test-coverage": "vitest --coverage",
"bootstrap": "lerna bootstrap",
@ -18,8 +18,10 @@
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
"lint": "npx eslint . --ext mjs,js --quiet",
"codeformat": "prettier --write ."
"lint": "eslint . --ext mjs,js --quiet",
"codeformat": "prettier --write .",
"format-check": "prettier --check .",
"check": "npm run format-check && npm run lint && npm run test"
},
"workspaces": [
"packages/*"

View File

@ -16,7 +16,7 @@ evalScope(
import('@strudel.cycles/tonal'),
);
setStringParser(mini)
setStringParser(mini);
const { evaluate } = repl({
defaultOutput: webaudioOutput,

View File

@ -80,16 +80,16 @@ export class Hap {
}
show(compact = false) {
const value = typeof this.value === 'object'
? compact
const value =
typeof this.value === 'object'
? compact
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
: JSON.stringify(this.value)
: this.value
: this.value;
var spans = '';
if (this.whole == undefined) {
spans = '~' + this.part.show;
}
else {
} else {
var is_whole = this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end);
if (!this.whole.begin.equals(this.part.begin)) {
spans = this.whole.begin.show() + ' ⇜ ';
@ -102,12 +102,10 @@ export class Hap {
spans += ')';
}
if (!this.whole.end.equals(this.part.end)) {
spans += ' ⇝ ' + this.whole.end.show()
spans += ' ⇝ ' + this.whole.end.show();
}
}
return (
'[ ' + spans + ' | ' + value + ' ]'
);
return '[ ' + spans + ' | ' + value + ' ]';
}
showWhole(compact = false) {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.4.1",
"version": "0.5.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",

View File

@ -534,6 +534,57 @@ export class Pattern {
return this.filterHaps((hap) => hap.whole);
}
/**
* Combines adjacent haps with the same value and whole. Only
* intended for use in tests.
*/
defragmentHaps() {
// remove continuous haps
const pat = this.discreteOnly();
return pat.withHaps((haps) => {
const result = [];
for (var i = 0; i < haps.length; ++i) {
var searching = true;
var a = haps[i];
while (searching) {
const a_value = JSON.stringify(haps[i].value);
var found = false;
for (var j = i + 1; j < haps.length; j++) {
const b = haps[j];
if (a.whole.equals(b.whole)) {
if (a.part.begin.eq(b.part.end)) {
if (a_value === JSON.stringify(b.value)) {
// eat the matching hap into 'a'
a = new Hap(a.whole, new TimeSpan(b.part.begin, a.part.end), a.value);
haps.splice(j, 1);
// restart the search
found = true;
break;
}
} else if (b.part.begin.eq(a.part.end)) {
if (a_value == JSON.stringify(b.value)) {
// eat the matching hap into 'a'
a = new Hap(a.whole, new TimeSpan(a.part.begin, b.part.end), a.value);
haps.splice(j, 1);
// restart the search
found = true;
break;
}
}
}
}
searching = found;
}
result.push(a);
}
return result;
});
}
/**
* Queries the pattern for the first cycle, returning Haps. Mainly of use when
* debugging a pattern.

View File

@ -915,4 +915,20 @@ describe('Pattern', () => {
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle());
});
});
describe('defragmentHaps', () => {
it('Can merge two touching haps with same whole and value', () => {
expect(stack(pure('a').mask(1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(1);
});
it('Doesnt merge two overlapping haps', () => {
expect(stack(pure('a').mask(1, 1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(
2,
);
});
it('Doesnt merge two touching haps with different values', () => {
expect(stack(pure('a').mask(1, 0), pure('b').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
});
it('Doesnt merge two touching haps with different wholes', () => {
expect(stack(sequence('a', silence), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
});
});
});

View File

@ -10,6 +10,7 @@ import {
tokenizeNote,
toMidi,
fromMidi,
freqToMidi,
_mod,
compose,
getFrequency,
@ -94,6 +95,12 @@ describe('fromMidi', () => {
expect(fromMidi(57)).toEqual(220);
});
});
describe('freqToMidi', () => {
it('should turn frequency into midi', () => {
expect(freqToMidi(440)).toEqual(69);
expect(freqToMidi(220)).toEqual(57);
});
});
describe('getFrequency', () => {
const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context);
it('should turn note into frequency', () => {

View File

@ -20,9 +20,9 @@ export class TimeSpan {
// Support zero-width timespans
if (begin.equals(end)) {
return([new TimeSpan(begin, end)]);
return [new TimeSpan(begin, end)];
}
while (end.gt(begin)) {
// If begin and end are in the same cycle, we're done.
if (begin.sam().equals(end_sam)) {

View File

@ -31,6 +31,30 @@ export const fromMidi = (n) => {
return Math.pow(2, (n - 69) / 12) * 440;
};
export const freqToMidi = (freq) => {
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
};
export const valueToMidi = (value, fallbackValue) => {
if (typeof value !== 'object') {
throw new Error('valueToMidi: expected object value');
}
let { freq, note } = value;
if (typeof freq === 'number') {
return freqToMidi(freq);
}
if (typeof note === 'string') {
return toMidi(note);
}
if (typeof note === 'number') {
return note;
}
if (!fallbackValue) {
throw new Error('valueToMidi: expected freq or note to be set');
}
return fallbackValue;
};
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
*/

View File

@ -79,21 +79,25 @@ function eventLogger(type, args) {
async function load() {
if (window.__csound__) {
// allows using some other csound instance
// Allows using some other csound instance.
// In that case, the external Csound is responsible
// for compiling an orchestra and starting to perform.
logger('[load] Using external Csound', 'warning');
_csound = window.__csound__;
return _csound;
} else {
const { Csound } = await import('@csound/browser');
_csound = await Csound({ audioContext: getAudioContext() });
_csound.removeAllListeners('message');
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
await _csound.setOption('--sample-accurate');
await _csound.compileCsdText(csd);
// await _csound.compileOrc(livecodeOrc);
await _csound.compileOrc(presetsOrc);
await _csound.start();
return _csound;
}
_csound.removeAllListeners('message');
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
await _csound.setOption('--sample-accurate');
await _csound.compileCsdText(csd);
// await _csound.compileOrc(livecodeOrc);
await _csound.compileOrc(presetsOrc);
await _csound.start();
return _csound;
}
async function init() {
@ -118,3 +122,49 @@ export async function loadOrc(url) {
}
await orcCache[url];
}
/**
* Sends notes to Csound for rendering with MIDI semantics. The hap value is
* translated to these Csound pfields:
*
* p1 -- Csound instrument either as a number (1-based, can be a fraction),
* or as a string name.
* p2 -- time in beats (usually seconds) from start of performance.
* p3 -- duration in beats (usually seconds).
* p4 -- MIDI key number (as a real number, not an integer but in [0, 127].
* p5 -- MIDI velocity (as a real number, not an integer but in [0, 127].
* p6 -- Strudel controls, as a string.
*/
export const csoundm = register('csoundm', (instrument, pat) => {
let p1 = instrument;
if (typeof instrument === 'string') {
p1 = `"{instrument}"`;
}
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
return pat.onTrigger((tidal_time, hap) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
}
if (typeof hap.value !== 'object') {
throw new Error('csound only support objects as hap values');
}
// Time in seconds counting from now.
const p2 = tidal_time - getAudioContext().currentTime;
const p3 = hap.duration.valueOf() + 0;
const frequency = getFrequency(hap);
// Translate frequency to MIDI key number _without_ rounding.
const C4 = 261.62558;
let octave = Math.log(frequency / C4) / Math.log(2.0) + 8.0;
const p4 = octave * 12.0 - 36.0;
// We prefer floating point precision, but over the MIDI range [0, 127].
const p5 = 127 * (hap.context?.velocity ?? 0.9);
// The Strudel controls as a string.
const p6 = Object.entries({ ...hap.value, frequency })
.flat()
.join('/');
const i_statement = `i ${p1} ${p2} ${p3} ${p4} ${p5} "${p6}"`;
console.log('[csoundm]:', i_statement);
_csound.inputMessage(i_statement);
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/csound",
"version": "0.3.0",
"version": "0.5.1",
"description": "csound bindings for strudel",
"main": "csound.mjs",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.4.1",
"version": "0.5.0",
"description": "Code evaluator for strudel",
"main": "index.mjs",
"type": "module",
@ -28,7 +28,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"estraverse": "^5.3.0",
"shift-ast": "^7.0.0",
"shift-codegen": "^8.1.0",

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.4.1",
"version": "0.5.0",
"description": "Midi API for strudel",
"main": "index.mjs",
"repository": {
@ -21,7 +21,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/tone": "^0.5.0",
"tone": "^14.7.77",
"webmidi": "^3.0.21"
}

View File

@ -6,7 +6,6 @@ This program is free software: you can redistribute it and/or modify it under th
import * as krill from './krill-parser.js';
import * as strudel from '@strudel.cycles/core';
// import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
const { pure, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
@ -104,11 +103,11 @@ function resolveReplications(ast) {
});
}
export function patternifyAST(ast) {
export function patternifyAST(ast, code) {
switch (ast.type_) {
case 'pattern': {
resolveReplications(ast);
const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
const children = ast.source_.map((child) => patternifyAST(child, code)).map(applyOptions(ast));
const alignment = ast.arguments_.alignment;
if (alignment === 'v') {
return stack(...children);
@ -137,9 +136,6 @@ export function patternifyAST(ast) {
return silence;
}
if (typeof ast.source_ !== 'object') {
/* if (!addMiniLocations) {
return ast.source_;
} */
if (!ast.location_) {
console.warn('no location for', ast);
return ast.source_;
@ -148,12 +144,20 @@ export function patternifyAST(ast) {
const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
// the following line expects the shapeshifter append .withMiniLocation
// because location_ is only relative to the mini string, but we need it relative to whole code
return pure(value).withLocation([start.line, start.column, start.offset], [end.line, end.column, end.offset]);
// make sure whitespaces are not part of the highlight:
const actual = code?.split('').slice(start.offset, end.offset).join('');
const [offsetStart = 0, offsetEnd = 0] = actual
? actual.split(ast.source_).map((p) => p.split('').filter((c) => c === ' ').length)
: [];
return pure(value).withLocation(
[start.line, start.column + offsetStart, start.offset + offsetStart],
[start.line, end.column - offsetEnd, end.offset - offsetEnd],
);
}
return patternifyAST(ast.source_);
return patternifyAST(ast.source_, code);
}
case 'stretch':
return patternifyAST(ast.source_).slow(ast.arguments_.amount);
return patternifyAST(ast.source_, code).slow(ast.arguments_.amount);
/* case 'scale':
let [tonic, scale] = Scale.tokenize(ast.arguments_.scale);
const intervals = Scale.get(scale).intervals;
@ -185,8 +189,9 @@ export function patternifyAST(ast) {
// mini notation only (wraps in "")
export const mini = (...strings) => {
const pats = strings.map((str) => {
const ast = krill.parse(`"${str}"`);
return patternifyAST(ast);
const code = `"${str}"`;
const ast = krill.parse(code);
return patternifyAST(ast, code);
});
return sequence(...pats);
};
@ -195,7 +200,7 @@ export const mini = (...strings) => {
export const h = (string) => {
const ast = krill.parse(string);
// console.log('ast', ast);
return patternifyAST(ast);
return patternifyAST(ast, string);
};
export function minify(thing) {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.4.1",
"version": "0.5.0",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
@ -26,9 +26,9 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/eval": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1"
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/eval": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0"
},
"devDependencies": {
"peggy": "^2.0.1"

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.3.1",
"version": "0.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.3.1",
"version": "0.4.0",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"scripts": {

View File

@ -25,8 +25,6 @@ const config = {
},
};
const osc = new OSC({ plugin: new OSC.BridgePlugin(config) });
osc.open(); // start a WebSocket server on port 8080

View File

@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})
plugins: [react()],
});

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> -->
<!-- <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Strudel React Components</title>
</head>

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/react",
"version": "0.4.2",
"version": "0.5.0",
"description": "React components for strudel",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
@ -39,10 +39,10 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/tone": "^0.5.0",
"@strudel.cycles/transpiler": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"@uiw/codemirror-themes": "^4.12.4",
"@uiw/react-codemirror": "^4.12.4",
"react-hook-inview": "^4.5.0"

View File

@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};

View File

@ -1,5 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<React.StrictMode><App /></React.StrictMode>,document.getElementById('root'))
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/serial",
"version": "0.2.0",
"version": "0.3.0",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"repository": {

View File

@ -23,17 +23,16 @@ export async function getWriter(br = 38400) {
const encoder = new TextEncoder();
const writer = port.writable.getWriter();
writeMessage = function (message, chk) {
const encoded = encoder.encode(message)
const encoded = encoder.encode(message);
if (!chk) {
writer.write(encoded);
}
else {
} else {
const bytes = new Uint8Array(4);
bytes[0] = 124; // | symbol
bytes[1] = (chk >> 8) & 0xFF;
bytes[2] = chk & 0xFF;
bytes[1] = (chk >> 8) & 0xff;
bytes[2] = chk & 0xff;
bytes[3] = 59; // semicolon
const withchk = new Uint8Array(encoded.length+4)
const withchk = new Uint8Array(encoded.length + 4);
withchk.set(encoded);
withchk.set(bytes, encoded.length);
writer.write(withchk);
@ -48,12 +47,12 @@ const latency = 0.1;
// crc16 (CCITT-FALSE) https://gist.github.com/tijnkooijmans/10981093
function crc16(data) {
const length = data.length
const length = data.length;
if (length == 0) {
return 0;
}
var crc = 0xFFFF;
var crc = 0xffff;
for (var i = 0; i < length; ++i) {
crc ^= data.charCodeAt(i) << 8;
for (var j = 0; j < 8; ++j) {
@ -64,7 +63,7 @@ function crc16(data) {
return crc & 0xffff;
}
Pattern.prototype.serial = function (br=38400,sendcrc=false,singlecharids=false) {
Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids = false) {
return this.withHap((hap) => {
if (!writeMessage) {
getWriter(br);
@ -96,7 +95,7 @@ Pattern.prototype.serial = function (br=38400,sendcrc=false,singlecharids=false)
}
message += ')';
if (sendcrc) {
chk = crc16(message)
chk = crc16(message);
}
} else {
for (const [key, val] of Object.entries(hap.value)) {
@ -108,7 +107,9 @@ Pattern.prototype.serial = function (br=38400,sendcrc=false,singlecharids=false)
}
const offset = (time - currentTime + latency) * 1000;
window.setTimeout(function () {writeMessage(message, chk)}, offset);
window.setTimeout(function () {
writeMessage(message, chk);
}, offset);
};
return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
});

View File

@ -42,7 +42,7 @@ export const instruments = [
'0031_FluidR3_GM_sf2_file',
'0031_GeneralUserGS_sf2_file',
'0031_SoundBlasterOld_sf2', // pianos until here
'0040_Aspirin_sf2_file',
'0040_Aspirin_sf2_file',
'0040_Chaos_sf2_file',
'0040_FluidR3_GM_sf2_file', // rhodes
'0040_GeneralUserGS_sf2_file', // staccato rhodes
@ -59,7 +59,7 @@ export const instruments = [
'0046_GeneralUserGS_sf2_file', // ?
'0050_Aspirin_sf2_file', // glass organ
'0050_Chaos_sf2_file', // short glass organ
'0050_FluidR3_GM_sf2_file',// long glass organ !
'0050_FluidR3_GM_sf2_file', // long glass organ !
'0050_GeneralUserGS_sf2_file', // short glass organ
'0050_JCLive_sf2_file', // glass organ
'0050_SBLive_sf2', // ?
@ -1635,7 +1635,7 @@ export const drums = [
'81_4_Chaos_sf2_file',
];
// see https://www.midi.org/specifications-old/item/gm-level-1-sound-set
// see https://www.midi.org/specifications-old/item/gm-level-1-sound-set
export const instrumentNames = [];
instrumentNames[0] = 'Acoustic Grand Piano: Piano';
instrumentNames[1] = 'Bright Acoustic Piano: Piano';

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.4.2",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.4.2",
"version": "0.5.0",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"type": "module",
@ -22,8 +22,8 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@strudel.cycles/core": "^0.5.0",
"@strudel.cycles/webaudio": "^0.5.0",
"sfumato": "^0.1.2",
"soundfont2": "^0.4.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.4.1",
"version": "0.5.0",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"type": "module",
@ -25,7 +25,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1",
"webmidi": "^3.0.21"

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tone",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tone",
"version": "0.4.1",
"version": "0.5.0",
"description": "Tone.js API for strudel",
"main": "index.mjs",
"type": "module",
@ -22,7 +22,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"tone": "^14.7.77"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/transpiler",
"version": "0.4.1",
"version": "0.5.0",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs",
"scripts": {
@ -24,7 +24,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"acorn": "^8.8.1",
"escodegen": "^2.0.0",
"estree-walker": "^3.0.1"

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.4.2",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.4.2",
"version": "0.5.0",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
@ -30,6 +30,6 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1"
"@strudel.cycles/core": "^0.5.0"
}
}

View File

@ -1,4 +1,4 @@
import { logger, toMidi } from '@strudel.cycles/core';
import { logger, toMidi, valueToMidi } from '@strudel.cycles/core';
import { getAudioContext } from './index.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer>
@ -20,9 +20,12 @@ function humanFileSize(bytes, si) {
return bytes.toFixed(1) + ' ' + units[u];
}
export const getSampleBufferSource = async (s, n, note, speed) => {
export const getSampleBufferSource = async (s, n, note, speed, freq) => {
let transpose = 0;
let midi = typeof note === 'string' ? toMidi(note) : note || 36;
if (freq !== undefined && note !== undefined) {
logger('[sampler] hap has note and freq. ignoring note', 'warning');
}
let midi = valueToMidi({ freq, note }, 36);
transpose = midi - 36; // C3 is middle C
const ac = getAudioContext();

View File

@ -288,10 +288,10 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
if (soundfont) {
// is soundfont
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac);
bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac, freq);
} else {
// is sample from loaded samples(..)
bufferSource = await getSampleBufferSource(s, n, note, speed);
bufferSource = await getSampleBufferSource(s, n, note, speed, freq);
}
// asny stuff above took too long?
if (ac.currentTime > t) {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.4.1",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.4.1",
"version": "0.5.0",
"description": "WebDirt integration for Strudel",
"main": "index.mjs",
"type": "module",
@ -22,7 +22,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/core": "^0.5.0",
"WebDirt": "github:dktr0/WebDirt"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/xen",
"version": "0.4.1",
"version": "0.5.0",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"scripts": {
@ -24,6 +24,6 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1"
"@strudel.cycles/core": "^0.5.0"
}
}

View File

@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};

View File

@ -1,13 +1,11 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}

View File

@ -1,4 +1,4 @@
import { Pattern, toMidi } from '@strudel.cycles/core';
import { Pattern, toMidi, valueToMidi } from '@strudel.cycles/core';
import { samples } from '@strudel.cycles/webaudio';
export async function prebake({ isMock = false, baseDir = '.' } = {}) {
@ -25,9 +25,9 @@ Pattern.prototype.piano = function () {
.s('piano')
.release(0.1)
.fmap((value) => {
const midi = typeof value.note === 'string' ? toMidi(value.note) : value.note;
const midi = valueToMidi(value);
// pan by pitch
const pan = panwidth(Math.min(midi / maxPan, 1), 0.5);
const pan = panwidth(Math.min(Math.round(midi) / maxPan, 1), 0.5);
return { ...value, pan: (value.pan || 1) * pan };
});
};

View File

@ -9,4 +9,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};