Merge pull request #95 from Bubobubobubobubo/topas

Experimental Workshop Branch
This commit is contained in:
Raphaël Forment
2023-12-02 10:48:13 +01:00
committed by GitHub
12 changed files with 208 additions and 316 deletions

View File

@ -43,6 +43,7 @@
"unique-names-generator": "^4.7.1",
"vite-plugin-markdown": "^2.1.0",
"zifferjs": "^0.0.44",
"zyklus": "^0.1.4",
"zzfx": "^1.2.0"
}
}

View File

@ -1298,7 +1298,7 @@ export class UserAPI {
const results: boolean[] = nArray.map(
(value) =>
(this.app.clock.pulses_since_origin - Math.floor(nudge * this.ppqn())) %
Math.floor(value * this.ppqn()) ===
Math.floor(value * this.ppqn()) ===
0,
);
return results.some((value) => value === true);
@ -1318,7 +1318,7 @@ export class UserAPI {
const results: boolean[] = nArray.map(
(value) =>
(this.app.clock.pulses_since_origin - nudgeInPulses) %
Math.floor(value * barLength) ===
Math.floor(value * barLength) ===
0,
);
return results.some((value) => value === true);
@ -1581,7 +1581,9 @@ export class UserAPI {
// Low Frequency Oscillators
// =============================================================
line = (start: number, end: number, step: number = 1): number[] => {
public range = (v: number, a: number, b: number): number => v * (b - a) + a;
public line = (start: number, end: number, step: number = 1): number[] => {
/**
* Returns an array of values between start and end, with a given step.
*
@ -1603,7 +1605,11 @@ export class UserAPI {
return result;
};
sine = (freq: number = 1, times: number = 1, offset: number = 0): number => {
public sine = (
freq: number = 1,
times: number = 1,
offset: number = 0,
): number => {
/**
* Returns a sine wave between -1 and 1.
*
@ -1617,7 +1623,11 @@ export class UserAPI {
);
};
usine = (freq: number = 1, times: number = 1, offset: number = 0): number => {
public usine = (
freq: number = 1,
times: number = 1,
offset: number = 0,
): number => {
/**
* Returns a sine wave between 0 and 1.
*
@ -1780,18 +1790,6 @@ export class UserAPI {
return sum / values.length;
};
public range = (
inputY: number,
yMin: number,
yMax: number,
xMin: number,
xMax: number,
): number => {
const percent = (inputY - yMin) / (yMax - yMin);
const outputX = percent * (xMax - xMin) + xMin;
return outputX;
};
limit = (value: number, min: number, max: number): number => {
/**
* Limits a value between a minimum and a maximum.
@ -1918,7 +1916,7 @@ export class UserAPI {
// =============================================================
register = (name: string, operation: EventOperation<AbstractEvent>): void => {
AbstractEvent.prototype[name] = function (
AbstractEvent.prototype[name] = function(
this: AbstractEvent,
...args: any[]
) {
@ -2101,19 +2099,6 @@ export class UserAPI {
// Transport functions
// =============================================================
public nudge = (nudge?: number): number => {
/**
* Sets or returns the current clock nudge.
*
* @param nudge - [optional] the nudge to set
* @returns The current nudge
*/
if (nudge) {
this.app.clock.nudge = nudge;
}
return this.app.clock.nudge;
};
public tempo = (n?: number): number => {
/**
* Sets or returns the current bpm.

View File

@ -1,7 +1,11 @@
// @ts-ignore
import { TransportNode } from "./TransportNode";
import TransportProcessor from "./TransportProcessor?worker&url";
import { Editor } from "./main";
import { tryEvaluate } from "./Evaluator";
// @ts-ignore
import { getAudioContext } from "superdough";
// @ts-ignore
import "zyklus";
const zeroPad = (num: number, places: number) =>
String(num).padStart(places, "0");
export interface TimePosition {
/**
@ -18,35 +22,29 @@ export interface TimePosition {
export class Clock {
/**
* The Clock Class is responsible for keeping track of the current time.
* It is also responsible for starting and stopping the Clock TransportNode.
*
* @param app - The main application instance
* @param ctx - The current AudioContext used by app
* @param transportNode - The TransportNode helper
* @param bpm - The current beats per minute value
* @param time_signature - The time signature
* @param time_position - The current time position
* @param ppqn - The pulses per quarter note
* @param tick - The current tick since origin
* @param app - main application instance
* @param clock - zyklus clock
* @param ctx - current AudioContext used by app
* @param bpm - current beats per minute value
* @param time_signature - time signature
* @param time_position - current time position
* @param ppqn - pulses per quarter note
* @param tick - 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
*/
private _bpm: number;
private _ppqn: number;
clock: any;
ctx: AudioContext;
logicalTime: number;
transportNode: TransportNode | null;
private _bpm: number;
time_signature: number[];
time_position: TimePosition;
private _ppqn: number;
tick: number;
running: boolean;
lastPauseTime: number;
lastPlayPressTime: number;
totalPauseTime: number;
timeviewer: HTMLElement;
deadline: number;
constructor(
public app: Editor,
@ -58,31 +56,58 @@ export class Clock {
this.tick = 0;
this._bpm = 120;
this._ppqn = 48;
this.transportNode = null;
this.ctx = ctx;
this.running = true;
this.lastPauseTime = 0;
this.lastPlayPressTime = 0;
this.totalPauseTime = 0;
ctx.audioWorklet
.addModule(TransportProcessor)
.then((e) => {
this.transportNode = new TransportNode(ctx, {}, this.app);
this.transportNode.connect(ctx.destination);
return e;
})
.catch((e) => {
console.log("Error loading TransportProcessor.js:", e);
});
this.deadline = 0;
this.timeviewer = document.getElementById("timeviewer")!;
this.clock = getAudioContext().createClock(
this.clockCallback,
this.pulse_duration,
);
}
// @ts-ignore
clockCallback = (time: number, duration: number, tick: number) => {
/**
* Callback function for the zyklus clock. Updates the clock info and sends a
* MIDI clock message if the setting is enabled. Also evaluates the global buffer.
*
* @param time - precise AudioContext time when the tick should happen
* @param duration - seconds between each tick
* @param tick - count of the current tick
*/
let deadline = time - getAudioContext().currentTime;
this.deadline = deadline;
this.tick = tick;
if (this.app.clock.running) {
if (this.app.settings.send_clock) {
this.app.api.MidiConnection.sendMidiClock();
}
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
this.app.clock.tick,
);
this.app.clock.time_position = futureTimeStamp;
if (futureTimeStamp.pulse % this.app.clock.ppqn == 0) {
this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${futureTimeStamp.beat + 1
} / ${this.app.clock.bpm}`;
}
if (this.app.exampleIsPlaying) {
tryEvaluate(this.app, this.app.example_buffer);
} else {
tryEvaluate(this.app, this.app.global_buffer);
}
}
// Implement TransportNode clock callback and update clock info with it
};
convertTicksToTimeposition(ticks: number): TimePosition {
/**
* Converts ticks to a TimePosition object.
* @param ticks The number of ticks to convert.
* @returns The TimePosition object representing the converted ticks.
* Converts ticks to a time position.
*
* @param ticks - ticks to convert
* @returns TimePosition
*/
const beatsPerBar = this.app.clock.time_signature[0];
const ppqnPosition = ticks % this.app.clock.ppqn;
const beatNumber = Math.floor(ticks / this.app.clock.ppqn);
@ -93,10 +118,9 @@ export class Clock {
get ticks_before_new_bar(): number {
/**
* This function returns the number of ticks separating the current moment
* from the beginning of the next bar.
*
* @returns number of ticks until next bar
* Calculates the number of ticks before the next bar.
*
* @returns number - ticks before the next bar
*/
const ticskMissingFromBeat = this.ppqn - this.time_position.pulse;
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
@ -105,10 +129,9 @@ export class Clock {
get next_beat_in_ticks(): number {
/**
* This function returns the number of ticks separating the current moment
* from the beginning of the next beat.
*
* @returns number of ticks until next beat
* Calculates the number of ticks before the next beat.
*
* @returns number - ticks before the next beat
*/
return this.app.clock.pulses_since_origin + this.time_position.pulse;
}
@ -116,6 +139,8 @@ export class Clock {
get beats_per_bar(): number {
/**
* Returns the number of beats per bar.
*
* @returns number - beats per bar
*/
return this.time_signature[0];
}
@ -123,8 +148,8 @@ export class Clock {
get beats_since_origin(): number {
/**
* Returns the number of beats since the origin.
*
* @returns number of beats since origin
*
* @returns number - beats since the origin
*/
return Math.floor(this.tick / this.ppqn);
}
@ -132,8 +157,8 @@ export class Clock {
get pulses_since_origin(): number {
/**
* Returns the number of pulses since the origin.
*
* @returns number of pulses since origin
*
* @returns number - pulses since the origin
*/
return this.tick;
}
@ -141,119 +166,112 @@ export class Clock {
get pulse_duration(): number {
/**
* Returns the duration of a pulse in seconds.
* @returns number - duration of a pulse in seconds
*/
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.
* Returns the duration of a pulse in seconds at a given bpm.
*
* @param bpm - bpm to calculate the pulse duration for
* @returns number - duration of a pulse in seconds
*/
return 60 / bpm / this.ppqn;
}
get bpm(): number {
/**
* Returns the current bpm.
* @returns number - current bpm
*/
return this._bpm;
}
set nudge(nudge: number) {
this.transportNode?.setNudge(nudge);
get tickDuration(): number {
/**
* Returns the duration of a tick in seconds.
* @returns number - duration of a tick in seconds
*/
return 1 / this.ppqn;
}
set bpm(bpm: number) {
/**
* Sets the bpm.
* @param bpm - bpm to set
*/
if (bpm > 0 && this._bpm !== bpm) {
this.transportNode?.setBPM(bpm);
this._bpm = bpm;
this.logicalTime = this.realTime;
this.clock.setDuration(() => (this.tickDuration * 60) / this.bpm);
}
}
get ppqn(): number {
/**
* Returns the current ppqn.
* @returns number - current 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) {
/**
* Sets the ppqn.
* @param ppqn - ppqn to set
* @returns number - current ppqn
*/
if (ppqn > 0 && this._ppqn !== ppqn) {
this._ppqn = ppqn;
this.transportNode?.setPPQN(ppqn);
this.logicalTime = this.realTime;
}
}
public incrementTick(bpm: number) {
this.tick++;
this.logicalTime += this.pulse_duration_at_bpm(bpm);
}
public nextTickFrom(time: number, nudge: number): number {
/**
* Compute the time remaining before the next clock tick.
* @param time - audio context currentTime
* @param nudge - nudge in the future (in seconds)
* @returns remainingTime
*/
const pulseDuration = this.pulse_duration;
const nudgedTime = time + nudge;
const nextTickTime = Math.ceil(nudgedTime / pulseDuration) * pulseDuration;
const remainingTime = nextTickTime - nudgedTime;
return remainingTime;
}
public convertPulseToSecond(n: number): number {
/**
* Converts a pulse to a second.
*/
return n * this.pulse_duration;
}
public start(): void {
/**
* Starts the TransportNode (starts the clock).
*
* Start the clock
*
* @remark also sends a MIDI message if a port is declared
*/
this.app.audioContext.resume();
this.running = true;
this.app.api.MidiConnection.sendStartMessage();
this.lastPlayPressTime = this.app.audioContext.currentTime;
this.totalPauseTime += this.lastPlayPressTime - this.lastPauseTime;
this.transportNode?.start();
this.clock.start();
}
public pause(): void {
/**
* Pauses the TransportNode (pauses the clock).
*
* Pause the clock.
*
* @remark also sends a MIDI message if a port is declared
*/
this.running = false;
this.transportNode?.pause();
this.app.api.MidiConnection.sendStopMessage();
this.lastPauseTime = this.app.audioContext.currentTime;
this.logicalTime = this.realTime;
this.clock.pause();
}
public stop(): void {
/**
* Stops the TransportNode (stops the clock).
* Stops the clock.
*
* @remark also sends a MIDI message if a port is declared
*/
this.running = false;
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.transportNode?.stop();
this.clock.stop();
}
}

View File

@ -144,9 +144,10 @@ export const installInterfaceLogic = (app: Editor) => {
});
app.interface.audio_nudge_range.addEventListener("input", () => {
app.clock.nudge = parseInt(
(app.interface.audio_nudge_range as HTMLInputElement).value,
);
// TODO: rebuild this
// app.clock.nudge = parseInt(
// (app.interface.audio_nudge_range as HTMLInputElement).value,
// );
});
app.interface.dough_nudge_range.addEventListener("input", () => {

View File

@ -1,68 +0,0 @@
import { tryEvaluate } from "./Evaluator";
const zeroPad = (num, places) => String(num).padStart(places, "0");
export class TransportNode extends AudioWorkletNode {
constructor(context, options, application) {
super(context, "transport", options);
this.app = application;
this.port.addEventListener("message", this.handleMessage);
this.port.start();
this.timeviewer = document.getElementById("timeviewer");
}
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
handleMessage = (message) => {
if (message.data) {
if (message.data.type === "bang") {
if (this.app.clock.running) {
if (this.app.settings.send_clock) {
this.app.api.MidiConnection.sendMidiClock();
}
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
this.app.clock.tick,
);
this.app.clock.time_position = futureTimeStamp;
if (futureTimeStamp.pulse % this.app.clock.ppqn == 0) {
this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${
futureTimeStamp.beat + 1
} / ${this.app.clock.bpm}`;
}
if (this.app.exampleIsPlaying) {
tryEvaluate(this.app, this.app.example_buffer);
} else {
tryEvaluate(this.app, this.app.global_buffer);
}
this.app.clock.incrementTick(message.data.bpm);
}
}
}
};
start() {
this.port.postMessage({ type: "start" });
}
pause() {
this.port.postMessage({ type: "pause" });
}
resume() {
this.port.postMessage({ type: "resume" });
}
setBPM(bpm) {
this.port.postMessage({ type: "bpm", value: bpm });
}
setPPQN(ppqn) {
this.port.postMessage({ type: "ppqn", value: ppqn });
}
setNudge(nudge) {
this.port.postMessage({ type: "nudge", value: nudge });
}
stop() {
this.port.postMessage({ type: "stop" });
}
}

View File

@ -1,47 +0,0 @@
class TransportProcessor extends AudioWorkletProcessor {
constructor(options) {
super(options);
this.port.addEventListener("message", this.handleMessage);
this.port.start();
this.nudge = 0;
this.started = false;
this.bpm = 120;
this.ppqn = 48;
this.currentPulsePosition = 0;
}
handleMessage = (message) => {
if (message.data && message.data.type === "ping") {
this.port.postMessage(message.data);
} else if (message.data.type === "start") {
this.started = true;
} else if (message.data.type === "pause") {
this.started = false;
} else if (message.data.type === "stop") {
this.started = false;
} else if (message.data.type === "bpm") {
this.bpm = message.data.value;
this.currentPulsePosition = currentTime;
} else if (message.data.type === "ppqn") {
this.ppqn = message.data.value;
this.currentPulsePosition = currentTime;
} else if (message.data.type === "nudge") {
this.nudge = message.data.value;
}
};
process(inputs, outputs, parameters) {
if (this.started) {
const adjustedCurrentTime = currentTime + this.nudge / 100;
const beatNumber = adjustedCurrentTime / (60 / this.bpm);
const currentPulsePosition = Math.ceil(beatNumber * this.ppqn);
if (currentPulsePosition > this.currentPulsePosition) {
this.currentPulsePosition = currentPulsePosition;
this.port.postMessage({ type: "bang", bpm: this.bpm });
}
}
return true;
}
}
registerProcessor("transport", TransportProcessor);

View File

@ -1,5 +1,11 @@
import { type Editor } from "../main";
import { freqToMidi, chord as parseChord, noteNameToMidi, resolvePitchBend, safeScale } from "zifferjs";
import {
freqToMidi,
chord as parseChord,
noteNameToMidi,
resolvePitchBend,
safeScale,
} from "zifferjs";
import { SkipEvent } from "./SkipEvent";
export type EventOperation<T> = (instance: T, ...args: any[]) => void;
@ -311,16 +317,16 @@ export abstract class AudibleEvent extends AbstractEvent {
return this;
};
protected updateValue<T>(
key: string,
value: T | T[] | null
): this {
protected updateValue<T>(key: string, value: T | T[] | null): this {
if (value == null) return this;
this.values[key] = value;
return this;
}
public note = (value: number | string | null, ...kwargs: number[]|string[]) => {
public note = (
value: number | string | null,
...kwargs: number[] | string[]
) => {
if (typeof value === "string") {
const parsedNote = noteNameToMidi(value);
return this.updateValue("note", [parsedNote, ...kwargs].flat(Infinity));
@ -331,8 +337,8 @@ export abstract class AudibleEvent extends AbstractEvent {
}
};
public chord = (value: number|string, ...kwargs: number[]) => {
if(typeof value === "string") {
public chord = (value: number | string, ...kwargs: number[]) => {
if (typeof value === "string") {
const chord = parseChord(value);
return this.updateValue("note", chord);
} else {

View File

@ -5,10 +5,7 @@ import {
arrayOfObjectsToObjectWithArrays,
objectWithArraysToArrayOfObjects,
} from "../Utils/Generic";
import {
midiToFreq,
noteFromPc,
} from "zifferjs";
import { midiToFreq, noteFromPc } from "zifferjs";
import {
superdough,
@ -66,7 +63,7 @@ export class SoundEvent extends AudibleEvent {
phaserDepth: ["phaserDepth", "phasdepth"],
phaserSweep: ["phaserSweep", "phassweep"],
phaserCenter: ["phaserCenter", "phascenter"],
fmadsr: function(
fmadsr: function (
self: SoundEvent,
a: number,
d: number,
@ -79,7 +76,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("fmrelease", r);
return self;
},
fmad: function(self: SoundEvent, a: number, d: number) {
fmad: function (self: SoundEvent, a: number, d: number) {
self.updateValue("fmattack", a);
self.updateValue("fmdecay", d);
return self;
@ -90,7 +87,7 @@ export class SoundEvent extends AudibleEvent {
decay: ["decay", "dec"],
sustain: ["sustain", "sus"],
release: ["release", "rel"],
adsr: function(
adsr: function (
self: SoundEvent,
a: number,
d: number,
@ -103,21 +100,21 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("release", r);
return self;
},
ad: function(self: SoundEvent, a: number, d: number) {
ad: function (self: SoundEvent, a: number, d: number) {
self.updateValue("attack", a);
self.updateValue("decay", d);
self.updateValue("sustain", 0.0);
self.updateValue("release", 0.0);
return self;
},
scope: function(self: SoundEvent) {
self.updateValue("analyze", true)
return self
scope: function (self: SoundEvent) {
self.updateValue("analyze", true);
return self;
},
debug: function(self: SoundEvent, callback?: Function) {
self.updateValue("debug", true)
debug: function (self: SoundEvent, callback?: Function) {
self.updateValue("debug", true);
if (callback) {
self.updateValue("debugFunction", callback)
self.updateValue("debugFunction", callback);
}
return self;
},
@ -126,27 +123,27 @@ export class SoundEvent extends AudibleEvent {
lpdecay: ["lpdecay", "lpd"],
lpsustain: ["lpsustain", "lps"],
lprelease: ["lprelease", "lpr"],
cutoff: function(self: SoundEvent, value: number, resonance?: number) {
cutoff: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("cutoff", value);
if (resonance) {
self.updateValue("resonance", resonance);
}
return self;
},
lpf: function(self: SoundEvent, value: number, resonance?: number) {
lpf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("cutoff", value);
if (resonance) {
self.updateValue("resonance", resonance);
}
return self;
},
resonance: function(self: SoundEvent, value: number) {
resonance: function (self: SoundEvent, value: number) {
if (value >= 0 && value <= 1) {
self.updateValue("resonance", 50 * value);
}
return self;
},
lpadsr: function(
lpadsr: function (
self: SoundEvent,
depth: number,
a: number,
@ -161,7 +158,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("lprelease", r);
return self;
},
lpad: function(self: SoundEvent, depth: number, a: number, d: number) {
lpad: function (self: SoundEvent, depth: number, a: number, d: number) {
self.updateValue("lpenv", depth);
self.updateValue("lpattack", a);
self.updateValue("lpdecay", d);
@ -174,25 +171,25 @@ export class SoundEvent extends AudibleEvent {
hpdecay: ["hpdecay", "hpd"],
hpsustain: ["hpsustain", "hpsus"],
hprelease: ["hprelease", "hpr"],
hcutoff: function(self: SoundEvent, value: number, resonance?: number) {
hcutoff: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("hcutoff", value);
if (resonance) {
self.updateValue("hresonance", resonance);
}
return self;
},
hpf: function(self: SoundEvent, value: number, resonance?: number) {
hpf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("hcutoff", value);
if (resonance) {
self.updateValue("hresonance", resonance);
}
return self;
},
hpq: function(self: SoundEvent, value: number) {
hpq: function (self: SoundEvent, value: number) {
self.updateValue("hresonance", value);
return self;
},
hpadsr: function(
hpadsr: function (
self: SoundEvent,
depth: number,
a: number,
@ -207,7 +204,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("hprelease", r);
return self;
},
hpad: function(self: SoundEvent, depth: number, a: number, d: number) {
hpad: function (self: SoundEvent, depth: number, a: number, d: number) {
self.updateValue("hpenv", depth);
self.updateValue("hpattack", a);
self.updateValue("hpdecay", d);
@ -220,14 +217,14 @@ export class SoundEvent extends AudibleEvent {
bpdecay: ["bpdecay", "bpd"],
bpsustain: ["bpsustain", "bps"],
bprelease: ["bprelease", "bpr"],
bandf: function(self: SoundEvent, value: number, resonance?: number) {
bandf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("bandf", value);
if (resonance) {
self.updateValue("bandq", resonance);
}
return self;
},
bpf: function(self: SoundEvent, value: number, resonance?: number) {
bpf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("bandf", value);
if (resonance) {
self.updateValue("bandq", resonance);
@ -235,7 +232,7 @@ export class SoundEvent extends AudibleEvent {
return self;
},
bandq: ["bandq", "bpq"],
bpadsr: function(
bpadsr: function (
self: SoundEvent,
depth: number,
a: number,
@ -250,7 +247,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("bprelease", r);
return self;
},
bpad: function(self: SoundEvent, depth: number, a: number, d: number) {
bpad: function (self: SoundEvent, depth: number, a: number, d: number) {
self.updateValue("bpenv", depth);
self.updateValue("bpattack", a);
self.updateValue("bpdecay", d);
@ -260,7 +257,7 @@ export class SoundEvent extends AudibleEvent {
},
vib: ["vib"],
vibmod: ["vibmod"],
fm: function(self: SoundEvent, value: number | string) {
fm: function (self: SoundEvent, value: number | string) {
if (typeof value === "number") {
self.values["fmi"] = value;
} else {
@ -276,11 +273,11 @@ export class SoundEvent extends AudibleEvent {
begin: ["begin"],
end: ["end"],
gain: ["gain"],
dbgain: function(self: SoundEvent, value: number) {
dbgain: function (self: SoundEvent, value: number) {
self.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
return self;
},
db: function(self: SoundEvent, value: number) {
db: function (self: SoundEvent, value: number) {
self.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
return self;
},
@ -303,32 +300,32 @@ export class SoundEvent extends AudibleEvent {
roomlp: ["roomlp", "rlp"],
roomdim: ["roomdim", "rdim"],
sound: ["s", "sound"],
size: function(self: SoundEvent, value: number) {
size: function (self: SoundEvent, value: number) {
self.updateValue("roomsize", value);
return self;
},
sz: function(self: SoundEvent, value: number) {
sz: function (self: SoundEvent, value: number) {
self.updateValue("roomsize", value);
return self;
},
comp: ["compressor", "cmp"],
ratio: function(self: SoundEvent, value: number) {
ratio: function (self: SoundEvent, value: number) {
self.updateValue("compressorRatio", value);
return self;
},
knee: function(self: SoundEvent, value: number) {
knee: function (self: SoundEvent, value: number) {
self.updateValue("compressorKnee", value);
return self;
},
compAttack: function(self: SoundEvent, value: number) {
compAttack: function (self: SoundEvent, value: number) {
self.updateValue("compressorAttack", value);
return self;
},
compRelease: function(self: SoundEvent, value: number) {
compRelease: function (self: SoundEvent, value: number) {
self.updateValue("compressorRelease", value);
return self;
},
stretch: function(self: SoundEvent, beat: number) {
stretch: function (self: SoundEvent, beat: number) {
self.updateValue("unit", "c");
self.updateValue("speed", 1 / beat);
self.updateValue("cut", beat);
@ -467,16 +464,12 @@ export class SoundEvent extends AudibleEvent {
}
if (this.values["debug"]) {
if (this.values["debugFunction"]) {
this.values["debugFunction"](filteredEvent)
this.values["debugFunction"](filteredEvent);
} else {
console.log(filteredEvent)
console.log(filteredEvent);
}
}
superdough(
filteredEvent,
this.nudge - this.app.clock.deviation,
filteredEvent.dur,
);
superdough(filteredEvent, this.app.clock.deadline, filteredEvent.dur);
}
};
}

View File

@ -10,17 +10,18 @@ You can turn on the oscilloscope to generate interesting visuals or to inspect a
You need to manually feed the scope with the sounds you want to inspect:
${makeExample(
"Feeding a sine to the oscilloscope",
`
"Feeding a sine to the oscilloscope",
`
beat(1)::sound('sine').freq(200).ad(0, .2).scope().out()
`, true
)}
`,
true,
)}
Here is a layout of the scope configuration options:
${makeExample(
"Oscilloscope configuration",
`
"Oscilloscope configuration",
`
scope({
enabled: true, // off by default
color: "#fdba74", // any valid CSS color or "random"
@ -34,12 +35,12 @@ scope({
refresh: 1 // refresh rate (in pulses)
})
`,
true,
)}
true,
)}
${makeExample(
"Demo with multiple scope mode",
`
"Demo with multiple scope mode",
`
rhythm(.5, [4,5].dur(4*3, 4*1), 8)::sound('fhardkick').out()
beat(0.25)::sound('square').freq([
[250, 250/2, 250/4].pick(),
@ -55,8 +56,8 @@ scope({enabled: true, thickness: 8,
color: ['purple', 'green', 'random'].beat(),
size: 0.5, fftSize: 2048})
`,
true,
)}
true,
)}
Note that these values can be patterned as well! You can transform the oscilloscope into its own light show if you want. The picture is not stable anyway so you won't have much use of it for precision work :)

View File

@ -25,7 +25,7 @@ declare global {
z15(): Player;
z16(): Player;
midi(): MidiEvent;
sound(name: string): SoundEvent|SkipEvent;
sound(name: string): SoundEvent | SkipEvent;
}
}
@ -102,11 +102,11 @@ export const makeNumberExtensions = (api: UserAPI) => {
return api.midi(this.valueOf(), ...kwargs);
};
Number.prototype.sound = function (name: string): SoundEvent|SkipEvent {
Number.prototype.sound = function (name: string): SoundEvent | SkipEvent {
if (Number.isInteger(this.valueOf())) {
return (api.sound(name) as SoundEvent).note(this.valueOf());
} else {
return (api.sound(name) as SoundEvent).freq(this.valueOf());
}
}
};
};

View File

@ -246,20 +246,17 @@ export class Editor {
"ui-known-universe-item-template",
) as HTMLTemplateElement;
if (!itemTemplate) {
console.warn("Missing template #ui-known-universe-item-template");
return;
}
let existing_universes = document.getElementById("existing-universes");
if (!existing_universes) {
console.warn("Missing element #existing-universes");
return;
}
let list = document.createElement("ul");
list.className =
"lg:h-80 lg:text-normal text-sm h-auto lg:w-80 w-auto lg:pb-2 lg:pt-2 overflow-y-scroll text-white lg:mb-4 border rounded-lg bg-neutral-800";
list.append(
...Object.keys(this.universes).map((it) => {
let item = itemTemplate.content.cloneNode(true) as DocumentFragment;

View File

@ -3887,6 +3887,11 @@ zifferjs@^0.0.44:
resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.44.tgz#c6b425166488ec05e349867e3de2460b74204449"
integrity sha512-Q+0affxeUZwl+oJpsa1nb4hqHV6V4VX+pkejCQq/e9+/0H6ooTpcDQ9IDopvrWBnhA8E11k0tbwUee5TJtE8UQ==
zyklus@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/zyklus/-/zyklus-0.1.4.tgz#229b2966fd1126ef72c6004697269118762bdcd5"
integrity sha512-hbv2cyy4nOI7P8nL8b3ki1jswoLzkUzewPgCLDdDfABryDkV5iO8DAbU25OgO5ShRZHLjXJIylwv5PJQPl3Mpw==
zzfx@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/zzfx/-/zzfx-1.2.0.tgz#021e5df8e1605f507e2dde15608eba22798b424b"