Skip to content

Commit

Permalink
Sequencer accepts data and parses events
Browse files Browse the repository at this point in the history
  • Loading branch information
stephband committed Sep 25, 2023
1 parent 8379674 commit 7809165
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 112 deletions.
133 changes: 94 additions & 39 deletions modules/event.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@

import arg from '../../fn/modules/arg.js';
import capture from '../../fn/modules/capture.js';
import compose from '../../fn/modules/compose.js';
import get from '../../fn/modules/get.js';
import overload from '../../fn/modules/overload.js';
import Pool from '../../fn/modules/pool.js';
import remove from '../../fn/modules/remove.js';
import toType from '../../fn/modules/to-type.js';
import { bytesToSignedFloat } from '../../midi/modules/maths.js';
import { toType as toTypeMIDI } from '../../midi/modules/data.js';
import parseFloat64 from './parse/parse-float-64.js';
import parseFloat32 from './parse/parse-float-32.js';
import parseFrequency from './parse/parse-frequency.js';
import parseGain from './parse/parse-gain.js';
import parseNote from './parse/parse-note.js';

/**
Event(time, type, data...)
Expand Down Expand Up @@ -80,21 +96,11 @@ sequence
**/


import capture from '../../fn/modules/capture.js';
import compose from '../../fn/modules/compose.js';
import get from '../../fn/modules/get.js';
import overload from '../../fn/modules/overload.js';
import Pool from '../../fn/modules/pool.js';
import remove from '../../fn/modules/remove.js';
import toType from '../../fn/modules/to-type.js';
import { bytesToSignedFloat } from '../../midi/modules/maths.js';
import { toType as toTypeMIDI } from '../../midi/modules/data.js';
import parseEvent from './parse/parse-event.js';

const assign = Object.assign;
const define = Object.defineProperties;
const getData = get('data');


// ---

const pitchBendRange = 2;
Expand All @@ -120,27 +126,84 @@ function pitchToFloat(message) {
}




/**
Event(time, type, ...)
A constructor for event objects for internal use.
**/

