From 8a7e49780a13cbfabb43a9a5089a050bc69effaf Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 27 Feb 2022 21:24:23 +0100 Subject: [PATCH] added _asNumber + interprete numbers as midi --- repl/src/useRepl.ts | 9 +++++++-- strudel.mjs | 30 ++++++++++++++++++++++++++---- util.mjs | 11 +++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 util.mjs diff --git a/repl/src/useRepl.ts b/repl/src/useRepl.ts index af477c9f..22d98515 100644 --- a/repl/src/useRepl.ts +++ b/repl/src/useRepl.ts @@ -1,5 +1,6 @@ import { useCallback, useState, useMemo } from 'react'; import { isNote } from 'tone'; +import { fromMidi } from '../../util.mjs'; import { evaluate } from './evaluate'; import type { Pattern } from './types'; import useCycle from './useCycle'; @@ -63,8 +64,12 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any) onEvent?.(event); const { onTrigger } = event.context; if (!onTrigger) { - const note = event.value; - if (!isNote(note)) { + let note = event.value; + // if value is number => interpret as midi number... + // TODO: what if value is meant as frequency? => check context + if (typeof event.value === 'number') { + note = fromMidi(event.value); + } else if (!isNote(note)) { throw new Error('not a note: ' + note); } if (defaultSynth) { diff --git a/strudel.mjs b/strudel.mjs index 505cdcb4..45ec1c31 100644 --- a/strudel.mjs +++ b/strudel.mjs @@ -1,5 +1,6 @@ import Fraction from 'fraction.js' import { compose } from 'ramda'; // will remove this as soon as compose is implemented here +import { isNote, toMidi } from './util.mjs'; // Removes 'None' values from given list const removeUndefineds = xs => xs.filter(x => x != undefined) @@ -466,20 +467,41 @@ class Pattern { return this.fmap(func).appLeft(reify(other)) } + _asNumber() { + return this._withEvent(event => { + const asNumber = Number(event.value) + if(!isNaN(asNumber)) { + return asNumber; + } + const specialValue = { + e: Math.E, + pi: Math.PI + }[event.value]; + if(typeof specialValue !== 'undefined') { + return specialValue; + } + if(isNote(event.value)) { + // set context type to midi to let the player know its meant as midi number and not as frequency + return new Hap(event.whole, event.part, toMidi(event.value), { ...event.context, type: 'midi' }); + } + throw new Error('cannot parse as number: "' + event.value + '"'); + }) + } + add(other) { - return this._opleft(other, a => b => a + b) + return this._asNumber()._opleft(other, a => b => a + b) } sub(other) { - return this._opleft(other, a => b => a - b) + return this._asNumber()._opleft(other, a => b => a - b) } mul(other) { - return this._opleft(other, a => b => a * b) + return this._asNumber()._opleft(other, a => b => a * b) } div(other) { - return this._opleft(other, a => b => a / b) + return this._asNumber()._opleft(other, a => b => a / b) } union(other) { diff --git a/util.mjs b/util.mjs new file mode 100644 index 00000000..ad9f58d6 --- /dev/null +++ b/util.mjs @@ -0,0 +1,11 @@ +export const isNote = (name) => /^[a-gA-G][#b]?[0-9]$/.test(name); +export const tokenizeNote = (note) => note.match(/^([a-gA-G])([#b])?([0-9])?$/).slice(1); +export const toMidi = (note) => { + const [pc, acc, oct] = tokenizeNote(note); + const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc]; + const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1 }[char], 0) || 0; + return (Number(oct) + 1) * 12 + chroma + offset; +}; +export const fromMidi = (n) => { + return Math.pow(2, (n - 69) / 12) * 440; +};