This commit is contained in:
2023-12-01 11:16:16 +01:00
parent 31adc17a36
commit a34f1a33eb
7 changed files with 117 additions and 172 deletions

View File

@ -1581,7 +1581,9 @@ export class UserAPI {
// Low Frequency Oscillators // 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. * Returns an array of values between start and end, with a given step.
* *
@ -1603,7 +1605,11 @@ export class UserAPI {
return result; 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. * 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. * Returns a sine wave between 0 and 1.
* *

View File

@ -4,7 +4,8 @@ import { tryEvaluate } from "./Evaluator";
import { getAudioContext } from "superdough"; import { getAudioContext } from "superdough";
// @ts-ignore // @ts-ignore
import "zyklus"; import "zyklus";
const zeroPad = (num: number, places: number) => String(num).padStart(places, "0"); const zeroPad = (num: number, places: number) =>
String(num).padStart(places, "0");
export interface TimePosition { export interface TimePosition {
/** /**
@ -61,35 +62,37 @@ export class Clock {
this.running = true; this.running = true;
this.deadline = 0; this.deadline = 0;
this.timeviewer = document.getElementById("timeviewer")!; this.timeviewer = document.getElementById("timeviewer")!;
this.clock = getAudioContext().createClock(this.clockCallback, this.pulse_duration) this.clock = getAudioContext().createClock(
} this.clockCallback,
this.pulse_duration,
);
}
clockCallback = (time: number, duration: number, tick: number) => { clockCallback = (time: number, duration: number, tick: number) => {
let deadline = time - getAudioContext().currentTime; let deadline = time - getAudioContext().currentTime;
this.deadline = deadline; this.deadline = deadline;
this.tick = tick; this.tick = tick;
if (this.app.clock.running) { if (this.app.clock.running) {
if (this.app.settings.send_clock) { if (this.app.settings.send_clock) {
this.app.api.MidiConnection.sendMidiClock(); this.app.api.MidiConnection.sendMidiClock();
} }
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;
if (futureTimeStamp.pulse % this.app.clock.ppqn == 0) { if (futureTimeStamp.pulse % this.app.clock.ppqn == 0) {
this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${ this.timeviewer.innerHTML = `${zeroPad(futureTimeStamp.bar, 2)}:${
futureTimeStamp.beat + 1 futureTimeStamp.beat + 1
} / ${this.app.clock.bpm}`; } / ${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);
} }
} }
// Implement TransportNode clock callback and update clock info with it // Implement TransportNode clock callback and update clock info with it
}; };
convertTicksToTimeposition(ticks: number): TimePosition { convertTicksToTimeposition(ticks: number): TimePosition {
@ -108,27 +111,27 @@ export class Clock {
} }
get next_beat_in_ticks(): number { get next_beat_in_ticks(): number {
return this.app.clock.pulses_since_origin + this.time_position.pulse; return this.app.clock.pulses_since_origin + this.time_position.pulse;
} }
get beats_per_bar(): number { get beats_per_bar(): number {
return this.time_signature[0]; return this.time_signature[0];
} }
get beats_since_origin(): number { get beats_since_origin(): number {
return Math.floor(this.tick / this.ppqn); return Math.floor(this.tick / this.ppqn);
} }
get pulses_since_origin(): number { get pulses_since_origin(): number {
return this.tick; return this.tick;
} }
get pulse_duration(): number { get pulse_duration(): number {
return 60 / this.bpm / this.ppqn; return 60 / this.bpm / this.ppqn;
} }
public pulse_duration_at_bpm(bpm: number = this.bpm): number { public pulse_duration_at_bpm(bpm: number = this.bpm): number {
return 60 / bpm / this.ppqn; return 60 / bpm / this.ppqn;
} }
get bpm(): number { get bpm(): number {
@ -162,7 +165,7 @@ export class Clock {
} }
public nextTickFrom(time: number, nudge: number): number { public nextTickFrom(time: number, nudge: number): number {
const pulseDuration = this.pulse_duration; const pulseDuration = this.pulse_duration;
const nudgedTime = time + nudge; const nudgedTime = time + nudge;
const nextTickTime = Math.ceil(nudgedTime / pulseDuration) * pulseDuration; const nextTickTime = Math.ceil(nudgedTime / pulseDuration) * pulseDuration;
const remainingTime = nextTickTime - nudgedTime; const remainingTime = nextTickTime - nudgedTime;
@ -183,13 +186,13 @@ export class Clock {
this.app.audioContext.resume(); this.app.audioContext.resume();
this.running = true; this.running = true;
this.app.api.MidiConnection.sendStartMessage(); this.app.api.MidiConnection.sendStartMessage();
this.clock.start() this.clock.start();
} }
public pause(): void { public pause(): void {
this.running = false; this.running = false;
this.app.api.MidiConnection.sendStopMessage(); this.app.api.MidiConnection.sendStopMessage();
this.clock.pause() this.clock.pause();
} }
public stop(): void { public stop(): void {
@ -204,4 +207,4 @@ export class Clock {
this.app.api.MidiConnection.sendStopMessage(); this.app.api.MidiConnection.sendStopMessage();
this.clock.stop(); this.clock.stop();
} }
} }

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

View File

@ -5,10 +5,7 @@ import {
arrayOfObjectsToObjectWithArrays, arrayOfObjectsToObjectWithArrays,
objectWithArraysToArrayOfObjects, objectWithArraysToArrayOfObjects,
} from "../Utils/Generic"; } from "../Utils/Generic";
import { import { midiToFreq, noteFromPc } from "zifferjs";
midiToFreq,
noteFromPc,
} from "zifferjs";
import { import {
superdough, superdough,
@ -66,7 +63,7 @@ export class SoundEvent extends AudibleEvent {
phaserDepth: ["phaserDepth", "phasdepth"], phaserDepth: ["phaserDepth", "phasdepth"],
phaserSweep: ["phaserSweep", "phassweep"], phaserSweep: ["phaserSweep", "phassweep"],
phaserCenter: ["phaserCenter", "phascenter"], phaserCenter: ["phaserCenter", "phascenter"],
fmadsr: function( fmadsr: function (
self: SoundEvent, self: SoundEvent,
a: number, a: number,
d: number, d: number,
@ -79,7 +76,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("fmrelease", r); self.updateValue("fmrelease", r);
return self; return self;
}, },
fmad: function(self: SoundEvent, a: number, d: number) { fmad: function (self: SoundEvent, a: number, d: number) {
self.updateValue("fmattack", a); self.updateValue("fmattack", a);
self.updateValue("fmdecay", d); self.updateValue("fmdecay", d);
return self; return self;
@ -90,7 +87,7 @@ export class SoundEvent extends AudibleEvent {
decay: ["decay", "dec"], decay: ["decay", "dec"],
sustain: ["sustain", "sus"], sustain: ["sustain", "sus"],
release: ["release", "rel"], release: ["release", "rel"],
adsr: function( adsr: function (
self: SoundEvent, self: SoundEvent,
a: number, a: number,
d: number, d: number,
@ -103,21 +100,21 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("release", r); self.updateValue("release", r);
return self; return self;
}, },
ad: function(self: SoundEvent, a: number, d: number) { ad: function (self: SoundEvent, a: number, d: number) {
self.updateValue("attack", a); self.updateValue("attack", a);
self.updateValue("decay", d); self.updateValue("decay", d);
self.updateValue("sustain", 0.0); self.updateValue("sustain", 0.0);
self.updateValue("release", 0.0); self.updateValue("release", 0.0);
return self; return self;
}, },
scope: function(self: SoundEvent) { scope: function (self: SoundEvent) {
self.updateValue("analyze", true) self.updateValue("analyze", true);
return self return self;
}, },
debug: function(self: SoundEvent, callback?: Function) { debug: function (self: SoundEvent, callback?: Function) {
self.updateValue("debug", true) self.updateValue("debug", true);
if (callback) { if (callback) {
self.updateValue("debugFunction", callback) self.updateValue("debugFunction", callback);
} }
return self; return self;
}, },
@ -126,27 +123,27 @@ export class SoundEvent extends AudibleEvent {
lpdecay: ["lpdecay", "lpd"], lpdecay: ["lpdecay", "lpd"],
lpsustain: ["lpsustain", "lps"], lpsustain: ["lpsustain", "lps"],
lprelease: ["lprelease", "lpr"], lprelease: ["lprelease", "lpr"],
cutoff: function(self: SoundEvent, value: number, resonance?: number) { cutoff: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("cutoff", value); self.updateValue("cutoff", value);
if (resonance) { if (resonance) {
self.updateValue("resonance", resonance); self.updateValue("resonance", resonance);
} }
return self; return self;
}, },
lpf: function(self: SoundEvent, value: number, resonance?: number) { lpf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("cutoff", value); self.updateValue("cutoff", value);
if (resonance) { if (resonance) {
self.updateValue("resonance", resonance); self.updateValue("resonance", resonance);
} }
return self; return self;
}, },
resonance: function(self: SoundEvent, value: number) { resonance: function (self: SoundEvent, value: number) {
if (value >= 0 && value <= 1) { if (value >= 0 && value <= 1) {
self.updateValue("resonance", 50 * value); self.updateValue("resonance", 50 * value);
} }
return self; return self;
}, },
lpadsr: function( lpadsr: function (
self: SoundEvent, self: SoundEvent,
depth: number, depth: number,
a: number, a: number,
@ -161,7 +158,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("lprelease", r); self.updateValue("lprelease", r);
return self; 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("lpenv", depth);
self.updateValue("lpattack", a); self.updateValue("lpattack", a);
self.updateValue("lpdecay", d); self.updateValue("lpdecay", d);
@ -174,25 +171,25 @@ export class SoundEvent extends AudibleEvent {
hpdecay: ["hpdecay", "hpd"], hpdecay: ["hpdecay", "hpd"],
hpsustain: ["hpsustain", "hpsus"], hpsustain: ["hpsustain", "hpsus"],
hprelease: ["hprelease", "hpr"], hprelease: ["hprelease", "hpr"],
hcutoff: function(self: SoundEvent, value: number, resonance?: number) { hcutoff: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("hcutoff", value); self.updateValue("hcutoff", value);
if (resonance) { if (resonance) {
self.updateValue("hresonance", resonance); self.updateValue("hresonance", resonance);
} }
return self; return self;
}, },
hpf: function(self: SoundEvent, value: number, resonance?: number) { hpf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("hcutoff", value); self.updateValue("hcutoff", value);
if (resonance) { if (resonance) {
self.updateValue("hresonance", resonance); self.updateValue("hresonance", resonance);
} }
return self; return self;
}, },
hpq: function(self: SoundEvent, value: number) { hpq: function (self: SoundEvent, value: number) {
self.updateValue("hresonance", value); self.updateValue("hresonance", value);
return self; return self;
}, },
hpadsr: function( hpadsr: function (
self: SoundEvent, self: SoundEvent,
depth: number, depth: number,
a: number, a: number,
@ -207,7 +204,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("hprelease", r); self.updateValue("hprelease", r);
return self; 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("hpenv", depth);
self.updateValue("hpattack", a); self.updateValue("hpattack", a);
self.updateValue("hpdecay", d); self.updateValue("hpdecay", d);
@ -220,14 +217,14 @@ export class SoundEvent extends AudibleEvent {
bpdecay: ["bpdecay", "bpd"], bpdecay: ["bpdecay", "bpd"],
bpsustain: ["bpsustain", "bps"], bpsustain: ["bpsustain", "bps"],
bprelease: ["bprelease", "bpr"], bprelease: ["bprelease", "bpr"],
bandf: function(self: SoundEvent, value: number, resonance?: number) { bandf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("bandf", value); self.updateValue("bandf", value);
if (resonance) { if (resonance) {
self.updateValue("bandq", resonance); self.updateValue("bandq", resonance);
} }
return self; return self;
}, },
bpf: function(self: SoundEvent, value: number, resonance?: number) { bpf: function (self: SoundEvent, value: number, resonance?: number) {
self.updateValue("bandf", value); self.updateValue("bandf", value);
if (resonance) { if (resonance) {
self.updateValue("bandq", resonance); self.updateValue("bandq", resonance);
@ -235,7 +232,7 @@ export class SoundEvent extends AudibleEvent {
return self; return self;
}, },
bandq: ["bandq", "bpq"], bandq: ["bandq", "bpq"],
bpadsr: function( bpadsr: function (
self: SoundEvent, self: SoundEvent,
depth: number, depth: number,
a: number, a: number,
@ -250,7 +247,7 @@ export class SoundEvent extends AudibleEvent {
self.updateValue("bprelease", r); self.updateValue("bprelease", r);
return self; 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("bpenv", depth);
self.updateValue("bpattack", a); self.updateValue("bpattack", a);
self.updateValue("bpdecay", d); self.updateValue("bpdecay", d);
@ -260,7 +257,7 @@ export class SoundEvent extends AudibleEvent {
}, },
vib: ["vib"], vib: ["vib"],
vibmod: ["vibmod"], vibmod: ["vibmod"],
fm: function(self: SoundEvent, value: number | string) { fm: function (self: SoundEvent, value: number | string) {
if (typeof value === "number") { if (typeof value === "number") {
self.values["fmi"] = value; self.values["fmi"] = value;
} else { } else {
@ -276,11 +273,11 @@ export class SoundEvent extends AudibleEvent {
begin: ["begin"], begin: ["begin"],
end: ["end"], end: ["end"],
gain: ["gain"], 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)); self.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
return self; 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)); self.updateValue("gain", Math.min(Math.pow(10, value / 20), 10));
return self; return self;
}, },
@ -303,32 +300,32 @@ export class SoundEvent extends AudibleEvent {
roomlp: ["roomlp", "rlp"], roomlp: ["roomlp", "rlp"],
roomdim: ["roomdim", "rdim"], roomdim: ["roomdim", "rdim"],
sound: ["s", "sound"], sound: ["s", "sound"],
size: function(self: SoundEvent, value: number) { size: function (self: SoundEvent, value: number) {
self.updateValue("roomsize", value); self.updateValue("roomsize", value);
return self; return self;
}, },
sz: function(self: SoundEvent, value: number) { sz: function (self: SoundEvent, value: number) {
self.updateValue("roomsize", value); self.updateValue("roomsize", value);
return self; return self;
}, },
comp: ["compressor", "cmp"], comp: ["compressor", "cmp"],
ratio: function(self: SoundEvent, value: number) { ratio: function (self: SoundEvent, value: number) {
self.updateValue("compressorRatio", value); self.updateValue("compressorRatio", value);
return self; return self;
}, },
knee: function(self: SoundEvent, value: number) { knee: function (self: SoundEvent, value: number) {
self.updateValue("compressorKnee", value); self.updateValue("compressorKnee", value);
return self; return self;
}, },
compAttack: function(self: SoundEvent, value: number) { compAttack: function (self: SoundEvent, value: number) {
self.updateValue("compressorAttack", value); self.updateValue("compressorAttack", value);
return self; return self;
}, },
compRelease: function(self: SoundEvent, value: number) { compRelease: function (self: SoundEvent, value: number) {
self.updateValue("compressorRelease", value); self.updateValue("compressorRelease", value);
return self; return self;
}, },
stretch: function(self: SoundEvent, beat: number) { stretch: function (self: SoundEvent, beat: number) {
self.updateValue("unit", "c"); self.updateValue("unit", "c");
self.updateValue("speed", 1 / beat); self.updateValue("speed", 1 / beat);
self.updateValue("cut", beat); self.updateValue("cut", beat);
@ -467,16 +464,12 @@ export class SoundEvent extends AudibleEvent {
} }
if (this.values["debug"]) { if (this.values["debug"]) {
if (this.values["debugFunction"]) { if (this.values["debugFunction"]) {
this.values["debugFunction"](filteredEvent) this.values["debugFunction"](filteredEvent);
} else { } else {
console.log(filteredEvent) console.log(filteredEvent);
} }
} }
superdough( superdough(filteredEvent, this.app.clock.deadline, filteredEvent.dur);
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: You need to manually feed the scope with the sounds you want to inspect:
${makeExample( ${makeExample(
"Feeding a sine to the oscilloscope", "Feeding a sine to the oscilloscope",
` `
beat(1)::sound('sine').freq(200).ad(0, .2).scope().out() beat(1)::sound('sine').freq(200).ad(0, .2).scope().out()
`, true `,
)} true,
)}
Here is a layout of the scope configuration options: Here is a layout of the scope configuration options:
${makeExample( ${makeExample(
"Oscilloscope configuration", "Oscilloscope configuration",
` `
scope({ scope({
enabled: true, // off by default enabled: true, // off by default
color: "#fdba74", // any valid CSS color or "random" color: "#fdba74", // any valid CSS color or "random"
@ -34,12 +35,12 @@ scope({
refresh: 1 // refresh rate (in pulses) refresh: 1 // refresh rate (in pulses)
}) })
`, `,
true, true,
)} )}
${makeExample( ${makeExample(
"Demo with multiple scope mode", "Demo with multiple scope mode",
` `
rhythm(.5, [4,5].dur(4*3, 4*1), 8)::sound('fhardkick').out() rhythm(.5, [4,5].dur(4*3, 4*1), 8)::sound('fhardkick').out()
beat(0.25)::sound('square').freq([ beat(0.25)::sound('square').freq([
[250, 250/2, 250/4].pick(), [250, 250/2, 250/4].pick(),
@ -55,8 +56,8 @@ scope({enabled: true, thickness: 8,
color: ['purple', 'green', 'random'].beat(), color: ['purple', 'green', 'random'].beat(),
size: 0.5, fftSize: 2048}) 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 :) 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; z15(): Player;
z16(): Player; z16(): Player;
midi(): MidiEvent; 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); 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())) { if (Number.isInteger(this.valueOf())) {
return (api.sound(name) as SoundEvent).note(this.valueOf()); return (api.sound(name) as SoundEvent).note(this.valueOf());
} else { } else {
return (api.sound(name) as SoundEvent).freq(this.valueOf()); return (api.sound(name) as SoundEvent).freq(this.valueOf());
} }
}; };
}; };