export function Event(time, type) {
if (window.DEBUG && !isValidEvent(arguments)) {
throw new Error('Soundstage new Event() called with invalid arguments [' + Array.from(arguments).join(', ') + ']. ' + eventValidationHint(arguments));
}
const tuning = 440; /* TEMP */
const constructEventType = overload(arg(1), {
'note': function() {
// frequency, gain, duration
this[2] = parseNote(arguments[2], tuning);
this[3] = parseGain(arguments[3]);
this[4] = parseFloat64(arguments[4]);
},

const length = type === 'param' && arguments[4] === 'target' ?
6 :
(lengths[type] || lengths.default) ;
'start': function() {
// frequency, gain
this[2] = parseFrequency(arguments[2], tuning);
this[3] = parseGain(arguments[3]);
},

this[0] = time;
this[1] = type;
'stop': function() {
// frequency
this[2] = parseFrequency(arguments[2], tuning);
},

'sequence': function() {
// name, target, duration
this[2] = arguments[2];
this[3] = arguments[3];
this[4] = parseFloat64(arguments[4]);
},

'sequence-start': function() {
// name, target
this[2] = arguments[2];
this[3] = arguments[3];
},

'sequence-stop': function() {
// name
this[2] = arguments[2];
},

'param': function() {
// name, value, [curve, [duration]]
this[2] = arguments[2];
this[3] = parseFloat32(arguments[3]);
this[4] = arguments[4] || 'step';

if (arguments[4] === 'target') {
this[5] = parseFloat64(arguments[5]);
}
},

let n = 1;
while (++n < length) {
this[n] = arguments[n];
'meter': function() {
// numerator, denominator
this[2] = parseInt(arguments[2], 10);
this[3] = parseInt(arguments[3], 10);
},

'rate': function() {
// rate
this[2] = parseFloat64(arguments[2]);
},

default: function() {
this[2] = arguments[2];
}
});

export function Event(time, type) {
// Yes, WebAudio time is Float32, but this event may be a beat and I see
// little reason not to use full accuracy
this[0] = parseFloat64(time);
this[1] = type;
constructEventType.apply(this, arguments);
}

function reset() {
Expand All @@ -163,9 +226,10 @@ assign(Event, {
return new Event(...arguments);
},

from: function(data) {
return Event.of.apply(Event, data);
},
from: overload(toType, {
string: (data) => Event.parse(data),
object: (data) => Event.of.apply(Event, data)
}),

fromMIDI: overload(compose(toTypeMIDI, getData), {
pitch: function(e) {
Expand All @@ -189,19 +253,10 @@ assign(Event, {
}
}),

// "time type ..."
parse: capture(/^\s*([-\d\.e]+)\s+(\w+)\s+/, {
2: (event, captures) => {
// time
event[0] = parseFloat(captures[1]);
// type
event[1] = captures[2];
// parameters
parseEvent(event, captures);
// Convert to event object
return Event.from(event);
}
}, []),
parse: function(string) {
const data = string.split(/\s+/);
return new Event(...data);
},

stringify: function(event) {
return Array.prototype.join.call(event, ' ');
Expand Down
11 changes: 11 additions & 0 deletions modules/parse/parse-float-32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

function throwParseFloat32() {
throw new Error('Cannot parse value "' + value + '" as Float32');
}

export default function parseFloat32(value) {
const number = Math.fround(value);
return Number.isNaN(number) ?
throwParseFloat32() :
number ;
}
16 changes: 16 additions & 0 deletions modules/parse/parse-float-64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

function throwParseNumber() {
throw new Error('Cannot parse value "' + value + '" as Number');
}

/**
parseFloat()
Like JavaScript's `parseFloat`, but with a check for `NaN`.
**/

export default function parseFloat64(value) {
const number = Number(value);
return Number.isNaN(number) ?
throwParseNumber() :
number ;
}
2 changes: 1 addition & 1 deletion modules/parse/parse-frequency.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const rdigit = /^\d/;

export default overload(toType, {
'number': id,
'string': (value, tuning) => (
'string': (value, tuning = 440) => (
rdigit.test(value) ?
value.slice(-2) === 'Hz' ? parseFloat(value) :
floatToFrequency(tuning, value) :
Expand Down
1 change: 1 addition & 0 deletions modules/parse/parse-gain.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import toGain from '../../../fn/modules/to-gain.js';
import parseValue from '../../../fn/modules/parse-value.js';
import parseFloat from './parse-float-64.js';

export default parseValue({
'': parseFloat,
Expand Down
15 changes: 15 additions & 0 deletions modules/parse/parse-note.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import id from '../../../fn/modules/id.js';
import overload from '../../../fn/modules/overload.js';
import toType from '../../../fn/modules/to-type.js';
import { toNoteNumber } from '../../../midi/modules/data.js';
import parseFloat from './parse-float-64.js';

const rdigit = /^\d/;

export default overload(toType, {
'number': id,
'string': (value, tuning = 440) => (
rdigit.test(value) ? parseFloat(value) : toNoteNumber(value)
)
});
31 changes: 19 additions & 12 deletions modules/sequencer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Event from './event.js';
import Clock from './clock.js';
import FrameStream from './sequencer/frame-stream.js';
import Meter from './sequencer/meter.js';
import Sequence, { by0Float32 } from './sequencer/sequence.js';
import Sequence from './sequencer/sequence.js';

import { print } from './print.js';
import { getDejitterTime } from './context.js';
Expand All @@ -24,6 +24,7 @@ import parseEvents from './parse/parse-events.js';
const assign = Object.assign;
const create = Object.create;
const define = Object.defineProperties;
const by0 = by(get(0));


/**
Expand Down Expand Up @@ -56,26 +57,32 @@ Sequencer()
```
**/

function sanitiseEvents(sequence) {
sequence.sequences && sequence.sequences.forEach(sanitiseEvents);
sequence.events = sequence.events
.map((data) => typeof data === 'string' ? Event.parse(data) : Event.from(data))
.sort(by0Float32);
function parseSequence(data) {
return {
id: data.id,
label: data.label,
events: data.events.map(Event.from).sort(by0),
sequences: data.sequences && data.sequences.map(parseSequence)
};
}

export default function Sequencer(transport, output, events = [], sequences = []) {
export default function Sequencer(transport, output, data) {
// .context
// .start()
// .stop()
Playable.call(this, transport.context);

this.transport = transport;
this.events = typeof events === 'string' ?
parseEvents(events).sort(by0Float32) :
events.map((data) => typeof data === 'string' ? Event.parse(data) : Event.from(data)).sort(by0Float32) ;
this.sequences = sequences;
this.rate = transport.outputs.rate.offset;

this.events = data.events ?
data.events.map(Event.from).sort(by0) :
[] ;

this.sequences = data.sequences ?
data.sequences.map(parseSequence) :
[] ;

const privates = Privates(this);
privates.beat = 0;
privates.output = output;
Expand Down Expand Up @@ -137,7 +144,7 @@ assign(Sequencer.prototype, Meter.prototype, {
.pipe(new Sequence(this, this.events, this.sequences, 'root'))
// Error-check and consume output events
.map(overload(get(1), {
// Do nothing, Sequencer doesn't respond to "start"
// Do nothing, Sequencer itself doesn't respond to "start"
'start': (event) => event.release(),

// But perhaps it can respond to "stop", why not - ooo, because
Expand Down
Loading

0 comments on commit 7809165

Please sign in to comment.