@blibliki/engine 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- import { IAnyAudioContext, Module } from "@/core";
1
+ import { Context } from "@blibliki/utils";
2
+ import { Module } from "@/core";
2
3
  import { IModuleConstructor } from "@/core/module/Module";
3
4
  import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
4
5
  import { PropSchema } from "@/core/schema";
@@ -54,7 +55,7 @@ class MonoFilter extends Module<ModuleType.Filter> {
54
55
  constructor(engineId: string, params: ICreateModule<ModuleType.Filter>) {
55
56
  const props = { ...DEFAULT_PROPS, ...params.props };
56
57
 
57
- const audioNodeConstructor = (context: IAnyAudioContext) =>
58
+ const audioNodeConstructor = (context: Context) =>
58
59
  newAudioWorklet(context, CustomWorklet.FilterProcessor);
59
60
 
60
61
  super(engineId, {
@@ -1,4 +1,5 @@
1
- import { IAnyAudioContext, IModule, Module } from "@/core";
1
+ import { Context } from "@blibliki/utils";
2
+ import { IModule, Module } from "@/core";
2
3
  import { IModuleConstructor } from "@/core/module/Module";
3
4
  import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
4
5
  import { PropSchema } from "@/core/schema";
@@ -26,8 +27,8 @@ export class MonoGain extends Module<ModuleType.Gain> {
26
27
 
27
28
  constructor(engineId: string, params: ICreateModule<ModuleType.Gain>) {
28
29
  const props = { ...DEFAULT_PROPS, ...params.props };
29
- const audioNodeConstructor = (context: IAnyAudioContext) =>
30
- new GainNode(context);
30
+ const audioNodeConstructor = (context: Context) =>
31
+ new GainNode(context.audioContext);
31
32
 
32
33
  super(engineId, {
33
34
  ...params,
@@ -1,4 +1,5 @@
1
- import { IAnyAudioContext, IModule, Module } from "@/core";
1
+ import { Context } from "@blibliki/utils";
2
+ import { IModule, Module } from "@/core";
2
3
  import { PropSchema } from "@/core/schema";
3
4
  import { ICreateModule, ModuleType } from ".";
4
5
 
@@ -23,8 +24,8 @@ export default class Inspector extends Module<ModuleType.Inspector> {
23
24
 
24
25
  constructor(engineId: string, params: ICreateModule<ModuleType.Inspector>) {
25
26
  const props = { ...DEFAULT_PROPS, ...params.props };
26
- const audioNodeConstructor = (context: IAnyAudioContext) =>
27
- new AnalyserNode(context);
27
+ const audioNodeConstructor = (context: Context) =>
28
+ new AnalyserNode(context.audioContext);
28
29
 
29
30
  super(engineId, {
30
31
  ...params,
@@ -1,5 +1,5 @@
1
- import { EmptyObject } from "@blibliki/utils";
2
- import { IAnyAudioContext, IModule, Module } from "@/core";
1
+ import { Context, EmptyObject } from "@blibliki/utils";
2
+ import { IModule, Module } from "@/core";
3
3
  import { PropSchema } from "@/core/schema";
4
4
  import { ICreateModule, ModuleType } from ".";
5
5
 
@@ -15,8 +15,7 @@ export default class Master extends Module<ModuleType.Master> {
15
15
 
16
16
  constructor(engineId: string, params: ICreateModule<ModuleType.Master>) {
17
17
  const props = { ...DEFAULT_PROPS, ...params.props };
18
- const audioNodeConstructor = (context: IAnyAudioContext) =>
19
- context.destination;
18
+ const audioNodeConstructor = (context: Context) => context.destination;
20
19
 
21
20
  super(engineId, { ...params, audioNodeConstructor, props });
22
21
 
@@ -1,7 +1,7 @@
1
- import { dbToGain } from "@blibliki/utils";
2
- import { IAnyAudioContext, IModule, Module } from "@/core";
1
+ import { ContextTime } from "@blibliki/transport";
2
+ import { Context, dbToGain } from "@blibliki/utils";
3
+ import { IModule, Module } from "@/core";
3
4
  import Note from "@/core/Note";
4
- import { nt, TTime } from "@/core/Timing/Time";
5
5
  import { IModuleConstructor } from "@/core/module/Module";
6
6
  import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
7
7
  import { PropSchema } from "@/core/schema";
@@ -95,8 +95,8 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
95
95
 
96
96
  constructor(engineId: string, params: ICreateModule<ModuleType.Oscillator>) {
97
97
  const props = { ...DEFAULT_PROPS, ...params.props };
98
- const audioNodeConstructor = (context: IAnyAudioContext) =>
99
- new OscillatorNode(context);
98
+ const audioNodeConstructor = (context: Context) =>
99
+ new OscillatorNode(context.audioContext);
100
100
 
101
101
  super(engineId, {
102
102
  ...params,
@@ -104,7 +104,7 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
104
104
  audioNodeConstructor,
105
105
  });
106
106
 
107
- this.lowOutputGain = new GainNode(this.context, {
107
+ this.lowOutputGain = new GainNode(this.context.audioContext, {
108
108
  gain: dbToGain(LOW_GAIN),
109
109
  });
110
110
 
@@ -140,17 +140,17 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
140
140
  this.rePlugAll();
141
141
  }
142
142
 
143
- start(time: TTime) {
143
+ start(time: ContextTime) {
144
144
  if (this.isStated) return;
145
145
 
146
146
  this.isStated = true;
147
- this.audioNode.start(nt(time));
147
+ this.audioNode.start(time);
148
148
  }
149
149
 
150
- stop(time: TTime) {
151
- this.audioNode.stop(nt(time));
150
+ stop(time: ContextTime) {
151
+ this.audioNode.stop(time);
152
152
  this.rePlugAll(() => {
153
- this.audioNode = new OscillatorNode(this.context, {
153
+ this.audioNode = new OscillatorNode(this.context.audioContext, {
154
154
  type: this.props.wave,
155
155
  frequency: this.finalFrequency,
156
156
  });
@@ -161,7 +161,7 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
161
161
  this.isStated = false;
162
162
  }
163
163
 
164
- triggerAttack = (note: Note, triggeredAt: TTime) => {
164
+ triggerAttack = (note: Note, triggeredAt: ContextTime) => {
165
165
  super.triggerAttack(note, triggeredAt);
166
166
 
167
167
  this.props = { frequency: note.frequency };
@@ -169,7 +169,7 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
169
169
  this.start(triggeredAt);
170
170
  };
171
171
 
172
- triggerRelease(note: Note, triggeredAt: TTime) {
172
+ triggerRelease(note: Note, triggeredAt: ContextTime) {
173
173
  super.triggerRelease(note, triggeredAt);
174
174
 
175
175
  const lastNote = this.activeNotes.length
@@ -190,14 +190,11 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
190
190
  return transposed;
191
191
  }
192
192
 
193
- private updateFrequency(actionAt?: TTime) {
193
+ private updateFrequency(actionAt?: ContextTime) {
194
194
  if (this.finalFrequency === undefined) return;
195
195
 
196
196
  if (actionAt) {
197
- this.audioNode.frequency.setValueAtTime(
198
- this.finalFrequency,
199
- nt(actionAt),
200
- );
197
+ this.audioNode.frequency.setValueAtTime(this.finalFrequency, actionAt);
201
198
  } else {
202
199
  this.audioNode.frequency.value = this.finalFrequency;
203
200
  }
@@ -208,7 +205,7 @@ export class MonoOscillator extends Module<ModuleType.Oscillator> {
208
205
  }
209
206
 
210
207
  private initializeGainDetune() {
211
- this.detuneGain = new GainNode(this.context, { gain: 100 });
208
+ this.detuneGain = new GainNode(this.context.audioContext, { gain: 100 });
212
209
  this.detuneGain.connect(this.audioNode.detune);
213
210
  }
214
211
 
@@ -249,13 +246,13 @@ export default class Oscillator extends PolyModule<ModuleType.Oscillator> {
249
246
  this.registerDefaultIOs("out");
250
247
  }
251
248
 
252
- start(time: TTime) {
249
+ start(time: ContextTime) {
253
250
  this.audioModules.forEach((audioModule) => {
254
251
  audioModule.start(time);
255
252
  });
256
253
  }
257
254
 
258
- stop(time: TTime) {
255
+ stop(time: ContextTime) {
259
256
  this.audioModules.forEach((audioModule) => {
260
257
  audioModule.stop(time);
261
258
  });
@@ -1,4 +1,5 @@
1
- import { IAnyAudioContext, IModule, Module } from "@/core";
1
+ import { Context } from "@blibliki/utils";
2
+ import { IModule, Module } from "@/core";
2
3
  import { PropSchema } from "@/core/schema";
3
4
  import { CustomWorklet, newAudioWorklet } from "@/processors";
4
5
  import { ICreateModule, ModuleType } from ".";
@@ -41,7 +42,7 @@ export default class Scale extends Module<ModuleType.Scale> {
41
42
 
42
43
  constructor(engineId: string, params: ICreateModule<ModuleType.Scale>) {
43
44
  const props = { ...DEFAULT_PROPS, ...params.props };
44
- const audioNodeConstructor = (context: IAnyAudioContext) =>
45
+ const audioNodeConstructor = (context: Context) =>
45
46
  newAudioWorklet(context, CustomWorklet.ScaleProcessor);
46
47
 
47
48
  super(engineId, {
@@ -1,12 +1,11 @@
1
1
  import { INote, PropSchema, Module, IModule, MidiOutput } from "@/core";
2
- import { BarsBeatsSixteenths } from "@/core/Timing/Time";
3
2
  import { ICreateModule, ModuleType } from ".";
4
3
 
5
4
  export type IStepSequencer = IModule<ModuleType.StepSequencer>;
6
5
 
7
6
  export type ISequence = {
8
7
  active: boolean;
9
- time: BarsBeatsSixteenths;
8
+ // time: BarsBeatsSixteenths;
10
9
  duration: string;
11
10
  notes: INote[];
12
11
  };
@@ -1,5 +1,5 @@
1
+ import { ContextTime } from "@blibliki/transport";
1
2
  import { PropSchema, Module, IModule, MidiOutput, Note } from "@/core";
2
- import { TTime } from "@/core/Timing/Time";
3
3
  import MidiEvent from "@/core/midi/MidiEvent";
4
4
  import { ICreateModule, ModuleType } from ".";
5
5
 
@@ -37,13 +37,13 @@ export default class VirtualMidi extends Module<ModuleType.VirtualMidi> {
37
37
  this.midiOutput.onMidiEvent(midiEvent);
38
38
  }
39
39
 
40
- triggerAttack = (note: Note, triggerAttack: TTime) => {
40
+ triggerAttack = (note: Note, triggerAttack: ContextTime) => {
41
41
  this.props = { activeNotes: [...this.props.activeNotes, note.fullName] };
42
42
  this.triggerPropsUpdate();
43
43
  this.sendMidi(MidiEvent.fromNote(note, true, triggerAttack));
44
44
  };
45
45
 
46
- triggerRelease = (note: Note, triggerAttack: TTime) => {
46
+ triggerRelease = (note: Note, triggerAttack: ContextTime) => {
47
47
  this.props = {
48
48
  activeNotes: this.props.activeNotes.filter(
49
49
  (name) => name !== note.fullName,
@@ -1,5 +1,4 @@
1
- import { assertNever } from "@blibliki/utils";
2
- import { IAnyAudioContext } from "@/core";
1
+ import { assertNever, Context } from "@blibliki/utils";
3
2
  import { filterProcessorURL } from "./filter-processor";
4
3
  import { scaleProcessorURL } from "./scale-processor";
5
4
 
@@ -8,20 +7,17 @@ export enum CustomWorklet {
8
7
  FilterProcessor = "FilterProcessor",
9
8
  }
10
9
 
11
- export async function loadProcessors(context: IAnyAudioContext) {
12
- await context.audioWorklet.addModule(scaleProcessorURL);
13
- await context.audioWorklet.addModule(filterProcessorURL);
10
+ export async function loadProcessors(context: Context) {
11
+ await context.addModule(scaleProcessorURL);
12
+ await context.addModule(filterProcessorURL);
14
13
  }
15
14
 
16
- export function newAudioWorklet(
17
- context: IAnyAudioContext,
18
- worklet: CustomWorklet,
19
- ) {
15
+ export function newAudioWorklet(context: Context, worklet: CustomWorklet) {
20
16
  switch (worklet) {
21
17
  case CustomWorklet.ScaleProcessor:
22
- return new AudioWorkletNode(context, "scale-processor");
18
+ return context.newAudioWorklet("scale-processor");
23
19
  case CustomWorklet.FilterProcessor:
24
- return new AudioWorkletNode(context, "filter-processor");
20
+ return context.newAudioWorklet("filter-processor");
25
21
  default:
26
22
  assertNever(worklet);
27
23
  }
@@ -1,37 +0,0 @@
1
- import { Scheduler as InternalScheduler } from "@ircam/sc-scheduling";
2
- import { now } from ".";
3
- import { nt, t, TTime } from "./Time";
4
- import Transport from "./Transport";
5
-
6
- export default class Scheduler {
7
- transport: Transport;
8
- internalScheduler: InternalScheduler;
9
-
10
- constructor(transport: Transport) {
11
- this.transport = transport;
12
- this.internalScheduler = new InternalScheduler(now, {
13
- currentTimeToProcessorTimeFunction: () => this.transport.playhead,
14
- });
15
- }
16
-
17
- start(actionAt: TTime, callback: () => void) {
18
- this.internalScheduler.add(this.processor, nt(actionAt));
19
- this.defer(callback, actionAt);
20
- }
21
-
22
- stop(actionAt: TTime, callback: () => void) {
23
- this.defer(() => {
24
- this.internalScheduler.remove(this.processor);
25
- callback();
26
- }, actionAt);
27
- }
28
-
29
- defer(callback: () => void, actionAt: TTime) {
30
- this.internalScheduler.defer(callback, nt(actionAt));
31
- }
32
-
33
- private processor = (currentTime: number, playhead: TTime) => {
34
- console.log(`playhead: ${t(playhead).toNotation()}`);
35
- return currentTime + 0.5;
36
- };
37
- }
@@ -1,103 +0,0 @@
1
- import { isNumber } from "@blibliki/utils";
2
- import { Engine } from "@/Engine";
3
- import { now } from ".";
4
-
5
- export type BarsBeatsSixteenths = `${number}:${number}:${number}`;
6
- export type TTime = number | BarsBeatsSixteenths | Time;
7
-
8
- export const t = (value?: TTime): Time => {
9
- value ??= now();
10
-
11
- if (value instanceof Time) return value;
12
-
13
- return new Time(value);
14
- };
15
-
16
- export const nt = (value?: TTime): number => {
17
- value ??= now();
18
-
19
- if (typeof value === "number") return value;
20
-
21
- return t(value).toNumber();
22
- };
23
-
24
- export default class Time {
25
- private value: number | BarsBeatsSixteenths;
26
- private _notation?: BarsBeatsSixteenths;
27
- private _number?: number;
28
-
29
- constructor(value: TTime) {
30
- this.value = value instanceof Time ? value.value : value;
31
-
32
- if (isNumber(this.value)) {
33
- this._number = this.value;
34
- } else {
35
- this._notation = this.value;
36
- }
37
- }
38
-
39
- add(value: TTime): Time {
40
- return t(this.toNumber() + t(value).toNumber());
41
- }
42
-
43
- subtrack(value: TTime): Time {
44
- return t(this.toNumber() - nt(value));
45
- }
46
-
47
- isBefore(value: TTime): boolean {
48
- return this.toNumber() < nt(value);
49
- }
50
-
51
- isAfter(value: TTime): boolean {
52
- return this.toNumber() > nt(value);
53
- }
54
-
55
- isEqual(value: TTime): boolean {
56
- return this.toNumber() > nt(value);
57
- }
58
-
59
- toNotation(): BarsBeatsSixteenths {
60
- if (this._notation) return this._notation;
61
-
62
- const [count, factor] = this.transport.timeSignature;
63
- const divisionsPerBeat = 16 / factor;
64
- const divisionsPerBar = count * divisionsPerBeat;
65
- const secondsPerBeat = 60 / this.transport.bpm;
66
-
67
- const totalDivisions = Math.floor(
68
- ((this.value as number) / secondsPerBeat) * divisionsPerBeat,
69
- );
70
-
71
- const bars = Math.floor(totalDivisions / divisionsPerBar);
72
- const beats = Math.floor(
73
- (totalDivisions % divisionsPerBar) / divisionsPerBeat,
74
- );
75
- const divisions = totalDivisions % divisionsPerBeat;
76
-
77
- this._notation = `${bars}:${beats}:${divisions}`;
78
-
79
- return this._notation;
80
- }
81
-
82
- toNumber(): number {
83
- if (isNumber(this._number)) return this._number;
84
-
85
- const [beatsPerBar, sixteenths] = this.transport.timeSignature;
86
- const secondsPerBeat = 60 / this.transport.bpm;
87
-
88
- const [bars, beats, divisions] = (this.value as BarsBeatsSixteenths)
89
- .split(":")
90
- .map(Number);
91
-
92
- const totalBeats =
93
- bars * beatsPerBar + beats + divisions / (sixteenths / 4);
94
-
95
- this._number = totalBeats * secondsPerBeat;
96
-
97
- return this._number;
98
- }
99
-
100
- private get transport() {
101
- return Engine.current.transport;
102
- }
103
- }
@@ -1,104 +0,0 @@
1
- import { now } from ".";
2
- import Scheduler from "./Scheduler";
3
- import Time, { t, TTime } from "./Time";
4
-
5
- export enum TransportState {
6
- playing = "playing",
7
- stopped = "stopped",
8
- paused = "paused",
9
- }
10
-
11
- export type TransportEvents = {
12
- start: { actionAt: TTime; offset: TTime };
13
- stop: { actionAt: TTime };
14
- pause: { actionAt: TTime };
15
- };
16
-
17
- type TPlaybackCallback = (actionAt: TTime) => void;
18
-
19
- type TransportProps = {
20
- onStart?: TPlaybackCallback;
21
- onStop?: TPlaybackCallback;
22
- };
23
-
24
- export default class Transport {
25
- bpm = 120;
26
- timeSignature: [number, number] = [4, 4];
27
- loopStart: TTime;
28
- loopEnd?: TTime;
29
-
30
- state: TransportState = TransportState.stopped;
31
- offset: TTime = 0;
32
-
33
- private startTime: TTime = 0;
34
- private onStart: TransportProps["onStart"];
35
- private onStop: TransportProps["onStop"];
36
- private scheduler: Scheduler;
37
-
38
- constructor(props: TransportProps) {
39
- this.onStart = props.onStart;
40
- this.onStop = props.onStop;
41
- this.loopStart = t("0:0:0");
42
- this.scheduler = new Scheduler(this);
43
- }
44
-
45
- start({
46
- offset = this.offset,
47
- actionAt = now(),
48
- }: {
49
- offset?: TTime;
50
- actionAt?: TTime;
51
- }) {
52
- if (this.state === TransportState.playing) return;
53
-
54
- this.validateFutureTime(actionAt);
55
-
56
- this.scheduler.start(actionAt, () => {
57
- this.state = TransportState.playing;
58
- this.offset = offset;
59
- this.startTime = t(actionAt).subtrack(this.offset);
60
- });
61
- this.onStart?.(actionAt);
62
-
63
- return actionAt;
64
- }
65
-
66
- stop({ actionAt: actionAt = now() }: { actionAt?: TTime }) {
67
- if (this.state === TransportState.stopped) return;
68
-
69
- this.validateFutureTime(actionAt);
70
-
71
- this.scheduler.stop(actionAt, () => {
72
- this.state = TransportState.stopped;
73
- this.offset = 0;
74
- });
75
- this.onStop?.(actionAt);
76
-
77
- return actionAt;
78
- }
79
-
80
- pause({ actionAt: actionAt = now() }: { actionAt?: TTime }) {
81
- if (this.state === TransportState.paused) return;
82
-
83
- this.validateFutureTime(actionAt);
84
-
85
- this.scheduler.stop(actionAt, () => {
86
- this.state = TransportState.paused;
87
- this.offset = t(actionAt).subtrack(this.startTime);
88
- });
89
- this.onStop?.(actionAt);
90
-
91
- return actionAt;
92
- }
93
-
94
- get playhead(): Time {
95
- if (this.state === TransportState.stopped) return t(0);
96
- if (this.state === TransportState.paused) return t(this.offset);
97
-
98
- return t(now()).subtrack(this.startTime);
99
- }
100
-
101
- private validateFutureTime(time: TTime) {
102
- if (t(time).isBefore(now())) throw Error("Past time not allowed");
103
- }
104
- }
@@ -1,16 +0,0 @@
1
- import { Engine } from "@/Engine";
2
-
3
- export { default as Transport, TransportState } from "./Transport";
4
- export { default as Time, t, nt } from "./Time";
5
-
6
- export type { TransportEvents } from "./Transport";
7
- export type { TTime } from "./Time";
8
-
9
- export function now() {
10
- return Engine.current.context.currentTime;
11
- }
12
-
13
- export function browserToContextTime(time: number): number {
14
- const differenceBetweenClocks = performance.now() / 1000 - now();
15
- return time / 1000 - differenceBetweenClocks;
16
- }