@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blibliki/engine",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.cjs",
@@ -20,7 +20,8 @@
20
20
  "@ircam/sc-scheduling": "^1.0.0",
21
21
  "node-web-audio-api": "^1.0.3",
22
22
  "webmidi": "^3.1.14",
23
- "@blibliki/utils": "^0.3.3"
23
+ "@blibliki/transport": "^0.3.4",
24
+ "@blibliki/utils": "^0.3.4"
24
25
  },
25
26
  "scripts": {
26
27
  "build": "tsup",
package/src/Engine.ts CHANGED
@@ -1,14 +1,18 @@
1
- import { assertDefined, Optional, pick, uuidv4 } from "@blibliki/utils";
2
1
  import {
3
- IAnyAudioContext,
4
- IRoute,
5
- Routes,
6
- MidiDeviceManager,
7
- IModule,
8
- TTime,
2
+ ContextTime,
3
+ Ticks,
4
+ TimeSignature,
9
5
  Transport,
10
- MidiEvent,
11
- } from "@/core";
6
+ TransportEvent,
7
+ } from "@blibliki/transport";
8
+ import {
9
+ assertDefined,
10
+ Context,
11
+ Optional,
12
+ pick,
13
+ uuidv4,
14
+ } from "@blibliki/utils";
15
+ import { IRoute, Routes, MidiDeviceManager, IModule, MidiEvent } from "@/core";
12
16
  import {
13
17
  ICreateModule,
14
18
  ModuleParams,
@@ -37,10 +41,10 @@ export class Engine {
37
41
  ) => void)[] = [];
38
42
 
39
43
  readonly id: string;
40
- context: IAnyAudioContext;
44
+ context: Context;
41
45
  isInitialized = false;
42
46
  routes: Routes;
43
- transport: Transport;
47
+ transport: Transport<TransportEvent>;
44
48
  modules: Map<
45
49
  string,
46
50
  ModuleTypeToModuleMapping[keyof ModuleTypeToModuleMapping]
@@ -61,17 +65,29 @@ export class Engine {
61
65
  return this.getById(this._currentId);
62
66
  }
63
67
 
