Merge pull request #83 from Bubobubobubobubo/clockwork
Ahead-of-time scheduling (part I)
This commit is contained in:
@ -116,6 +116,7 @@ export class UserAPI {
|
|||||||
? code
|
? code
|
||||||
: (this.app.selectedExample as string);
|
: (this.app.selectedExample as string);
|
||||||
}
|
}
|
||||||
|
this.stop();
|
||||||
this.play();
|
this.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,7 +127,7 @@ export class UserAPI {
|
|||||||
current_universe.example.candidate! = "";
|
current_universe.example.candidate! = "";
|
||||||
current_universe.example.committed! = "";
|
current_universe.example.committed! = "";
|
||||||
}
|
}
|
||||||
this.pause();
|
this.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
_playDocExampleOnce = (code?: string) => {
|
_playDocExampleOnce = (code?: string) => {
|
||||||
@ -135,6 +136,7 @@ export class UserAPI {
|
|||||||
current_universe.example.candidate! = "";
|
current_universe.example.candidate! = "";
|
||||||
current_universe.example.committed! = "";
|
current_universe.example.committed! = "";
|
||||||
}
|
}
|
||||||
|
this.stop();
|
||||||
this.play();
|
this.play();
|
||||||
this.app.exampleIsPlaying = true;
|
this.app.exampleIsPlaying = true;
|
||||||
evaluateOnce(this.app, code as string);
|
evaluateOnce(this.app, code as string);
|
||||||
@ -207,13 +209,11 @@ export class UserAPI {
|
|||||||
|
|
||||||
public pause = (): void => {
|
public pause = (): void => {
|
||||||
this.app.setButtonHighlighting("pause", true);
|
this.app.setButtonHighlighting("pause", true);
|
||||||
this.MidiConnection.sendStopMessage();
|
|
||||||
this.app.clock.pause();
|
this.app.clock.pause();
|
||||||
};
|
};
|
||||||
|
|
||||||
public stop = (): void => {
|
public stop = (): void => {
|
||||||
this.app.setButtonHighlighting("stop", true);
|
this.app.setButtonHighlighting("stop", true);
|
||||||
this.MidiConnection.sendStopMessage();
|
|
||||||
this.app.clock.stop();
|
this.app.clock.stop();
|
||||||
};
|
};
|
||||||
silence = this.stop;
|
silence = this.stop;
|
||||||
|
|||||||
54
src/Clock.ts
54
src/Clock.ts
@ -29,24 +29,38 @@ export class Clock {
|
|||||||
* @param time_position - The current time position
|
* @param time_position - The current time position
|
||||||
* @param ppqn - The pulses per quarter note
|
* @param ppqn - The pulses per quarter note
|
||||||
* @param tick - The current tick since origin
|
* @param tick - The current tick since origin
|
||||||
|
* @param running - Is the clock running?
|
||||||
|
* @param lastPauseTime - The last time the clock was paused
|
||||||
|
* @param lastPlayPressTime - The last time the clock was started
|
||||||
|
* @param totalPauseTime - The total time the clock has been paused / stopped
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ctx: AudioContext;
|
ctx: AudioContext;
|
||||||
|
logicalTime: number;
|
||||||
transportNode: TransportNode | null;
|
transportNode: TransportNode | null;
|
||||||
private _bpm: number;
|
private _bpm: number;
|
||||||
time_signature: number[];
|
time_signature: number[];
|
||||||
time_position: TimePosition;
|
time_position: TimePosition;
|
||||||
private _ppqn: number;
|
private _ppqn: number;
|
||||||
tick: number;
|
tick: number;
|
||||||
|
running: boolean;
|
||||||
|
lastPauseTime: number;
|
||||||
|
lastPlayPressTime: number;
|
||||||
|
totalPauseTime: number;
|
||||||
|
|
||||||
constructor(public app: Editor, ctx: AudioContext) {
|
constructor(public app: Editor, ctx: AudioContext) {
|
||||||
this.time_position = { bar: -1, beat: -1, pulse: -1 };
|
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
||||||
this.time_signature = [4, 4];
|
this.time_signature = [4, 4];
|
||||||
this.tick = -1;
|
this.logicalTime = 0;
|
||||||
|
this.tick = 0;
|
||||||
this._bpm = 120;
|
this._bpm = 120;
|
||||||
this._ppqn = 48;
|
this._ppqn = 48;
|
||||||
this.transportNode = null;
|
this.transportNode = null;
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
|
this.running = true;
|
||||||
|
this.lastPauseTime = 0;
|
||||||
|
this.lastPlayPressTime = 0;
|
||||||
|
this.totalPauseTime = 0;
|
||||||
ctx.audioWorklet
|
ctx.audioWorklet
|
||||||
.addModule(TransportProcessor)
|
.addModule(TransportProcessor)
|
||||||
.then((e) => {
|
.then((e) => {
|
||||||
@ -122,6 +136,13 @@ export class Clock {
|
|||||||
return 60 / this.bpm / this.ppqn;
|
return 60 / this.bpm / this.ppqn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public pulse_duration_at_bpm(bpm: number = this.bpm): number {
|
||||||
|
/**
|
||||||
|
* Returns the duration of a pulse in seconds at a specific bpm.
|
||||||
|
*/
|
||||||
|
return 60 / bpm / this.ppqn;
|
||||||
|
}
|
||||||
|
|
||||||
get bpm(): number {
|
get bpm(): number {
|
||||||
return this._bpm;
|
return this._bpm;
|
||||||
}
|
}
|
||||||
@ -132,8 +153,9 @@ export class Clock {
|
|||||||
|
|
||||||
set bpm(bpm: number) {
|
set bpm(bpm: number) {
|
||||||
if (bpm > 0 && this._bpm !== bpm) {
|
if (bpm > 0 && this._bpm !== bpm) {
|
||||||
this._bpm = bpm;
|
|
||||||
this.transportNode?.setBPM(bpm);
|
this.transportNode?.setBPM(bpm);
|
||||||
|
this._bpm = bpm;
|
||||||
|
this.logicalTime = this.realTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +163,14 @@ export class Clock {
|
|||||||
return this._ppqn;
|
return this._ppqn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get realTime(): number {
|
||||||
|
return this.app.audioContext.currentTime - this.totalPauseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
get deviation(): number {
|
||||||
|
return Math.abs(this.logicalTime - this.realTime);
|
||||||
|
}
|
||||||
|
|
||||||
set ppqn(ppqn: number) {
|
set ppqn(ppqn: number) {
|
||||||
if (ppqn > 0 && this._ppqn !== ppqn) {
|
if (ppqn > 0 && this._ppqn !== ppqn) {
|
||||||
this._ppqn = ppqn;
|
this._ppqn = ppqn;
|
||||||
@ -148,6 +178,11 @@ export class Clock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public incrementTick(bpm: number) {
|
||||||
|
this.tick++;
|
||||||
|
this.logicalTime += this.pulse_duration_at_bpm(bpm);
|
||||||
|
}
|
||||||
|
|
||||||
public nextTickFrom(time: number, nudge: number): number {
|
public nextTickFrom(time: number, nudge: number): number {
|
||||||
/**
|
/**
|
||||||
* Compute the time remaining before the next clock tick.
|
* Compute the time remaining before the next clock tick.
|
||||||
@ -180,7 +215,10 @@ export class Clock {
|
|||||||
* @remark also sends a MIDI message if a port is declared
|
* @remark also sends a MIDI message if a port is declared
|
||||||
*/
|
*/
|
||||||
this.app.audioContext.resume();
|
this.app.audioContext.resume();
|
||||||
|
this.running = true;
|
||||||
this.app.api.MidiConnection.sendStartMessage();
|
this.app.api.MidiConnection.sendStartMessage();
|
||||||
|
this.lastPlayPressTime = this.app.audioContext.currentTime;
|
||||||
|
this.totalPauseTime += (this.lastPlayPressTime - this.lastPauseTime);
|
||||||
this.transportNode?.start();
|
this.transportNode?.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +228,11 @@ export class Clock {
|
|||||||
*
|
*
|
||||||
* @remark also sends a MIDI message if a port is declared
|
* @remark also sends a MIDI message if a port is declared
|
||||||
*/
|
*/
|
||||||
|
this.running = false;
|
||||||
this.transportNode?.pause();
|
this.transportNode?.pause();
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
this.app.api.MidiConnection.sendStopMessage();
|
||||||
|
this.lastPauseTime = this.app.audioContext.currentTime;
|
||||||
|
this.logicalTime = this.realTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
@ -200,8 +241,11 @@ export class Clock {
|
|||||||
*
|
*
|
||||||
* @remark also sends a MIDI message if a port is declared
|
* @remark also sends a MIDI message if a port is declared
|
||||||
*/
|
*/
|
||||||
this.app.clock.tick = -1;
|
this.running = false;
|
||||||
this.time_position = { bar: -1, beat: -1, pulse: -1 };
|
this.tick = 0;
|
||||||
|
this.lastPauseTime = this.app.audioContext.currentTime;
|
||||||
|
this.logicalTime = this.realTime;
|
||||||
|
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
this.app.api.MidiConnection.sendStopMessage();
|
||||||
this.transportNode?.stop();
|
this.transportNode?.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,31 +12,39 @@ export class TransportNode extends AudioWorkletNode {
|
|||||||
|
|
||||||
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
|
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
|
||||||
handleMessage = (message) => {
|
handleMessage = (message) => {
|
||||||
if (message.data && message.data.type === "bang") {
|
if(message.data) {
|
||||||
if (this.app.settings.send_clock)
|
if (message.data.type === "bang") {
|
||||||
|
if(this.app.clock.running) {
|
||||||
|
if (this.app.settings.send_clock) {
|
||||||
this.app.api.MidiConnection.sendMidiClock();
|
this.app.api.MidiConnection.sendMidiClock();
|
||||||
this.app.clock.tick++;
|
}
|
||||||
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
|
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
|
||||||
this.app.clock.tick
|
this.app.clock.tick
|
||||||
);
|
);
|
||||||
this.app.clock.time_position = futureTimeStamp;
|
this.app.clock.time_position = futureTimeStamp;
|
||||||
this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${
|
this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${futureTimeStamp.beat + 1
|
||||||
futureTimeStamp.beat + 1
|
|
||||||
}:${zeroPad(futureTimeStamp.pulse, 2)} / ${this.app.clock.bpm}`;
|
}:${zeroPad(futureTimeStamp.pulse, 2)} / ${this.app.clock.bpm}`;
|
||||||
if (this.app.exampleIsPlaying) {
|
if (this.app.exampleIsPlaying) {
|
||||||
tryEvaluate(this.app, this.app.example_buffer);
|
tryEvaluate(this.app, this.app.example_buffer);
|
||||||
} else {
|
} else {
|
||||||
tryEvaluate(this.app, this.app.global_buffer);
|
tryEvaluate(this.app, this.app.global_buffer);
|
||||||
}
|
}
|
||||||
|
this.app.clock.incrementTick(message.data.bpm);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.port.postMessage("start");
|
this.port.postMessage({ type: "start" });
|
||||||
}
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
this.port.postMessage("pause");
|
this.port.postMessage({ type: "pause" });
|
||||||
|
}
|
||||||
|
|
||||||
|
resume() {
|
||||||
|
this.port.postMessage({ type: "resume" });
|
||||||
}
|
}
|
||||||
|
|
||||||
setBPM(bpm) {
|
setBPM(bpm) {
|
||||||
@ -52,6 +60,6 @@ export class TransportNode extends AudioWorkletNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.port.postMessage("stop");
|
this.port.postMessage({type: "stop" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,20 +14,19 @@ class TransportProcessor extends AudioWorkletProcessor {
|
|||||||
handleMessage = (message) => {
|
handleMessage = (message) => {
|
||||||
if (message.data && message.data.type === "ping") {
|
if (message.data && message.data.type === "ping") {
|
||||||
this.port.postMessage(message.data);
|
this.port.postMessage(message.data);
|
||||||
} else if (message.data === "start") {
|
} else if (message.data.type === "start") {
|
||||||
this.started = true;
|
this.started = true;
|
||||||
} else if (message.data === "pause") {
|
} else if (message.data.type === "pause") {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
} else if (message.data === "stop") {
|
} else if (message.data.type === "stop") {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
} else if (message.data.type === 'bpm') {
|
} else if (message.data.type === 'bpm') {
|
||||||
this.bpm = message.data.value;
|
this.bpm = message.data.value;
|
||||||
this.currentPulsePosition = 0;
|
this.currentPulsePosition = currentTime;
|
||||||
} else if (message.data.type === 'ppqn') {
|
} else if (message.data.type === 'ppqn') {
|
||||||
this.ppqn = message.data.value;
|
this.ppqn = message.data.value;
|
||||||
this.currentPulsePosition = 0;
|
|
||||||
} else if (message.data.type === 'nudge') {
|
} else if (message.data.type === 'nudge') {
|
||||||
this.nudge = message.data.value
|
this.nudge = message.data.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +37,7 @@ class TransportProcessor extends AudioWorkletProcessor {
|
|||||||
const currentPulsePosition = Math.ceil(beatNumber * this.ppqn);
|
const currentPulsePosition = Math.ceil(beatNumber * this.ppqn);
|
||||||
if (currentPulsePosition > this.currentPulsePosition) {
|
if (currentPulsePosition > this.currentPulsePosition) {
|
||||||
this.currentPulsePosition = currentPulsePosition;
|
this.currentPulsePosition = currentPulsePosition;
|
||||||
this.port.postMessage({ type: "bang" });
|
this.port.postMessage({ type: "bang", bpm: this.bpm });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -6,9 +6,15 @@
|
|||||||
* @returns {Record<string, any>[]} Array of objects
|
* @returns {Record<string, any>[]} Array of objects
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function objectWithArraysToArrayOfObjects(input: Record<string, any>, ignoredKeys: string[]): Record<string, any>[] {
|
export function objectWithArraysToArrayOfObjects(input: Record<string, any>, arraysToArrays: string[]): Record<string, any>[] {
|
||||||
ignoredKeys = ignoredKeys.map((k) => Array.isArray(input[k]) ? undefined : k).filter((k) => k !== undefined) as string[];
|
arraysToArrays.forEach((k) => {
|
||||||
const keys = Object.keys(input).filter((k) => !ignoredKeys.includes(k));
|
// Transform single array to array of arrays and keep array of arrays as is
|
||||||
|
if (Array.isArray(input[k]) && !Array.isArray(input[k][0])) {
|
||||||
|
input[k] = [input[k]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const keys = Object.keys(input);
|
||||||
|
|
||||||
const maxLength = Math.max(
|
const maxLength = Math.max(
|
||||||
...keys.map((k) =>
|
...keys.map((k) =>
|
||||||
Array.isArray(input[k]) ? (input[k] as any[]).length : 1
|
Array.isArray(input[k]) ? (input[k] as any[]).length : 1
|
||||||
@ -20,16 +26,12 @@ export function objectWithArraysToArrayOfObjects(input: Record<string, any>, ign
|
|||||||
for (let i = 0; i < maxLength; i++) {
|
for (let i = 0; i < maxLength; i++) {
|
||||||
const event: Record<string, any> = {};
|
const event: Record<string, any> = {};
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
if (ignoredKeys.includes(k)) {
|
|
||||||
event[k] = input[k];
|
|
||||||
} else {
|
|
||||||
if (Array.isArray(input[k])) {
|
if (Array.isArray(input[k])) {
|
||||||
event[k] = (input[k] as any[])[i % (input[k] as any[]).length];
|
event[k] = (input[k] as any[])[i % (input[k] as any[]).length];
|
||||||
} else {
|
} else {
|
||||||
event[k] = input[k];
|
event[k] = input[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
output.push(event);
|
output.push(event);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { type Editor } from "../main";
|
import { type Editor } from "../main";
|
||||||
import { AudibleEvent } from "./AbstractEvents";
|
import { AudibleEvent } from "./AbstractEvents";
|
||||||
import { filterObject, arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects } from "../Utils/Generic";
|
import {
|
||||||
|
filterObject,
|
||||||
|
arrayOfObjectsToObjectWithArrays,
|
||||||
|
objectWithArraysToArrayOfObjects,
|
||||||
|
} from "../Utils/Generic";
|
||||||
import {
|
import {
|
||||||
chord as parseChord,
|
chord as parseChord,
|
||||||
midiToFreq,
|
midiToFreq,
|
||||||
@ -277,7 +281,6 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
constructor(sound: string | string[] | SoundParams, public app: Editor) {
|
constructor(sound: string | string[] | SoundParams, public app: Editor) {
|
||||||
super(app);
|
super(app);
|
||||||
this.nudge = app.dough_nudge / 100;
|
this.nudge = app.dough_nudge / 100;
|
||||||
@ -296,11 +299,13 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
this.values = this.processSound(sound);
|
this.values = this.processSound(sound);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processSound = (sound: string | string[] | SoundParams | SoundParams[]): SoundParams => {
|
private processSound = (
|
||||||
if (Array.isArray(sound) && typeof sound[0] === 'string') {
|
sound: string | string[] | SoundParams | SoundParams[]
|
||||||
|
): SoundParams => {
|
||||||
|
if (Array.isArray(sound) && typeof sound[0] === "string") {
|
||||||
const s: string[] = [];
|
const s: string[] = [];
|
||||||
const n: number[] = [];
|
const n: number[] = [];
|
||||||
sound.forEach(str => {
|
sound.forEach((str) => {
|
||||||
const parts = (str as string).split(":");
|
const parts = (str as string).split(":");
|
||||||
s.push(parts[0]);
|
s.push(parts[0]);
|
||||||
if (parts[1]) {
|
if (parts[1]) {
|
||||||
@ -311,13 +316,13 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
s,
|
s,
|
||||||
n: n.length > 0 ? n : undefined,
|
n: n.length > 0 ? n : undefined,
|
||||||
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
||||||
analyze: true
|
analyze: true,
|
||||||
};
|
};
|
||||||
} else if (typeof sound === 'object') {
|
} else if (typeof sound === "object") {
|
||||||
const validatedObj: SoundParams = {
|
const validatedObj: SoundParams = {
|
||||||
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
||||||
analyze: true,
|
analyze: true,
|
||||||
...sound as Partial<SoundParams>
|
...(sound as Partial<SoundParams>),
|
||||||
};
|
};
|
||||||
return validatedObj;
|
return validatedObj;
|
||||||
} else {
|
} else {
|
||||||
@ -329,15 +334,18 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
s,
|
s,
|
||||||
n,
|
n,
|
||||||
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
dur: this.app.clock.convertPulseToSecond(this.app.clock.ppqn),
|
||||||
analyze: true
|
analyze: true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return { s: sound, dur: 0.5, analyze: true };
|
return { s: sound, dur: 0.5, analyze: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private updateValue<T>(key: string, value: T | T[] | SoundParams[] | null): this {
|
private updateValue<T>(
|
||||||
|
key: string,
|
||||||
|
value: T | T[] | SoundParams[] | null
|
||||||
|
): this {
|
||||||
if (value == null) return this;
|
if (value == null) return this;
|
||||||
this.values[key] = value;
|
this.values[key] = value;
|
||||||
return this;
|
return this;
|
||||||
@ -358,15 +366,21 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
update = (): void => {
|
update = (): void => {
|
||||||
const filteredValues = filterObject(this.values, ["key", "pitch", "parsedScale", "octave"]);
|
const filteredValues = filterObject(this.values, [
|
||||||
const events = objectWithArraysToArrayOfObjects(filteredValues,["parsedScale"]);
|
"key",
|
||||||
|
"pitch",
|
||||||
|
"parsedScale",
|
||||||
|
"octave",
|
||||||
|
]);
|
||||||
|
const events = objectWithArraysToArrayOfObjects(filteredValues, [
|
||||||
|
"parsedScale",
|
||||||
|
]);
|
||||||
events.forEach((event) => {
|
events.forEach((event) => {
|
||||||
const [note, _] = noteFromPc(
|
const [note, _] = noteFromPc(
|
||||||
event.key as number || "C4",
|
(event.key as number) || "C4",
|
||||||
event.pitch as number || 0,
|
(event.pitch as number) || 0,
|
||||||
event.parsedScale as number[] || event.scale || "MAJOR",
|
(event.parsedScale as number[]) || event.scale || "MAJOR",
|
||||||
event.octave as number || 0
|
(event.octave as number) || 0
|
||||||
);
|
);
|
||||||
event.note = note;
|
event.note = note;
|
||||||
event.freq = midiToFreq(note);
|
event.freq = midiToFreq(note);
|
||||||
@ -411,13 +425,11 @@ export class SoundEvent extends AudibleEvent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
out = (): void => {
|
out = (): void => {
|
||||||
const events = objectWithArraysToArrayOfObjects(this.values,["parsedScale"]);
|
const events = objectWithArraysToArrayOfObjects(this.values, [
|
||||||
|
"parsedScale",
|
||||||
|
]);
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
superdough(
|
superdough(event, this.nudge + this.app.clock.deviation, event.dur);
|
||||||
event,
|
|
||||||
this.nudge,
|
|
||||||
event.dur
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,12 @@ export class Player extends Event {
|
|||||||
options: InputOptions = {};
|
options: InputOptions = {};
|
||||||
skipIndex = 0;
|
skipIndex = 0;
|
||||||
|
|
||||||
constructor(input: string, options: InputOptions, public app: Editor, zid: string = "") {
|
constructor(
|
||||||
|
input: string,
|
||||||
|
options: InputOptions,
|
||||||
|
public app: Editor,
|
||||||
|
zid: string = ""
|
||||||
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@ -109,15 +114,17 @@ export class Player extends Event {
|
|||||||
this.app.api.resetAllFromCache();
|
this.app.api.resetAllFromCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
const patternIsStarting = (this.notStarted() &&
|
const patternIsStarting =
|
||||||
|
this.notStarted() &&
|
||||||
(this.pulse() === 0 || this.origin() >= this.nextBeatInTicks()) &&
|
(this.pulse() === 0 || this.origin() >= this.nextBeatInTicks()) &&
|
||||||
this.origin() >= this.waitTime);
|
this.origin() >= this.waitTime;
|
||||||
|
|
||||||
const timeToPlayNext = (this.current &&
|
const timeToPlayNext =
|
||||||
|
this.current &&
|
||||||
this.pulseToSecond(this.origin()) >=
|
this.pulseToSecond(this.origin()) >=
|
||||||
this.pulseToSecond(this.lastCallTime) +
|
this.pulseToSecond(this.lastCallTime) +
|
||||||
this.pulseToSecond(this.current.duration * 4 * this.app.clock.ppqn) &&
|
this.pulseToSecond(this.current.duration * 4 * this.app.clock.ppqn) &&
|
||||||
this.origin() >= this.waitTime);
|
this.origin() >= this.waitTime;
|
||||||
|
|
||||||
// If pattern is starting or it's time to play next event
|
// If pattern is starting or it's time to play next event
|
||||||
const areWeThereYet = patternIsStarting || timeToPlayNext;
|
const areWeThereYet = patternIsStarting || timeToPlayNext;
|
||||||
@ -142,7 +149,9 @@ export class Player extends Event {
|
|||||||
sound(name?: string) {
|
sound(name?: string) {
|
||||||
if (this.areWeThereYet()) {
|
if (this.areWeThereYet()) {
|
||||||
const event = this.next() as Pitch | Chord | ZRest;
|
const event = this.next() as Pitch | Chord | ZRest;
|
||||||
const noteLengthInSeconds = this.app.clock.convertPulseToSecond(event.duration*4*this.app.clock.ppqn);
|
const noteLengthInSeconds = this.app.clock.convertPulseToSecond(
|
||||||
|
event.duration * 4 * this.app.clock.ppqn
|
||||||
|
);
|
||||||
if (event instanceof Pitch) {
|
if (event instanceof Pitch) {
|
||||||
const obj = event.getExisting(
|
const obj = event.getExisting(
|
||||||
"freq",
|
"freq",
|
||||||
@ -171,7 +180,10 @@ export class Player extends Event {
|
|||||||
}) as SoundParams[];
|
}) as SoundParams[];
|
||||||
const add = { dur: noteLengthInSeconds } as SoundParams;
|
const add = { dur: noteLengthInSeconds } as SoundParams;
|
||||||
if (name) add.s = name;
|
if (name) add.s = name;
|
||||||
let sound = arrayOfObjectsToObjectWithArrays(pitches,add) as SoundParams;
|
let sound = arrayOfObjectsToObjectWithArrays(
|
||||||
|
pitches,
|
||||||
|
add
|
||||||
|
) as SoundParams;
|
||||||
return new SoundEvent(sound, this.app);
|
return new SoundEvent(sound, this.app);
|
||||||
} else if (event instanceof ZRest) {
|
} else if (event instanceof ZRest) {
|
||||||
return RestEvent.createRestProxy(event.duration, this.app);
|
return RestEvent.createRestProxy(event.duration, this.app);
|
||||||
@ -191,7 +203,7 @@ export class Player extends Event {
|
|||||||
"key",
|
"key",
|
||||||
"scale",
|
"scale",
|
||||||
"octave",
|
"octave",
|
||||||
"parsedScale",
|
"parsedScale"
|
||||||
) as MidiParams;
|
) as MidiParams;
|
||||||
if (event instanceof Pitch) {
|
if (event instanceof Pitch) {
|
||||||
if (event.soundIndex) obj.channel = event.soundIndex as number;
|
if (event.soundIndex) obj.channel = event.soundIndex as number;
|
||||||
@ -236,7 +248,7 @@ export class Player extends Event {
|
|||||||
this.ziffers.invert(n);
|
this.ziffers.invert(n);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
};
|
||||||
|
|
||||||
retrograde() {
|
retrograde() {
|
||||||
if (this.atTheBeginning()) this.ziffers.retrograde();
|
if (this.atTheBeginning()) this.ziffers.retrograde();
|
||||||
|
|||||||
Reference in New Issue
Block a user