diff --git a/packages/core/strudel.mjs b/packages/core/strudel.mjs index 5f659e886..a09657534 100644 --- a/packages/core/strudel.mjs +++ b/packages/core/strudel.mjs @@ -1,5 +1,5 @@ import Fraction from './fraction.mjs'; -import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry } from './util.mjs'; +import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, objectify } from './util.mjs'; class TimeSpan { constructor(begin, end) { @@ -515,7 +515,7 @@ class Pattern { } union(other) { - return this._opleft(other, (a) => (b) => Object.assign({}, a, b)); + return this._opleft(other, (a) => (b) => Object.assign({}, objectify(a), objectify(b))); } _bindWhole(choose_whole, func) { diff --git a/packages/core/test/util.test.mjs b/packages/core/test/util.test.mjs index 5e8c906e7..aad7a37ce 100644 --- a/packages/core/test/util.test.mjs +++ b/packages/core/test/util.test.mjs @@ -1,5 +1,5 @@ import { strict as assert } from 'assert'; -import { isNote, tokenizeNote, toMidi, mod, compose } from '../util.mjs'; +import { isNote, tokenizeNote, toMidi, mod, compose, dirtify } from '../util.mjs'; describe('isNote', () => { it('should recognize notes without accidentals', () => { @@ -96,3 +96,28 @@ describe('compose', () => { assert.equal(compose(addS('a'), addS('b'))('x'), 'xab'); }); }); + +describe('dirtify', () => { + it('should offset plain number', () => { + assert.deepStrictEqual(dirtify(61), { n: 1 }); + assert.deepStrictEqual(dirtify(60), { n: 0 }); + }); + it('should parse note string as midi number', () => { + assert.deepStrictEqual(dirtify('c4'), { n: 0 }); + assert.deepStrictEqual(dirtify('c5'), { n: 12 }); + }); + it('should use non note string as sound', () => { + assert.deepStrictEqual(dirtify('bd'), { s: 'bd' }); + }); + it('should keep objects as is', () => { + assert.deepStrictEqual(dirtify({ s: 'bd' }), { s: 'bd' }); + assert.deepStrictEqual(dirtify({ n: 7 }), { n: 7 }); + assert.deepStrictEqual(dirtify({ n: 7, room: 0.5 }), { n: 7, room: 0.5 }); + }); + it('should dirtify .value', () => { + assert.deepStrictEqual(dirtify({ value: 61 }), { n: 1 }); + assert.deepStrictEqual(dirtify({ value: 'c5' }), { n: 12 }); + assert.deepStrictEqual(dirtify({ value: 'bd' }), { s: 'bd' }); + assert.deepStrictEqual(dirtify({ value: 61, room: 0.5 }), { n: 1, room: 0.5 }); + }); +}); diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 2de7a8e6b..2a77b284f 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -83,3 +83,42 @@ export function curry(func, overload) { } return fn; } + +// this functions just makes sure non objects values are wrapped in { value } +export function objectify(value) { + if (typeof value === 'object' && !Array.isArray(value)) { + return value; // do nothing if its already an object + } + return { value }; +} + +// maybe move this to osc? +// turns value into object that is the right format superdirt osc +export function dirtify(val) { + const obj = objectify(val); + if (obj.n && typeof obj.n === 'string') { + return { ...obj, ...objectify(obj.n) }; + } + const { value, ...rest } = obj; + if (typeof value === 'undefined') { + return rest; + } + const isNumber = typeof value === 'number'; + const isMidi = typeof value === 'string' && isNote(value); + if (isNumber || isMidi) { + const numberOffset = -60; // tidal 0 = midi 60 + const n = (isNumber ? value : toMidi(value)) + numberOffset; + if (rest.n) { + console.warn(`dirtify: n is already defined as "${rest.n}", overwriting with value "${n}"`); + } + return { ...rest, n }; + } + if (typeof value === 'string') { + // not a note => should be a sound + return { ...rest, s: value }; + } + console.warn(`dirtify: ignored value "${value}"`); + // what lands here? + // throw new Error('cannot objectify: ' + value); + return {}; +}