64
- constructor(context: IAnyAudioContext) {
68
+ constructor(context: Context) {
65
69
  this.id = uuidv4();
66
70
 
67
71
  this.context = context;
68
- this.transport = new Transport({
72
+ this.transport = new Transport(this.context, {
73
+ generator: (_start: Ticks, _end: Ticks) => {
74
+ return [] as TransportEvent[];
75
+ },
76
+ consumer: (_event: TransportEvent) => {
77
+ return;
78
+ },
79
+ onJump: (_ticks: Ticks) => {
80
+ return;
81
+ },
69
82
  onStart: this.onStart,
70
83
  onStop: this.onStop,
84
+ silence: (_actionAt: ContextTime) => {
85
+ return;
86
+ },
71
87
  });
72
88
  this.routes = new Routes(this);
73
89
  this.modules = new Map();
74
- this.midiDeviceManager = new MidiDeviceManager();
90
+ this.midiDeviceManager = new MidiDeviceManager(this.context);
75
91
 
76
92
  Engine._engines.set(this.id, this);
77
93
  Engine._currentId = this.id;
@@ -142,17 +158,18 @@ export class Engine {
142
158
  );
143
159
  }
144
160
 
145
- async start(props: { offset?: TTime; actionAt?: TTime } = {}) {
161
+ async start() {
146
162
  await this.resume();
147
- this.transport.start(props);
163
+ this.transport.start();
148
164
  }
149
165
 
150
- stop(props: { actionAt?: TTime } = {}) {
151
- this.transport.stop(props);
166
+ stop() {
167
+ this.transport.stop();
168
+ this.transport.reset();
152
169
  }
153
170
 
154
- pause(props: { actionAt?: TTime } = {}) {
155
- this.transport.pause(props);
171
+ pause() {
172
+ this.transport.stop();
156
173
  }
157
174
 
158
175
  get bpm() {
@@ -167,14 +184,10 @@ export class Engine {
167
184
  return this.transport.timeSignature;
168
185
  }
169
186
 
170
- set timeSignature(value: [number, number]) {
187
+ set timeSignature(value: TimeSignature) {
171
188
  this.transport.timeSignature = value;
172
189
  }
173
190
 
174
- get playhead() {
175
- return this.transport.playhead;
176
- }
177
-
178
191
  async resume() {
179
192
  await this.context.resume();
180
193
  }
@@ -186,8 +199,6 @@ export class Engine {
186
199
  module.dispose();
187
200
  });
188
201
  this.modules.clear();
189
- Engine._engines.delete(this.id);
190
- Engine._currentId = Engine._engines.keys().next().value;
191
202
  }
192
203
 
193
204
  findModule(
@@ -224,16 +235,20 @@ export class Engine {
224
235
  if (virtualMidi.moduleType !== ModuleType.VirtualMidi)
225
236
  throw Error("This is not a virtual mid");
226
237
 
227
- virtualMidi.sendMidi(MidiEvent.fromNote(noteName, type === "noteOn"));
238
+ virtualMidi.sendMidi(
239
+ MidiEvent.fromNote(noteName, type === "noteOn", this.context.currentTime),
240
+ );
228
241
  }
229
242
 
230
- private onStart = (actionAt: TTime) => {
243
+ // actionAt is context time
244
+ private onStart = (actionAt: ContextTime) => {
231
245
  this.modules.forEach((module) => {
232
246
  module.start(actionAt);
233
247
  });
234
248
  };
235
249
 
236
- private onStop = (actionAt: TTime) => {
250
+ // actionAt is context time
251
+ private onStop = (actionAt: ContextTime) => {
237
252
  this.modules.forEach((module) => {
238
253
  module.stop(actionAt);
239
254
  });
@@ -1,5 +1,5 @@
1
+ import { Seconds } from "@blibliki/transport";
1
2
  import { Message } from "webmidi";
2
- import { TTime } from "../Timing/Time";
3
3
  import frequencyTable from "./frequencyTable";
4
4
 
5
5
  const Notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
@@ -10,7 +10,7 @@ export type INote = {
10
10
  name: string;
11
11
  octave: number;
12
12
  frequency: number;
13
- duration?: TTime;
13
+ duration?: Seconds;
14
14
  velocity?: number;
15
15
  };
16
16
 
@@ -19,7 +19,7 @@ export default class Note implements INote {
19
19
  name!: string;
20
20
  octave!: number;
21
21
  velocity = 1;
22
- duration?: TTime;
22
+ duration?: Seconds;
23
23
 
24
24
  static fromFrequency(frequency: number) {
25
25
  let noteName: string | undefined;
package/src/core/index.ts CHANGED
@@ -19,9 +19,6 @@ export type {
19
19
  IIOSerialize,
20
20
  } from "./IO";
21
21
 
22
- export { Time, TransportState, Transport, t, nt } from "./Timing";
23
- export type { TTime } from "./Timing";
24
-
25
22
  export type {
26
23
  PropDefinition,
27
24
  PropSchema,
@@ -1,3 +1,4 @@
1
+ import { Context } from "@blibliki/utils";
1
2
  import Note from "../Note";
2
3
  import { EventListerCallback, IMidiInput, MidiPortState } from "./MidiDevice";
3
4
  import MidiEvent from "./MidiEvent";
@@ -32,12 +33,14 @@ export default class ComputerKeyboardInput implements IMidiInput {
32
33
  name: string;
33
34
  state: MidiPortState;
34
35
  eventListerCallbacks: EventListerCallback[] = [];
36
+ private context: Readonly<Context>;
35
37
 
36
- constructor() {
38
+ constructor(context: Context) {
37
39
  const { id, name, state } = computerKeyboardData();
38
40
  this.id = id;
39
41
  this.name = name;
40
42
  this.state = state;
43
+ this.context = context;
41
44
 
42
45
  document.addEventListener("keydown", this.onKeyTrigger(true));
43
46
  document.addEventListener("keyup", this.onKeyTrigger(false));
@@ -63,7 +66,11 @@ export default class ComputerKeyboardInput implements IMidiInput {
63
66
  const note = this.extractNote(event);
64
67
  if (!note) return;
65
68
 
66
- const midiEvent = MidiEvent.fromNote(note, noteOn);
69
+ const midiEvent = MidiEvent.fromNote(
70
+ note,
71
+ noteOn,
72
+ this.context.browserToContextTime(event.timeStamp),
73
+ );
67
74
  this.eventListerCallbacks.forEach((callback) => {
68
75
  callback(midiEvent);
69
76
  });
@@ -1,5 +1,5 @@
1
+ import { Context } from "@blibliki/utils";
1
2
  import { MessageEvent, Input } from "webmidi";
2
- import { browserToContextTime } from "../Timing";
3
3
  import MidiEvent, { MidiEventType } from "./MidiEvent";
4
4
 
5
5
  export enum MidiPortState {
@@ -24,12 +24,14 @@ export default class MidiDevice implements IMidiDevice {
24
24
  name: string;
25
25
  eventListerCallbacks: EventListerCallback[] = [];
26
26
 
27
+ private context: Readonly<Context>;
27
28
  private input: Input;
28
29
 
29
- constructor(input: Input) {
30
+ constructor(input: Input, context: Context) {
30
31
  this.id = input.id;
31
32
  this.name = input.name || `Device ${input.id}`;
32
33
  this.input = input;
34
+ this.context = context;
33
35
 
34
36
  this.connect();
35
37
  }
@@ -67,7 +69,7 @@ export default class MidiDevice implements IMidiDevice {
67
69
  private processEvent(event: MessageEvent) {
68
70
  const midiEvent = new MidiEvent(
69
71
  event.message,
70
- browserToContextTime(event.timestamp),
72
+ this.context.browserToContextTime(event.timestamp),
71
73
  );
72
74
 
73
75
  switch (midiEvent.type) {
@@ -1,3 +1,4 @@
1
+ import { Context } from "@blibliki/utils";
1
2
  import { Input, Output, WebMidi } from "webmidi";
2
3
  import ComputerKeyboardDevice from "./ComputerKeyboardDevice";
3
4
  import MidiDevice from "./MidiDevice";
@@ -8,10 +9,11 @@ export default class MidiDeviceManager {
8
9
  devices = new Map<string, MidiDevice | ComputerKeyboardDevice>();
9
10
  private initialized = false;
10
11
  private listeners: ListenerCallback[] = [];
12
+ private context: Readonly<Context>;
11
13
 
12
- constructor() {
13
- const computerKeyboardDevice = new ComputerKeyboardDevice();
14
- this.devices.set(computerKeyboardDevice.id, computerKeyboardDevice);
14
+ constructor(context: Context) {
15
+ this.context = context;
16
+ this.addComputerKeyboard();
15
17
  }
16
18
 
17
19
  async initialize() {
@@ -37,7 +39,7 @@ export default class MidiDeviceManager {
37
39
 
38
40
  WebMidi.inputs.forEach((input) => {
39
41
  if (!this.devices.has(input.id)) {
40
- this.devices.set(input.id, new MidiDevice(input));
42
+ this.devices.set(input.id, new MidiDevice(input, this.context));
41
43
  }
42
44
  });
43
45
  } catch (err) {
@@ -45,6 +47,13 @@ export default class MidiDeviceManager {
45
47
  }
46
48
  }
47
49
 
50
+ private addComputerKeyboard() {
51
+ if (typeof document === "undefined") return;
52
+
53
+ const computerKeyboardDevice = new ComputerKeyboardDevice(this.context);
54
+ this.devices.set(computerKeyboardDevice.id, computerKeyboardDevice);
55
+ }
56
+
48
57
  private listenChanges() {
49
58
  WebMidi.addListener("connected", (event) => {
50
59
  const port = event.port as Input | Output;
@@ -52,7 +61,7 @@ export default class MidiDeviceManager {
52
61
 
53
62
  if (this.devices.has(port.id)) return;
54
63
 
55
- const device = new MidiDevice(port);
64
+ const device = new MidiDevice(port, this.context);
56
65
  this.devices.set(device.id, device);
57
66
 
58
67
  this.listeners.forEach((listener) => {
@@ -1,6 +1,6 @@
1
+ import { ContextTime } from "@blibliki/transport";
1
2
  import { Message } from "webmidi";
2
3
  import Note, { INote } from "../Note";
3
- import { t, TTime } from "../Timing/Time";
4
4
 
5
5
  export enum MidiEventType {
6
6
  noteOn = "noteon",
@@ -11,29 +11,33 @@ export enum MidiEventType {
11
11
  export default class MidiEvent {
12
12
  note?: Note;
13
13
  voiceNo?: number;
14
- readonly triggeredAt: TTime;
14
+ readonly triggeredAt: ContextTime;
15
15
  private message: Message;
16
16
 
17
17
  static fromNote(
18
18
  noteName: string | Note | Omit<INote, "frequency">,
19
19
  noteOn = true,
20
- triggeredAt?: TTime,
20
+ triggeredAt: ContextTime,
21
21
  ): MidiEvent {
22
22
  const note = noteName instanceof Note ? noteName : new Note(noteName);
23
23
 
24
24
  return new MidiEvent(new Message(note.midiData(noteOn)), triggeredAt);
25
25
  }
26
26
 
27
- static fromCC(cc: number, value: number, triggeredAt?: TTime): MidiEvent {
27
+ static fromCC(
28
+ cc: number,
29
+ value: number,
30
+ triggeredAt: ContextTime,
31
+ ): MidiEvent {
28
32
  return new MidiEvent(
29
33
  new Message(new Uint8Array([0xb0, cc, value])),
30
34
  triggeredAt,
31
35
  );
32
36
  }
33
37
 
34
- constructor(message: Message, triggeredAt?: TTime) {
38
+ constructor(message: Message, triggeredAt: ContextTime) {
35
39
  this.message = message;
36
- this.triggeredAt = triggeredAt ?? t();
40
+ this.triggeredAt = triggeredAt;
37
41
  this.defineNotes();
38
42
  }
39
43
 
@@ -1,7 +1,7 @@
1
- import { Optional, upperFirst, uuidv4 } from "@blibliki/utils";
1
+ import { ContextTime } from "@blibliki/transport";
2
+ import { Context, Optional, upperFirst, uuidv4 } from "@blibliki/utils";
2
3
  import { Engine } from "@/Engine";
3
4
  import { AnyModule, ModuleType, ModuleTypeToPropsMapping } from "@/modules";
4
- import { IAnyAudioContext } from "../";
5
5
  import {
6
6
  AudioInputProps,
7
7
  AudioOutputProps,
@@ -13,7 +13,6 @@ import {
13
13
  MidiOutputProps,
14
14
  } from "../IO";
15
15
  import Note from "../Note";
16
- import { TTime } from "../Timing/Time";
17
16
  import MidiEvent, { MidiEventType } from "../midi/MidiEvent";
18
17
 
19
18
  export type IModule<T extends ModuleType> = {
@@ -33,7 +32,7 @@ export type IModuleConstructor<T extends ModuleType> = Optional<
33
32
  IModule<T>,
34
33
  "id" | "voiceNo"
35
34
  > & {
36
- audioNodeConstructor?: (context: IAnyAudioContext) => AudioNode;
35
+ audioNodeConstructor?: (context: Context) => AudioNode;
37
36
  };
38
37
 
39
38
  export abstract class Module<T extends ModuleType> implements IModule<T> {
@@ -130,21 +129,21 @@ export abstract class Module<T extends ModuleType> implements IModule<T> {
130
129
  this.outputs.unPlugAll();
131
130
  }
132
131
 
133
- start(_time: TTime): void {
132
+ start(_time: ContextTime): void {
134
133
  // Optional implementation in modules
135
134
  }
136
135
 
137
- stop(_time: TTime): void {
136
+ stop(_time: ContextTime): void {
138
137
  // Optional implementation in modules
139
138
  }
140
139
 
141
- triggerAttack(note: Note, _triggeredAt: TTime): void {
140
+ triggerAttack(note: Note, _triggeredAt: ContextTime): void {
142
141
  if (this.activeNotes.some((n) => n.fullName === note.fullName)) return;
143
142
 
144
143
  this.activeNotes.push(note);
145
144
  }
146
145
 
147
- triggerRelease(note: Note, _triggeredAt: TTime): void {
146
+ triggerRelease(note: Note, _triggeredAt: ContextTime): void {
148
147
  this.activeNotes = this.activeNotes.filter(
149
148
  (n) => n.fullName !== note.fullName,
150
149
  );
@@ -1,3 +1,4 @@
1
+ import { ContextTime } from "@blibliki/transport";
1
2
  import { deterministicId, Optional, uuidv4 } from "@blibliki/utils";
2
3
  import { Engine } from "@/Engine";
3
4
  import { ModuleType, ModuleTypeToPropsMapping } from "@/modules";
@@ -10,7 +11,6 @@ import {
10
11
  OutputCollection,
11
12
  } from "../IO";
12
13
  import { PolyAudioInputProps, PolyAudioOutputProps } from "../IO/PolyAudioIO";
13
- import { TTime } from "../Timing";
14
14
  import MidiEvent from "../midi/MidiEvent";
15
15
  import { IModule, IModuleConstructor, Module } from "./Module";
16
16
 
@@ -101,13 +101,13 @@ export abstract class PolyModule<T extends ModuleType>
101
101
  this.rePlugAll();
102
102
  }
103
103
 
104
- start(time: TTime): void {
104
+ start(time: ContextTime): void {
105
105
  this.audioModules.forEach((m) => {
106
106
  m.start(time);
107
107
  });
108
108
  }
109
109
 
110
- stop(time: TTime): void {
110
+ stop(time: ContextTime): void {
111
111
  this.audioModules.forEach((m) => {
112
112
  m.stop(time);
113
113
  });
@@ -1,7 +1,7 @@
1
+ import { ContextTime } from "@blibliki/transport";
1
2
  import { EmptyObject } from "@blibliki/utils";
2
3
  import { ICreateModule, ModuleType } from "@/modules";
3
4
  import { MidiOutput } from "../IO";
4
- import { nt, t, TTime } from "../Timing/Time";
5
5
  import MidiEvent, { MidiEventType } from "../midi/MidiEvent";
6
6
  import { PropSchema } from "../schema";
7
7
  import { IModuleConstructor, Module } from "./Module";
@@ -14,7 +14,7 @@ const DEFAULT_PROPS = {};
14
14
  class Voice extends Module<ModuleType.VoiceScheduler> {
15
15
  declare audioNode: undefined;
16
16
  activeNote: string | null = null;
17
- triggeredAt: TTime = t(0);
17
+ triggeredAt: ContextTime = 0;
18
18
 
19
19
  constructor(
20
20
  engineId: string,
@@ -102,7 +102,7 @@ export default class VoiceScheduler extends PolyModule<ModuleType.VoiceScheduler
102
102
 
103
103
  // If no available voice, get the one with the lowest triggeredAt
104
104
  voice ??= this.audioModules.sort((a, b) => {
105
- return nt(a.triggeredAt) - nt(b.triggeredAt);
105
+ return a.triggeredAt - b.triggeredAt;
106
106
  })[0];
107
107
 
108
108
  return voice;
package/src/index.ts CHANGED
@@ -16,10 +16,13 @@ export type {
16
16
  BooleanProp,
17
17
  ArrayProp,
18
18
  INote,
19
- t,
20
- nt,
21
19
  } from "./core";
22
- export { TransportState, MidiDevice, MidiPortState, Note } from "./core";
20
+ export { MidiDevice, MidiPortState, Note } from "./core";
21
+
22
+ export { TransportState } from "@blibliki/transport";
23
+ export type { TimeSignature, Position } from "@blibliki/transport";
24
+
25
+ export { Context } from "@blibliki/utils";
23
26
 
24
27
  export { ModuleType, moduleSchemas, OscillatorWave } from "./modules";
25
28
  export type {
@@ -1,5 +1,5 @@
1
- import { IAnyAudioContext, Module } from "@/core";
2
- import { IModuleConstructor } from "@/core/module/Module";
1
+ import { Context } from "@blibliki/utils";
2
+ import { IModuleConstructor, Module } from "@/core/module/Module";
3
3
  import { IPolyModuleConstructor, PolyModule } from "@/core/module/PolyModule";
4
4
  import { PropSchema } from "@/core/schema";
5
5
  import { createModule, ICreateModule, ModuleType } from ".";
@@ -63,8 +63,8 @@ class MonoBiquadFilter extends Module<ModuleType.BiquadFilter> {
63
63
  ) {
64
64
  const props = { ...DEFAULT_PROPS, ...params.props };
65
65
 
66
- const audioNodeConstructor = (context: IAnyAudioContext) =>
67
- new BiquadFilterNode(context, {
66
+ const audioNodeConstructor = (context: Context) =>
67
+ new BiquadFilterNode(context.audioContext, {
68
68
  type: props.type,
69
69
  frequency: props.cutoff,
70
70
  Q: props.Q,
@@ -1,6 +1,7 @@
1
- import { IAnyAudioContext, IModule, Module } from "@/core";
1
+ import { ContextTime } from "@blibliki/transport";
2
+ import { Context } from "@blibliki/utils";
3
+ import { IModule, Module } from "@/core";
2
4
  import Note from "@/core/Note";
3
- import { nt, TTime } from "@/core/Timing/Time";
4
5
  import { PropSchema } from "@/core/schema";
5
6
  import { ICreateModule, ModuleType } from ".";
6
7
 
@@ -27,8 +28,8 @@ export default class Constant extends Module<ModuleType.Constant> {
27
28
 
28
29
  constructor(engineId: string, params: ICreateModule<ModuleType.Constant>) {
29
30
  const props = { ...DEFAULT_PROPS, ...params.props };
30
- const audioNodeConstructor = (context: IAnyAudioContext) =>
31
- new ConstantSourceNode(context);
31
+ const audioNodeConstructor = (context: Context) =>
32
+ new ConstantSourceNode(context.audioContext);
32
33
 
33
34
  super(engineId, {
34
35
  ...params,
@@ -43,17 +44,17 @@ export default class Constant extends Module<ModuleType.Constant> {
43
44
  this.audioNode.offset.value = value;
44
45
  }
45
46
 
46
- start(time: TTime) {
47
+ start(time: ContextTime) {
47
48
  if (this.isStated) return;
48
49
 
49
50
  this.isStated = true;
50
- this.audioNode.start(nt(time));
51
+ this.audioNode.start(time);
51
52
  }
52
53
 
53
- stop(time: TTime) {
54
- this.audioNode.stop(nt(time));
54
+ stop(time: ContextTime) {
55
+ this.audioNode.stop(time);
55
56
  this.rePlugAll(() => {
56
- this.audioNode = new ConstantSourceNode(this.context, {
57
+ this.audioNode = new ConstantSourceNode(this.context.audioContext, {
57
58
  offset: this.props.value,
58
59
  });
59
60
  });
@@ -61,8 +62,8 @@ export default class Constant extends Module<ModuleType.Constant> {
61
62
  this.isStated = false;
62
63
  }
63
64
 
64
- triggerAttack = (note: Note, triggeredAt: TTime) => {
65
- this.audioNode.offset.setValueAtTime(note.frequency, nt(triggeredAt));
65
+ triggerAttack = (note: Note, triggeredAt: ContextTime) => {
66
+ this.audioNode.offset.setValueAtTime(note.frequency, triggeredAt);
66
67
  this.start(triggeredAt);
67
68
  };
68
69
 
@@ -1,7 +1,7 @@
1
- import { createScaleNormalized } from "@blibliki/utils";
2
- import { IAnyAudioContext, Module } from "@/core";
1
+ import { ContextTime } from "@blibliki/transport";
2
+ import { Context, createScaleNormalized } from "@blibliki/utils";
3
+ import { 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";
@@ -67,8 +67,8 @@ class MonoEnvelope extends Module<ModuleType.Envelope> {
67
67
 
68
68
  constructor(engineId: string, params: ICreateModule<ModuleType.Envelope>) {
69
69
  const props = { ...DEFAULT_PROPS, ...params.props };
70
- const audioNodeConstructor = (context: IAnyAudioContext) => {
71
- const audioNode = new GainNode(context);
70
+ const audioNodeConstructor = (context: Context) => {
71
+ const audioNode = new GainNode(context.audioContext);
72
72
  audioNode.gain.value = 0;
73
73
  return audioNode;
74
74
  };
@@ -82,65 +82,60 @@ class MonoEnvelope extends Module<ModuleType.Envelope> {
82
82
  this.registerDefaultIOs();
83
83
  }
84
84
 
85
- triggerAttack(note: Note, triggeredAt: TTime) {
85
+ triggerAttack(note: Note, triggeredAt: ContextTime) {
86
86
  super.triggerAttack(note, triggeredAt);
87
87
 
88
88
  const attack = this.scaledAttack();
89
89
  const decay = this.scaledDecay();
90
90
  const sustain = this.props.sustain;
91
- const triggeredAtNum = nt(triggeredAt);
92
91
 
93
- this.audioNode.gain.cancelAndHoldAtTime(triggeredAtNum);
92
+ this.audioNode.gain.cancelAndHoldAtTime(triggeredAt);
94
93
 
95
94
  // Always start from a tiny value, can't ramp from 0
96
95
  if (this.audioNode.gain.value === 0) {
97
- this.audioNode.gain.setValueAtTime(0.001, triggeredAtNum);
96
+ this.audioNode.gain.setValueAtTime(0.001, triggeredAt);
98
97
  }
99
98
 
100
99
  // Attack
101
- this.audioNode.gain.exponentialRampToValueAtTime(
102
- 1.0,
103
- triggeredAtNum + attack,
104
- );
100
+ this.audioNode.gain.exponentialRampToValueAtTime(1.0, triggeredAt + attack);
105
101
 
106
102
  // Decay
107
103
  if (sustain > 0) {
108
104
  this.audioNode.gain.exponentialRampToValueAtTime(
109
105
  sustain,
110
- triggeredAtNum + attack + decay,
106
+ triggeredAt + attack + decay,
111
107
  );
112
108
  // Do not set to zero or anything else!
113
109
  } else {
114
110
  this.audioNode.gain.exponentialRampToValueAtTime(
115
111
  0.001,
116
- triggeredAtNum + attack + decay,
112
+ triggeredAt + attack + decay,
117
113
  );
118
114
  }
119
115
  }
120
116
 
121
- triggerRelease(note: Note, triggeredAt: TTime) {
117
+ triggerRelease(note: Note, triggeredAt: ContextTime) {
122
118
  super.triggerRelease(note, triggeredAt);
123
119
  if (this.activeNotes.length > 0) return;
124
120
 
125
121
  const release = this.scaledRelease();
126
- const triggeredAtNum = nt(triggeredAt);
127
122
 
128
123
  // Cancel scheduled automations and set gain to the ACTUAL value at this moment
129
- this.audioNode.gain.cancelAndHoldAtTime(triggeredAtNum);
124
+ this.audioNode.gain.cancelAndHoldAtTime(triggeredAt);
130
125
  const currentGainValue = this.audioNode.gain.value;
131
126
 
132
127
  if (currentGainValue >= 0.0001) {
133
128
  // Always set the value at the release time to ensure a smooth ramp from here
134
- this.audioNode.gain.setValueAtTime(currentGainValue, triggeredAtNum);
129
+ this.audioNode.gain.setValueAtTime(currentGainValue, triggeredAt);
135
130
  // Exponential ramp to a tiny value
136
131
  this.audioNode.gain.exponentialRampToValueAtTime(
137
132
  0.0001,
138
- triggeredAtNum + release - 0.0001,
133
+ triggeredAt + release - 0.0001,
139
134
  );
140
135
  }
141
136
 
142
137
  // Set to zero at the very end
143
- this.audioNode.gain.setValueAtTime(0, triggeredAtNum + release);
138
+ this.audioNode.gain.setValueAtTime(0, triggeredAt + release);
144
139
  }
145
140
 
146
141
  private scaledAttack() {