@react-synth/synth 0.0.6-alpha
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/README.md +187 -0
- package/dist/cli.cjs +127 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +126 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1702 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +1701 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/samples/bd_haus.flac +0 -0
- package/src/samples/drum_cymbal_closed.flac +0 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1702 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const nodeWebAudioApi = require("node-web-audio-api");
|
|
6
|
+
const promises = require("fs/promises");
|
|
7
|
+
const url = require("url");
|
|
8
|
+
const module$1 = require("module");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
11
|
+
let schedulerInstance;
|
|
12
|
+
let audioContextInstance;
|
|
13
|
+
class Scheduler {
|
|
14
|
+
context;
|
|
15
|
+
_bpm;
|
|
16
|
+
startTime;
|
|
17
|
+
events = /* @__PURE__ */ new Map();
|
|
18
|
+
timerID = null;
|
|
19
|
+
running = false;
|
|
20
|
+
lookahead = 0.1;
|
|
21
|
+
scheduleInterval = 25;
|
|
22
|
+
/** Grace period before actually removing a loop - allows React HMR to re-add it */
|
|
23
|
+
removalGracePeriod = 100;
|
|
24
|
+
constructor(bpm) {
|
|
25
|
+
if (!audioContextInstance) {
|
|
26
|
+
audioContextInstance = new nodeWebAudioApi.AudioContext();
|
|
27
|
+
}
|
|
28
|
+
this.context = audioContextInstance;
|
|
29
|
+
this._bpm = bpm;
|
|
30
|
+
this.startTime = this.context.currentTime;
|
|
31
|
+
}
|
|
32
|
+
// --- Public API ---
|
|
33
|
+
get audioContext() {
|
|
34
|
+
return this.context;
|
|
35
|
+
}
|
|
36
|
+
get bpm() {
|
|
37
|
+
return this._bpm;
|
|
38
|
+
}
|
|
39
|
+
set bpm(value) {
|
|
40
|
+
this._bpm = value;
|
|
41
|
+
}
|
|
42
|
+
start() {
|
|
43
|
+
if (this.running) return;
|
|
44
|
+
this.startTime = this.context.currentTime;
|
|
45
|
+
this.running = true;
|
|
46
|
+
for (const event of this.events.values()) {
|
|
47
|
+
if (event.intervalBeats > 0) {
|
|
48
|
+
event.nextBeatTime = 0;
|
|
49
|
+
event.active = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.timerID = setInterval(() => this.schedule(), this.scheduleInterval);
|
|
53
|
+
}
|
|
54
|
+
stop() {
|
|
55
|
+
if (this.timerID) {
|
|
56
|
+
clearInterval(this.timerID);
|
|
57
|
+
this.timerID = null;
|
|
58
|
+
}
|
|
59
|
+
this.running = false;
|
|
60
|
+
console.debug("[scheduler] stopped");
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
this.events.clear();
|
|
64
|
+
}
|
|
65
|
+
addLoop(id, intervalBeats, callback, startBeat) {
|
|
66
|
+
const existing = this.events.get(id);
|
|
67
|
+
if (existing && existing.intervalBeats === intervalBeats) {
|
|
68
|
+
existing.callback = callback;
|
|
69
|
+
existing.active = true;
|
|
70
|
+
existing.pendingRemoval = void 0;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const currentBeat = this.getCurrentBeat();
|
|
74
|
+
let nextBeat;
|
|
75
|
+
if (startBeat !== void 0) {
|
|
76
|
+
nextBeat = startBeat;
|
|
77
|
+
} else {
|
|
78
|
+
nextBeat = Math.ceil(currentBeat / intervalBeats) * intervalBeats;
|
|
79
|
+
if (nextBeat <= currentBeat) {
|
|
80
|
+
nextBeat += intervalBeats;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this.events.delete(id);
|
|
84
|
+
this.events.set(id, {
|
|
85
|
+
id,
|
|
86
|
+
nextBeatTime: nextBeat,
|
|
87
|
+
intervalBeats,
|
|
88
|
+
callback,
|
|
89
|
+
active: true
|
|
90
|
+
});
|
|
91
|
+
console.debug(
|
|
92
|
+
`[scheduler] loop "${id}" added, interval=${intervalBeats} beats, start=${nextBeat.toFixed(2)}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
remove(id) {
|
|
96
|
+
const event = this.events.get(id);
|
|
97
|
+
if (event) {
|
|
98
|
+
event.pendingRemoval = Date.now();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
beatsToSeconds(beats) {
|
|
102
|
+
return beats * 60 / this._bpm;
|
|
103
|
+
}
|
|
104
|
+
// --- Private ---
|
|
105
|
+
secondsToBeats(seconds) {
|
|
106
|
+
return seconds * this._bpm / 60;
|
|
107
|
+
}
|
|
108
|
+
getCurrentBeat() {
|
|
109
|
+
const elapsed = this.context.currentTime - this.startTime;
|
|
110
|
+
return this.secondsToBeats(elapsed);
|
|
111
|
+
}
|
|
112
|
+
beatToAudioTime(beatTime) {
|
|
113
|
+
return this.startTime + this.beatsToSeconds(beatTime);
|
|
114
|
+
}
|
|
115
|
+
schedule() {
|
|
116
|
+
const currentAudioTime = this.context.currentTime;
|
|
117
|
+
const currentBeat = this.getCurrentBeat();
|
|
118
|
+
const scheduleUntilBeat = currentBeat + this.secondsToBeats(this.lookahead);
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
for (const event of this.events.values()) {
|
|
121
|
+
if (event.pendingRemoval !== void 0) continue;
|
|
122
|
+
if (!event.active) continue;
|
|
123
|
+
while (event.nextBeatTime < scheduleUntilBeat) {
|
|
124
|
+
const audioTime = this.beatToAudioTime(event.nextBeatTime);
|
|
125
|
+
if (audioTime >= currentAudioTime) {
|
|
126
|
+
event.callback(audioTime, event.nextBeatTime);
|
|
127
|
+
}
|
|
128
|
+
if (event.intervalBeats > 0) {
|
|
129
|
+
event.nextBeatTime += event.intervalBeats;
|
|
130
|
+
} else {
|
|
131
|
+
event.active = false;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const [id, event] of this.events) {
|
|
137
|
+
if (!event.active && event.intervalBeats === 0) {
|
|
138
|
+
this.events.delete(id);
|
|
139
|
+
} else if (event.pendingRemoval !== void 0 && now - event.pendingRemoval > this.removalGracePeriod) {
|
|
140
|
+
this.events.delete(id);
|
|
141
|
+
console.debug(`[scheduler] loop "${id}" removed`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function getScheduler(bpm) {
|
|
147
|
+
if (!schedulerInstance) {
|
|
148
|
+
schedulerInstance = new Scheduler(bpm);
|
|
149
|
+
} else {
|
|
150
|
+
schedulerInstance.bpm = bpm;
|
|
151
|
+
}
|
|
152
|
+
return schedulerInstance;
|
|
153
|
+
}
|
|
154
|
+
function resetScheduler(bpm) {
|
|
155
|
+
if (schedulerInstance) {
|
|
156
|
+
schedulerInstance.stop();
|
|
157
|
+
schedulerInstance.clear();
|
|
158
|
+
}
|
|
159
|
+
schedulerInstance = new Scheduler(bpm);
|
|
160
|
+
return schedulerInstance;
|
|
161
|
+
}
|
|
162
|
+
const ScheduleNoteContext = React.createContext(null);
|
|
163
|
+
const TrackContext = React.createContext(null);
|
|
164
|
+
function Track({ bpm, children }) {
|
|
165
|
+
const scheduler = React.useRef(getScheduler(bpm));
|
|
166
|
+
const audioContext = scheduler.current.audioContext;
|
|
167
|
+
React.useEffect(() => {
|
|
168
|
+
scheduler.current.start();
|
|
169
|
+
}, [scheduler]);
|
|
170
|
+
const scheduleContextValue = React.useMemo(
|
|
171
|
+
() => ({
|
|
172
|
+
scheduleNote: (_id, callback) => {
|
|
173
|
+
const now = audioContext.currentTime + 5e-3;
|
|
174
|
+
callback(now, 0);
|
|
175
|
+
},
|
|
176
|
+
unscheduleNote: () => {
|
|
177
|
+
}
|
|
178
|
+
}),
|
|
179
|
+
[audioContext]
|
|
180
|
+
);
|
|
181
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
182
|
+
TrackContext.Provider,
|
|
183
|
+
{
|
|
184
|
+
value: {
|
|
185
|
+
audioContext,
|
|
186
|
+
scheduler: scheduler.current
|
|
187
|
+
},
|
|
188
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ScheduleNoteContext.Provider, { value: scheduleContextValue, children })
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function useTrack() {
|
|
193
|
+
const ctx = React.useContext(TrackContext);
|
|
194
|
+
if (!ctx) {
|
|
195
|
+
throw new Error("useTrack must be used inside a <Track> component");
|
|
196
|
+
}
|
|
197
|
+
return ctx;
|
|
198
|
+
}
|
|
199
|
+
function useScheduleNote() {
|
|
200
|
+
const ctx = React.useContext(ScheduleNoteContext);
|
|
201
|
+
if (!ctx) {
|
|
202
|
+
throw new Error("useScheduleNote must be used inside a <Track> component");
|
|
203
|
+
}
|
|
204
|
+
return ctx;
|
|
205
|
+
}
|
|
206
|
+
function Loop({
|
|
207
|
+
id,
|
|
208
|
+
interval: interval2,
|
|
209
|
+
children
|
|
210
|
+
}) {
|
|
211
|
+
const { scheduler } = useTrack();
|
|
212
|
+
const callbacksRef = React.useRef(/* @__PURE__ */ new Map());
|
|
213
|
+
React.useEffect(() => {
|
|
214
|
+
const loopId = id;
|
|
215
|
+
const callbacks = callbacksRef.current;
|
|
216
|
+
const runAllCallbacks = (audioTime, beatTime) => {
|
|
217
|
+
for (const callback of callbacks.values()) {
|
|
218
|
+
callback(audioTime, beatTime);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
scheduler.addLoop(loopId, interval2, runAllCallbacks);
|
|
222
|
+
return () => {
|
|
223
|
+
scheduler.remove(loopId);
|
|
224
|
+
};
|
|
225
|
+
}, [id, interval2, scheduler]);
|
|
226
|
+
const scheduleContextValue = React.useMemo(
|
|
227
|
+
() => ({
|
|
228
|
+
scheduleNote: (noteId, callback) => {
|
|
229
|
+
callbacksRef.current.set(noteId, callback);
|
|
230
|
+
},
|
|
231
|
+
unscheduleNote: (noteId) => {
|
|
232
|
+
callbacksRef.current.delete(noteId);
|
|
233
|
+
}
|
|
234
|
+
}),
|
|
235
|
+
[]
|
|
236
|
+
);
|
|
237
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ScheduleNoteContext.Provider, { value: scheduleContextValue, children });
|
|
238
|
+
}
|
|
239
|
+
const ADSR_DEFAULTS = {
|
|
240
|
+
attack: 0,
|
|
241
|
+
attack_level: 1,
|
|
242
|
+
decay: 0,
|
|
243
|
+
sustain: 0,
|
|
244
|
+
sustain_level: 1,
|
|
245
|
+
release: 1
|
|
246
|
+
};
|
|
247
|
+
function applyADSREnvelope(gainNode, audioTime, props, amp, beatsToSeconds) {
|
|
248
|
+
const attack = props.attack ?? ADSR_DEFAULTS.attack;
|
|
249
|
+
const attack_level = props.attack_level ?? ADSR_DEFAULTS.attack_level;
|
|
250
|
+
const decay = props.decay ?? ADSR_DEFAULTS.decay;
|
|
251
|
+
const sustain = props.sustain ?? ADSR_DEFAULTS.sustain;
|
|
252
|
+
const sustain_level = props.sustain_level ?? ADSR_DEFAULTS.sustain_level;
|
|
253
|
+
const release = props.release ?? ADSR_DEFAULTS.release;
|
|
254
|
+
const decay_level = props.decay_level ?? sustain_level;
|
|
255
|
+
const attackSec = beatsToSeconds(attack);
|
|
256
|
+
const decaySec = beatsToSeconds(decay);
|
|
257
|
+
const sustainSec = beatsToSeconds(sustain);
|
|
258
|
+
const releaseSec = beatsToSeconds(release);
|
|
259
|
+
const attackAmp = attack_level * amp;
|
|
260
|
+
const decayAmp = decay_level * amp;
|
|
261
|
+
const sustainAmp = sustain_level * amp;
|
|
262
|
+
const attackEnd = audioTime + attackSec;
|
|
263
|
+
const decayEnd = attackEnd + decaySec;
|
|
264
|
+
const sustainEnd = decayEnd + sustainSec;
|
|
265
|
+
const releaseEnd = sustainEnd + releaseSec;
|
|
266
|
+
gainNode.gain.setValueAtTime(1e-3, audioTime);
|
|
267
|
+
if (attackSec > 0) {
|
|
268
|
+
gainNode.gain.linearRampToValueAtTime(attackAmp, attackEnd);
|
|
269
|
+
} else {
|
|
270
|
+
gainNode.gain.setValueAtTime(attackAmp, audioTime);
|
|
271
|
+
}
|
|
272
|
+
if (decaySec > 0) {
|
|
273
|
+
gainNode.gain.linearRampToValueAtTime(decayAmp, decayEnd);
|
|
274
|
+
} else {
|
|
275
|
+
gainNode.gain.setValueAtTime(decayAmp, attackEnd);
|
|
276
|
+
}
|
|
277
|
+
if (sustainSec > 0) {
|
|
278
|
+
gainNode.gain.linearRampToValueAtTime(sustainAmp, decayEnd + 1e-3);
|
|
279
|
+
gainNode.gain.setValueAtTime(sustainAmp, sustainEnd);
|
|
280
|
+
} else {
|
|
281
|
+
gainNode.gain.setValueAtTime(sustainAmp, decayEnd);
|
|
282
|
+
}
|
|
283
|
+
gainNode.gain.exponentialRampToValueAtTime(1e-3, releaseEnd);
|
|
284
|
+
return releaseEnd;
|
|
285
|
+
}
|
|
286
|
+
function isNamedPitch(src) {
|
|
287
|
+
return src !== null && typeof src === "object" && "name" in src && typeof src.name === "string" ? true : false;
|
|
288
|
+
}
|
|
289
|
+
function isPitch(pitch2) {
|
|
290
|
+
return pitch2 !== null && typeof pitch2 === "object" && "step" in pitch2 && typeof pitch2.step === "number" && "alt" in pitch2 && typeof pitch2.alt === "number" && !isNaN(pitch2.step) && !isNaN(pitch2.alt) ? true : false;
|
|
291
|
+
}
|
|
292
|
+
var FIFTHS = [0, 2, 4, -1, 1, 3, 5];
|
|
293
|
+
var STEPS_TO_OCTS = FIFTHS.map(
|
|
294
|
+
(fifths) => Math.floor(fifths * 7 / 12)
|
|
295
|
+
);
|
|
296
|
+
function coordinates(pitch2) {
|
|
297
|
+
const { step, alt, oct, dir = 1 } = pitch2;
|
|
298
|
+
const f = FIFTHS[step] + 7 * alt;
|
|
299
|
+
if (oct === void 0) {
|
|
300
|
+
return [dir * f];
|
|
301
|
+
}
|
|
302
|
+
const o = oct - STEPS_TO_OCTS[step] - 4 * alt;
|
|
303
|
+
return [dir * f, dir * o];
|
|
304
|
+
}
|
|
305
|
+
var FIFTHS_TO_STEPS = [3, 0, 4, 1, 5, 2, 6];
|
|
306
|
+
function pitch(coord) {
|
|
307
|
+
const [f, o, dir] = coord;
|
|
308
|
+
const step = FIFTHS_TO_STEPS[unaltered(f)];
|
|
309
|
+
const alt = Math.floor((f + 1) / 7);
|
|
310
|
+
if (o === void 0) {
|
|
311
|
+
return { step, alt, dir };
|
|
312
|
+
}
|
|
313
|
+
const oct = o + 4 * alt + STEPS_TO_OCTS[step];
|
|
314
|
+
return { step, alt, oct, dir };
|
|
315
|
+
}
|
|
316
|
+
function unaltered(f) {
|
|
317
|
+
const i = (f + 1) % 7;
|
|
318
|
+
return i < 0 ? 7 + i : i;
|
|
319
|
+
}
|
|
320
|
+
var fillStr$1 = (s, n) => Array(Math.abs(n) + 1).join(s);
|
|
321
|
+
var NoInterval = Object.freeze({
|
|
322
|
+
empty: true,
|
|
323
|
+
name: "",
|
|
324
|
+
num: NaN,
|
|
325
|
+
q: "",
|
|
326
|
+
type: "",
|
|
327
|
+
step: NaN,
|
|
328
|
+
alt: NaN,
|
|
329
|
+
dir: NaN,
|
|
330
|
+
simple: NaN,
|
|
331
|
+
semitones: NaN,
|
|
332
|
+
chroma: NaN,
|
|
333
|
+
coord: [],
|
|
334
|
+
oct: NaN
|
|
335
|
+
});
|
|
336
|
+
var INTERVAL_TONAL_REGEX = "([-+]?\\d+)(d{1,4}|m|M|P|A{1,4})";
|
|
337
|
+
var INTERVAL_SHORTHAND_REGEX = "(AA|A|P|M|m|d|dd)([-+]?\\d+)";
|
|
338
|
+
var REGEX$2 = new RegExp(
|
|
339
|
+
"^" + INTERVAL_TONAL_REGEX + "|" + INTERVAL_SHORTHAND_REGEX + "$"
|
|
340
|
+
);
|
|
341
|
+
function tokenizeInterval(str) {
|
|
342
|
+
const m = REGEX$2.exec(`${str}`);
|
|
343
|
+
if (m === null) {
|
|
344
|
+
return ["", ""];
|
|
345
|
+
}
|
|
346
|
+
return m[1] ? [m[1], m[2]] : [m[4], m[3]];
|
|
347
|
+
}
|
|
348
|
+
var cache$2 = {};
|
|
349
|
+
function interval(src) {
|
|
350
|
+
return typeof src === "string" ? cache$2[src] || (cache$2[src] = parse$1(src)) : isPitch(src) ? interval(pitchName$1(src)) : isNamedPitch(src) ? interval(src.name) : NoInterval;
|
|
351
|
+
}
|
|
352
|
+
var SIZES = [0, 2, 4, 5, 7, 9, 11];
|
|
353
|
+
var TYPES = "PMMPPMM";
|
|
354
|
+
function parse$1(str) {
|
|
355
|
+
const tokens = tokenizeInterval(str);
|
|
356
|
+
if (tokens[0] === "") {
|
|
357
|
+
return NoInterval;
|
|
358
|
+
}
|
|
359
|
+
const num = +tokens[0];
|
|
360
|
+
const q = tokens[1];
|
|
361
|
+
const step = (Math.abs(num) - 1) % 7;
|
|
362
|
+
const t = TYPES[step];
|
|
363
|
+
if (t === "M" && q === "P") {
|
|
364
|
+
return NoInterval;
|
|
365
|
+
}
|
|
366
|
+
const type = t === "M" ? "majorable" : "perfectable";
|
|
367
|
+
const name = "" + num + q;
|
|
368
|
+
const dir = num < 0 ? -1 : 1;
|
|
369
|
+
const simple = num === 8 || num === -8 ? num : dir * (step + 1);
|
|
370
|
+
const alt = qToAlt(type, q);
|
|
371
|
+
const oct = Math.floor((Math.abs(num) - 1) / 7);
|
|
372
|
+
const semitones = dir * (SIZES[step] + alt + 12 * oct);
|
|
373
|
+
const chroma = (dir * (SIZES[step] + alt) % 12 + 12) % 12;
|
|
374
|
+
const coord = coordinates({ step, alt, oct, dir });
|
|
375
|
+
return {
|
|
376
|
+
empty: false,
|
|
377
|
+
name,
|
|
378
|
+
num,
|
|
379
|
+
q,
|
|
380
|
+
step,
|
|
381
|
+
alt,
|
|
382
|
+
dir,
|
|
383
|
+
type,
|
|
384
|
+
simple,
|
|
385
|
+
semitones,
|
|
386
|
+
chroma,
|
|
387
|
+
coord,
|
|
388
|
+
oct
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function coordToInterval(coord, forceDescending) {
|
|
392
|
+
const [f, o = 0] = coord;
|
|
393
|
+
const isDescending = f * 7 + o * 12 < 0;
|
|
394
|
+
const ivl = forceDescending || isDescending ? [-f, -o, -1] : [f, o, 1];
|
|
395
|
+
return interval(pitch(ivl));
|
|
396
|
+
}
|
|
397
|
+
function qToAlt(type, q) {
|
|
398
|
+
return q === "M" && type === "majorable" || q === "P" && type === "perfectable" ? 0 : q === "m" && type === "majorable" ? -1 : /^A+$/.test(q) ? q.length : /^d+$/.test(q) ? -1 * (type === "perfectable" ? q.length : q.length + 1) : 0;
|
|
399
|
+
}
|
|
400
|
+
function pitchName$1(props) {
|
|
401
|
+
const { step, alt, oct = 0, dir } = props;
|
|
402
|
+
if (!dir) {
|
|
403
|
+
return "";
|
|
404
|
+
}
|
|
405
|
+
const calcNum = step + 1 + 7 * oct;
|
|
406
|
+
const num = calcNum === 0 ? step + 1 : calcNum;
|
|
407
|
+
const d = dir < 0 ? "-" : "";
|
|
408
|
+
const type = TYPES[step] === "M" ? "majorable" : "perfectable";
|
|
409
|
+
const name = d + num + altToQ(type, alt);
|
|
410
|
+
return name;
|
|
411
|
+
}
|
|
412
|
+
function altToQ(type, alt) {
|
|
413
|
+
if (alt === 0) {
|
|
414
|
+
return type === "majorable" ? "M" : "P";
|
|
415
|
+
} else if (alt === -1 && type === "majorable") {
|
|
416
|
+
return "m";
|
|
417
|
+
} else if (alt > 0) {
|
|
418
|
+
return fillStr$1("A", alt);
|
|
419
|
+
} else {
|
|
420
|
+
return fillStr$1("d", type === "perfectable" ? alt : alt + 1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
var fillStr = (s, n) => Array(Math.abs(n) + 1).join(s);
|
|
424
|
+
var NoNote = Object.freeze({
|
|
425
|
+
empty: true,
|
|
426
|
+
name: "",
|
|
427
|
+
letter: "",
|
|
428
|
+
acc: "",
|
|
429
|
+
pc: "",
|
|
430
|
+
step: NaN,
|
|
431
|
+
alt: NaN,
|
|
432
|
+
chroma: NaN,
|
|
433
|
+
height: NaN,
|
|
434
|
+
coord: [],
|
|
435
|
+
midi: null,
|
|
436
|
+
freq: null
|
|
437
|
+
});
|
|
438
|
+
var cache$1 = /* @__PURE__ */ new Map();
|
|
439
|
+
var stepToLetter = (step) => "CDEFGAB".charAt(step);
|
|
440
|
+
var altToAcc = (alt) => alt < 0 ? fillStr("b", -alt) : fillStr("#", alt);
|
|
441
|
+
var accToAlt = (acc) => acc[0] === "b" ? -acc.length : acc.length;
|
|
442
|
+
function note(src) {
|
|
443
|
+
const stringSrc = JSON.stringify(src);
|
|
444
|
+
const cached = cache$1.get(stringSrc);
|
|
445
|
+
if (cached) {
|
|
446
|
+
return cached;
|
|
447
|
+
}
|
|
448
|
+
const value = typeof src === "string" ? parse(src) : isPitch(src) ? note(pitchName(src)) : isNamedPitch(src) ? note(src.name) : NoNote;
|
|
449
|
+
cache$1.set(stringSrc, value);
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
var REGEX$1 = /^([a-gA-G]?)(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)$/;
|
|
453
|
+
function tokenizeNote(str) {
|
|
454
|
+
const m = REGEX$1.exec(str);
|
|
455
|
+
return m ? [m[1].toUpperCase(), m[2].replace(/x/g, "##"), m[3], m[4]] : ["", "", "", ""];
|
|
456
|
+
}
|
|
457
|
+
function coordToNote(noteCoord) {
|
|
458
|
+
return note(pitch(noteCoord));
|
|
459
|
+
}
|
|
460
|
+
var mod = (n, m) => (n % m + m) % m;
|
|
461
|
+
var SEMI = [0, 2, 4, 5, 7, 9, 11];
|
|
462
|
+
function parse(noteName) {
|
|
463
|
+
const tokens = tokenizeNote(noteName);
|
|
464
|
+
if (tokens[0] === "" || tokens[3] !== "") {
|
|
465
|
+
return NoNote;
|
|
466
|
+
}
|
|
467
|
+
const letter = tokens[0];
|
|
468
|
+
const acc = tokens[1];
|
|
469
|
+
const octStr = tokens[2];
|
|
470
|
+
const step = (letter.charCodeAt(0) + 3) % 7;
|
|
471
|
+
const alt = accToAlt(acc);
|
|
472
|
+
const oct = octStr.length ? +octStr : void 0;
|
|
473
|
+
const coord = coordinates({ step, alt, oct });
|
|
474
|
+
const name = letter + acc + octStr;
|
|
475
|
+
const pc = letter + acc;
|
|
476
|
+
const chroma = (SEMI[step] + alt + 120) % 12;
|
|
477
|
+
const height = oct === void 0 ? mod(SEMI[step] + alt, 12) - 12 * 99 : SEMI[step] + alt + 12 * (oct + 1);
|
|
478
|
+
const midi = height >= 0 && height <= 127 ? height : null;
|
|
479
|
+
const freq2 = oct === void 0 ? null : Math.pow(2, (height - 69) / 12) * 440;
|
|
480
|
+
return {
|
|
481
|
+
empty: false,
|
|
482
|
+
acc,
|
|
483
|
+
alt,
|
|
484
|
+
chroma,
|
|
485
|
+
coord,
|
|
486
|
+
freq: freq2,
|
|
487
|
+
height,
|
|
488
|
+
letter,
|
|
489
|
+
midi,
|
|
490
|
+
name,
|
|
491
|
+
oct,
|
|
492
|
+
pc,
|
|
493
|
+
step
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function pitchName(props) {
|
|
497
|
+
const { step, alt, oct } = props;
|
|
498
|
+
const letter = stepToLetter(step);
|
|
499
|
+
if (!letter) {
|
|
500
|
+
return "";
|
|
501
|
+
}
|
|
502
|
+
const pc = letter + altToAcc(alt);
|
|
503
|
+
return oct || oct === 0 ? pc + oct : pc;
|
|
504
|
+
}
|
|
505
|
+
function transpose(noteName, intervalName) {
|
|
506
|
+
const note$1 = note(noteName);
|
|
507
|
+
const intervalCoord = Array.isArray(intervalName) ? intervalName : interval(intervalName).coord;
|
|
508
|
+
if (note$1.empty || !intervalCoord || intervalCoord.length < 2) {
|
|
509
|
+
return "";
|
|
510
|
+
}
|
|
511
|
+
const noteCoord = note$1.coord;
|
|
512
|
+
const tr = noteCoord.length === 1 ? [noteCoord[0] + intervalCoord[0]] : [noteCoord[0] + intervalCoord[0], noteCoord[1] + intervalCoord[1]];
|
|
513
|
+
return coordToNote(tr).name;
|
|
514
|
+
}
|
|
515
|
+
function distance(fromNote, toNote) {
|
|
516
|
+
const from = note(fromNote);
|
|
517
|
+
const to = note(toNote);
|
|
518
|
+
if (from.empty || to.empty) {
|
|
519
|
+
return "";
|
|
520
|
+
}
|
|
521
|
+
const fcoord = from.coord;
|
|
522
|
+
const tcoord = to.coord;
|
|
523
|
+
const fifths = tcoord[0] - fcoord[0];
|
|
524
|
+
const octs = fcoord.length === 2 && tcoord.length === 2 ? tcoord[1] - fcoord[1] : -Math.floor(fifths * 7 / 12);
|
|
525
|
+
const forceDescending = to.height === from.height && to.midi !== null && from.oct === to.oct && from.step > to.step;
|
|
526
|
+
return coordToInterval([fifths, octs], forceDescending).name;
|
|
527
|
+
}
|
|
528
|
+
function rotate(times, arr) {
|
|
529
|
+
const len = arr.length;
|
|
530
|
+
const n = (times % len + len) % len;
|
|
531
|
+
return arr.slice(n, len).concat(arr.slice(0, n));
|
|
532
|
+
}
|
|
533
|
+
var EmptyPcset = {
|
|
534
|
+
empty: true,
|
|
535
|
+
name: "",
|
|
536
|
+
setNum: 0,
|
|
537
|
+
chroma: "000000000000",
|
|
538
|
+
normalized: "000000000000",
|
|
539
|
+
intervals: []
|
|
540
|
+
};
|
|
541
|
+
var setNumToChroma = (num2) => Number(num2).toString(2).padStart(12, "0");
|
|
542
|
+
var chromaToNumber = (chroma2) => parseInt(chroma2, 2);
|
|
543
|
+
var REGEX = /^[01]{12}$/;
|
|
544
|
+
function isChroma(set) {
|
|
545
|
+
return REGEX.test(set);
|
|
546
|
+
}
|
|
547
|
+
var isPcsetNum = (set) => typeof set === "number" && set >= 0 && set <= 4095;
|
|
548
|
+
var isPcset = (set) => set && isChroma(set.chroma);
|
|
549
|
+
var cache = { [EmptyPcset.chroma]: EmptyPcset };
|
|
550
|
+
function get$5(src) {
|
|
551
|
+
const chroma2 = isChroma(src) ? src : isPcsetNum(src) ? setNumToChroma(src) : Array.isArray(src) ? listToChroma(src) : isPcset(src) ? src.chroma : EmptyPcset.chroma;
|
|
552
|
+
return cache[chroma2] = cache[chroma2] || chromaToPcset(chroma2);
|
|
553
|
+
}
|
|
554
|
+
var IVLS = [
|
|
555
|
+
"1P",
|
|
556
|
+
"2m",
|
|
557
|
+
"2M",
|
|
558
|
+
"3m",
|
|
559
|
+
"3M",
|
|
560
|
+
"4P",
|
|
561
|
+
"5d",
|
|
562
|
+
"5P",
|
|
563
|
+
"6m",
|
|
564
|
+
"6M",
|
|
565
|
+
"7m",
|
|
566
|
+
"7M"
|
|
567
|
+
];
|
|
568
|
+
function chromaToIntervals(chroma2) {
|
|
569
|
+
const intervals2 = [];
|
|
570
|
+
for (let i = 0; i < 12; i++) {
|
|
571
|
+
if (chroma2.charAt(i) === "1") intervals2.push(IVLS[i]);
|
|
572
|
+
}
|
|
573
|
+
return intervals2;
|
|
574
|
+
}
|
|
575
|
+
function chromaRotations(chroma2) {
|
|
576
|
+
const binary = chroma2.split("");
|
|
577
|
+
return binary.map((_, i) => rotate(i, binary).join(""));
|
|
578
|
+
}
|
|
579
|
+
function chromaToPcset(chroma2) {
|
|
580
|
+
const setNum = chromaToNumber(chroma2);
|
|
581
|
+
const normalizedNum = chromaRotations(chroma2).map(chromaToNumber).filter((n) => n >= 2048).sort()[0];
|
|
582
|
+
const normalized = setNumToChroma(normalizedNum);
|
|
583
|
+
const intervals2 = chromaToIntervals(chroma2);
|
|
584
|
+
return {
|
|
585
|
+
empty: false,
|
|
586
|
+
name: "",
|
|
587
|
+
setNum,
|
|
588
|
+
chroma: chroma2,
|
|
589
|
+
normalized,
|
|
590
|
+
intervals: intervals2
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function listToChroma(set) {
|
|
594
|
+
if (set.length === 0) {
|
|
595
|
+
return EmptyPcset.chroma;
|
|
596
|
+
}
|
|
597
|
+
let pitch2;
|
|
598
|
+
const binary = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
599
|
+
for (let i = 0; i < set.length; i++) {
|
|
600
|
+
pitch2 = note(set[i]);
|
|
601
|
+
if (pitch2.empty) pitch2 = interval(set[i]);
|
|
602
|
+
if (!pitch2.empty) binary[pitch2.chroma] = 1;
|
|
603
|
+
}
|
|
604
|
+
return binary.join("");
|
|
605
|
+
}
|
|
606
|
+
var CHORDS = [
|
|
607
|
+
// ==Major==
|
|
608
|
+
["1P 3M 5P", "major", "M ^ maj"],
|
|
609
|
+
["1P 3M 5P 7M", "major seventh", "maj7 Δ ma7 M7 Maj7 ^7"],
|
|
610
|
+
["1P 3M 5P 7M 9M", "major ninth", "maj9 Δ9 ^9"],
|
|
611
|
+
["1P 3M 5P 7M 9M 13M", "major thirteenth", "maj13 Maj13 ^13"],
|
|
612
|
+
["1P 3M 5P 6M", "sixth", "6 add6 add13 M6"],
|
|
613
|
+
["1P 3M 5P 6M 9M", "sixth added ninth", "6add9 6/9 69 M69"],
|
|
614
|
+
["1P 3M 6m 7M", "major seventh flat sixth", "M7b6 ^7b6"],
|
|
615
|
+
[
|
|
616
|
+
"1P 3M 5P 7M 11A",
|
|
617
|
+
"major seventh sharp eleventh",
|
|
618
|
+
"maj#4 Δ#4 Δ#11 M7#11 ^7#11 maj7#11"
|
|
619
|
+
],
|
|
620
|
+
// ==Minor==
|
|
621
|
+
// '''Normal'''
|
|
622
|
+
["1P 3m 5P", "minor", "m min -"],
|
|
623
|
+
["1P 3m 5P 7m", "minor seventh", "m7 min7 mi7 -7"],
|
|
624
|
+
[
|
|
625
|
+
"1P 3m 5P 7M",
|
|
626
|
+
"minor/major seventh",
|
|
627
|
+
"m/ma7 m/maj7 mM7 mMaj7 m/M7 -Δ7 mΔ -^7 -maj7"
|
|
628
|
+
],
|
|
629
|
+
["1P 3m 5P 6M", "minor sixth", "m6 -6"],
|
|
630
|
+
["1P 3m 5P 7m 9M", "minor ninth", "m9 -9"],
|
|
631
|
+
["1P 3m 5P 7M 9M", "minor/major ninth", "mM9 mMaj9 -^9"],
|
|
632
|
+
["1P 3m 5P 7m 9M 11P", "minor eleventh", "m11 -11"],
|
|
633
|
+
["1P 3m 5P 7m 9M 13M", "minor thirteenth", "m13 -13"],
|
|
634
|
+
// '''Diminished'''
|
|
635
|
+
["1P 3m 5d", "diminished", "dim ° o"],
|
|
636
|
+
["1P 3m 5d 7d", "diminished seventh", "dim7 °7 o7"],
|
|
637
|
+
["1P 3m 5d 7m", "half-diminished", "m7b5 ø -7b5 h7 h"],
|
|
638
|
+
// ==Dominant/Seventh==
|
|
639
|
+
// '''Normal'''
|
|
640
|
+
["1P 3M 5P 7m", "dominant seventh", "7 dom"],
|
|
641
|
+
["1P 3M 5P 7m 9M", "dominant ninth", "9"],
|
|
642
|
+
["1P 3M 5P 7m 9M 13M", "dominant thirteenth", "13"],
|
|
643
|
+
["1P 3M 5P 7m 11A", "lydian dominant seventh", "7#11 7#4"],
|
|
644
|
+
// '''Altered'''
|
|
645
|
+
["1P 3M 5P 7m 9m", "dominant flat ninth", "7b9"],
|
|
646
|
+
["1P 3M 5P 7m 9A", "dominant sharp ninth", "7#9"],
|
|
647
|
+
["1P 3M 7m 9m", "altered", "alt7"],
|
|
648
|
+
// '''Suspended'''
|
|
649
|
+
["1P 4P 5P", "suspended fourth", "sus4 sus"],
|
|
650
|
+
["1P 2M 5P", "suspended second", "sus2"],
|
|
651
|
+
["1P 4P 5P 7m", "suspended fourth seventh", "7sus4 7sus"],
|
|
652
|
+
["1P 5P 7m 9M 11P", "eleventh", "11"],
|
|
653
|
+
[
|
|
654
|
+
"1P 4P 5P 7m 9m",
|
|
655
|
+
"suspended fourth flat ninth",
|
|
656
|
+
"b9sus phryg 7b9sus 7b9sus4"
|
|
657
|
+
],
|
|
658
|
+
// ==Other==
|
|
659
|
+
["1P 5P", "fifth", "5"],
|
|
660
|
+
["1P 3M 5A", "augmented", "aug + +5 ^#5"],
|
|
661
|
+
["1P 3m 5A", "minor augmented", "m#5 -#5 m+"],
|
|
662
|
+
["1P 3M 5A 7M", "augmented seventh", "maj7#5 maj7+5 +maj7 ^7#5"],
|
|
663
|
+
[
|
|
664
|
+
"1P 3M 5P 7M 9M 11A",
|
|
665
|
+
"major sharp eleventh (lydian)",
|
|
666
|
+
"maj9#11 Δ9#11 ^9#11"
|
|
667
|
+
],
|
|
668
|
+
// ==Legacy==
|
|
669
|
+
["1P 2M 4P 5P", "", "sus24 sus4add9"],
|
|
670
|
+
["1P 3M 5A 7M 9M", "", "maj9#5 Maj9#5"],
|
|
671
|
+
["1P 3M 5A 7m", "", "7#5 +7 7+ 7aug aug7"],
|
|
672
|
+
["1P 3M 5A 7m 9A", "", "7#5#9 7#9#5 7alt"],
|
|
673
|
+
["1P 3M 5A 7m 9M", "", "9#5 9+"],
|
|
674
|
+
["1P 3M 5A 7m 9M 11A", "", "9#5#11"],
|
|
675
|
+
["1P 3M 5A 7m 9m", "", "7#5b9 7b9#5"],
|
|
676
|
+
["1P 3M 5A 7m 9m 11A", "", "7#5b9#11"],
|
|
677
|
+
["1P 3M 5A 9A", "", "+add#9"],
|
|
678
|
+
["1P 3M 5A 9M", "", "M#5add9 +add9"],
|
|
679
|
+
["1P 3M 5P 6M 11A", "", "M6#11 M6b5 6#11 6b5"],
|
|
680
|
+
["1P 3M 5P 6M 7M 9M", "", "M7add13"],
|
|
681
|
+
["1P 3M 5P 6M 9M 11A", "", "69#11"],
|
|
682
|
+
["1P 3m 5P 6M 9M", "", "m69 -69"],
|
|
683
|
+
["1P 3M 5P 6m 7m", "", "7b6"],
|
|
684
|
+
["1P 3M 5P 7M 9A 11A", "", "maj7#9#11"],
|
|
685
|
+
["1P 3M 5P 7M 9M 11A 13M", "", "M13#11 maj13#11 M13+4 M13#4"],
|
|
686
|
+
["1P 3M 5P 7M 9m", "", "M7b9"],
|
|
687
|
+
["1P 3M 5P 7m 11A 13m", "", "7#11b13 7b5b13"],
|
|
688
|
+
["1P 3M 5P 7m 13M", "", "7add6 67 7add13"],
|
|
689
|
+
["1P 3M 5P 7m 9A 11A", "", "7#9#11 7b5#9 7#9b5"],
|
|
690
|
+
["1P 3M 5P 7m 9A 11A 13M", "", "13#9#11"],
|
|
691
|
+
["1P 3M 5P 7m 9A 11A 13m", "", "7#9#11b13"],
|
|
692
|
+
["1P 3M 5P 7m 9A 13M", "", "13#9"],
|
|
693
|
+
["1P 3M 5P 7m 9A 13m", "", "7#9b13"],
|
|
694
|
+
["1P 3M 5P 7m 9M 11A", "", "9#11 9+4 9#4"],
|
|
695
|
+
["1P 3M 5P 7m 9M 11A 13M", "", "13#11 13+4 13#4"],
|
|
696
|
+
["1P 3M 5P 7m 9M 11A 13m", "", "9#11b13 9b5b13"],
|
|
697
|
+
["1P 3M 5P 7m 9m 11A", "", "7b9#11 7b5b9 7b9b5"],
|
|
698
|
+
["1P 3M 5P 7m 9m 11A 13M", "", "13b9#11"],
|
|
699
|
+
["1P 3M 5P 7m 9m 11A 13m", "", "7b9b13#11 7b9#11b13 7b5b9b13"],
|
|
700
|
+
["1P 3M 5P 7m 9m 13M", "", "13b9"],
|
|
701
|
+
["1P 3M 5P 7m 9m 13m", "", "7b9b13"],
|
|
702
|
+
["1P 3M 5P 7m 9m 9A", "", "7b9#9"],
|
|
703
|
+
["1P 3M 5P 9M", "", "Madd9 2 add9 add2"],
|
|
704
|
+
["1P 3M 5P 9m", "", "Maddb9"],
|
|
705
|
+
["1P 3M 5d", "", "Mb5"],
|
|
706
|
+
["1P 3M 5d 6M 7m 9M", "", "13b5"],
|
|
707
|
+
["1P 3M 5d 7M", "", "M7b5"],
|
|
708
|
+
["1P 3M 5d 7M 9M", "", "M9b5"],
|
|
709
|
+
["1P 3M 5d 7m", "", "7b5"],
|
|
710
|
+
["1P 3M 5d 7m 9M", "", "9b5"],
|
|
711
|
+
["1P 3M 7m", "", "7no5"],
|
|
712
|
+
["1P 3M 7m 13m", "", "7b13"],
|
|
713
|
+
["1P 3M 7m 9M", "", "9no5"],
|
|
714
|
+
["1P 3M 7m 9M 13M", "", "13no5"],
|
|
715
|
+
["1P 3M 7m 9M 13m", "", "9b13"],
|
|
716
|
+
["1P 3m 4P 5P", "", "madd4"],
|
|
717
|
+
["1P 3m 5P 6m 7M", "", "mMaj7b6"],
|
|
718
|
+
["1P 3m 5P 6m 7M 9M", "", "mMaj9b6"],
|
|
719
|
+
["1P 3m 5P 7m 11P", "", "m7add11 m7add4"],
|
|
720
|
+
["1P 3m 5P 9M", "", "madd9"],
|
|
721
|
+
["1P 3m 5d 6M 7M", "", "o7M7"],
|
|
722
|
+
["1P 3m 5d 7M", "", "oM7"],
|
|
723
|
+
["1P 3m 6m 7M", "", "mb6M7"],
|
|
724
|
+
["1P 3m 6m 7m", "", "m7#5"],
|
|
725
|
+
["1P 3m 6m 7m 9M", "", "m9#5"],
|
|
726
|
+
["1P 3m 5A 7m 9M 11P", "", "m11A"],
|
|
727
|
+
["1P 3m 6m 9m", "", "mb6b9"],
|
|
728
|
+
["1P 2M 3m 5d 7m", "", "m9b5"],
|
|
729
|
+
["1P 4P 5A 7M", "", "M7#5sus4"],
|
|
730
|
+
["1P 4P 5A 7M 9M", "", "M9#5sus4"],
|
|
731
|
+
["1P 4P 5A 7m", "", "7#5sus4"],
|
|
732
|
+
["1P 4P 5P 7M", "", "M7sus4"],
|
|
733
|
+
["1P 4P 5P 7M 9M", "", "M9sus4"],
|
|
734
|
+
["1P 4P 5P 7m 9M", "", "9sus4 9sus"],
|
|
735
|
+
["1P 4P 5P 7m 9M 13M", "", "13sus4 13sus"],
|
|
736
|
+
["1P 4P 5P 7m 9m 13m", "", "7sus4b9b13 7b9b13sus4"],
|
|
737
|
+
["1P 4P 7m 10m", "", "4 quartal"],
|
|
738
|
+
["1P 5P 7m 9m 11P", "", "11b9"]
|
|
739
|
+
];
|
|
740
|
+
var data_default$2 = CHORDS;
|
|
741
|
+
var NoChordType = {
|
|
742
|
+
...EmptyPcset,
|
|
743
|
+
name: "",
|
|
744
|
+
quality: "Unknown",
|
|
745
|
+
intervals: [],
|
|
746
|
+
aliases: []
|
|
747
|
+
};
|
|
748
|
+
var dictionary = [];
|
|
749
|
+
var index$2 = {};
|
|
750
|
+
function get$4(type) {
|
|
751
|
+
return index$2[type] || NoChordType;
|
|
752
|
+
}
|
|
753
|
+
function add$2(intervals, aliases, fullName) {
|
|
754
|
+
const quality = getQuality(intervals);
|
|
755
|
+
const chord = {
|
|
756
|
+
...get$5(intervals),
|
|
757
|
+
name: fullName || "",
|
|
758
|
+
quality,
|
|
759
|
+
intervals,
|
|
760
|
+
aliases
|
|
761
|
+
};
|
|
762
|
+
dictionary.push(chord);
|
|
763
|
+
if (chord.name) {
|
|
764
|
+
index$2[chord.name] = chord;
|
|
765
|
+
}
|
|
766
|
+
index$2[chord.setNum] = chord;
|
|
767
|
+
index$2[chord.chroma] = chord;
|
|
768
|
+
chord.aliases.forEach((alias) => addAlias$1(chord, alias));
|
|
769
|
+
}
|
|
770
|
+
function addAlias$1(chord, alias) {
|
|
771
|
+
index$2[alias] = chord;
|
|
772
|
+
}
|
|
773
|
+
function getQuality(intervals) {
|
|
774
|
+
const has = (interval2) => intervals.indexOf(interval2) !== -1;
|
|
775
|
+
return has("5A") ? "Augmented" : has("3M") ? "Major" : has("5d") ? "Diminished" : has("3m") ? "Minor" : "Unknown";
|
|
776
|
+
}
|
|
777
|
+
data_default$2.forEach(
|
|
778
|
+
([ivls, fullName, names2]) => add$2(ivls.split(" "), names2.split(" "), fullName)
|
|
779
|
+
);
|
|
780
|
+
dictionary.sort((a, b) => a.setNum - b.setNum);
|
|
781
|
+
var subtract = combinator((a, b) => [a[0] - b[0], a[1] - b[1]]);
|
|
782
|
+
function combinator(fn) {
|
|
783
|
+
return (a, b) => {
|
|
784
|
+
const coordA = interval(a).coord;
|
|
785
|
+
const coordB = interval(b).coord;
|
|
786
|
+
if (coordA && coordB) {
|
|
787
|
+
const coord = fn(coordA, coordB);
|
|
788
|
+
return coordToInterval(coord).name;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
var SCALES = [
|
|
793
|
+
// Basic scales
|
|
794
|
+
["1P 2M 3M 5P 6M", "major pentatonic", "pentatonic"],
|
|
795
|
+
["1P 2M 3M 4P 5P 6M 7M", "major", "ionian"],
|
|
796
|
+
["1P 2M 3m 4P 5P 6m 7m", "minor", "aeolian"],
|
|
797
|
+
// Jazz common scales
|
|
798
|
+
["1P 2M 3m 3M 5P 6M", "major blues"],
|
|
799
|
+
["1P 3m 4P 5d 5P 7m", "minor blues", "blues"],
|
|
800
|
+
["1P 2M 3m 4P 5P 6M 7M", "melodic minor"],
|
|
801
|
+
["1P 2M 3m 4P 5P 6m 7M", "harmonic minor"],
|
|
802
|
+
["1P 2M 3M 4P 5P 6M 7m 7M", "bebop"],
|
|
803
|
+
["1P 2M 3m 4P 5d 6m 6M 7M", "diminished", "whole-half diminished"],
|
|
804
|
+
// Modes
|
|
805
|
+
["1P 2M 3m 4P 5P 6M 7m", "dorian"],
|
|
806
|
+
["1P 2M 3M 4A 5P 6M 7M", "lydian"],
|
|
807
|
+
["1P 2M 3M 4P 5P 6M 7m", "mixolydian", "dominant"],
|
|
808
|
+
["1P 2m 3m 4P 5P 6m 7m", "phrygian"],
|
|
809
|
+
["1P 2m 3m 4P 5d 6m 7m", "locrian"],
|
|
810
|
+
// 5-note scales
|
|
811
|
+
["1P 3M 4P 5P 7M", "ionian pentatonic"],
|
|
812
|
+
["1P 3M 4P 5P 7m", "mixolydian pentatonic", "indian"],
|
|
813
|
+
["1P 2M 4P 5P 6M", "ritusen"],
|
|
814
|
+
["1P 2M 4P 5P 7m", "egyptian"],
|
|
815
|
+
["1P 3M 4P 5d 7m", "neopolitan major pentatonic"],
|
|
816
|
+
["1P 3m 4P 5P 6m", "vietnamese 1"],
|
|
817
|
+
["1P 2m 3m 5P 6m", "pelog"],
|
|
818
|
+
["1P 2m 4P 5P 6m", "kumoijoshi"],
|
|
819
|
+
["1P 2M 3m 5P 6m", "hirajoshi"],
|
|
820
|
+
["1P 2m 4P 5d 7m", "iwato"],
|
|
821
|
+
["1P 2m 4P 5P 7m", "in-sen"],
|
|
822
|
+
["1P 3M 4A 5P 7M", "lydian pentatonic", "chinese"],
|
|
823
|
+
["1P 3m 4P 6m 7m", "malkos raga"],
|
|
824
|
+
["1P 3m 4P 5d 7m", "locrian pentatonic", "minor seven flat five pentatonic"],
|
|
825
|
+
["1P 3m 4P 5P 7m", "minor pentatonic", "vietnamese 2"],
|
|
826
|
+
["1P 3m 4P 5P 6M", "minor six pentatonic"],
|
|
827
|
+
["1P 2M 3m 5P 6M", "flat three pentatonic", "kumoi"],
|
|
828
|
+
["1P 2M 3M 5P 6m", "flat six pentatonic"],
|
|
829
|
+
["1P 2m 3M 5P 6M", "scriabin"],
|
|
830
|
+
["1P 3M 5d 6m 7m", "whole tone pentatonic"],
|
|
831
|
+
["1P 3M 4A 5A 7M", "lydian #5P pentatonic"],
|
|
832
|
+
["1P 3M 4A 5P 7m", "lydian dominant pentatonic"],
|
|
833
|
+
["1P 3m 4P 5P 7M", "minor #7M pentatonic"],
|
|
834
|
+
["1P 3m 4d 5d 7m", "super locrian pentatonic"],
|
|
835
|
+
// 6-note scales
|
|
836
|
+
["1P 2M 3m 4P 5P 7M", "minor hexatonic"],
|
|
837
|
+
["1P 2A 3M 5P 5A 7M", "augmented"],
|
|
838
|
+
["1P 2M 4P 5P 6M 7m", "piongio"],
|
|
839
|
+
["1P 2m 3M 4A 6M 7m", "prometheus neopolitan"],
|
|
840
|
+
["1P 2M 3M 4A 6M 7m", "prometheus"],
|
|
841
|
+
["1P 2m 3M 5d 6m 7m", "mystery #1"],
|
|
842
|
+
["1P 2m 3M 4P 5A 6M", "six tone symmetric"],
|
|
843
|
+
["1P 2M 3M 4A 5A 6A", "whole tone", "messiaen's mode #1"],
|
|
844
|
+
["1P 2m 4P 4A 5P 7M", "messiaen's mode #5"],
|
|
845
|
+
// 7-note scales
|
|
846
|
+
["1P 2M 3M 4P 5d 6m 7m", "locrian major", "arabian"],
|
|
847
|
+
["1P 2m 3M 4A 5P 6m 7M", "double harmonic lydian"],
|
|
848
|
+
[
|
|
849
|
+
"1P 2m 2A 3M 4A 6m 7m",
|
|
850
|
+
"altered",
|
|
851
|
+
"super locrian",
|
|
852
|
+
"diminished whole tone",
|
|
853
|
+
"pomeroy"
|
|
854
|
+
],
|
|
855
|
+
["1P 2M 3m 4P 5d 6m 7m", "locrian #2", "half-diminished", "aeolian b5"],
|
|
856
|
+
[
|
|
857
|
+
"1P 2M 3M 4P 5P 6m 7m",
|
|
858
|
+
"mixolydian b6",
|
|
859
|
+
"melodic minor fifth mode",
|
|
860
|
+
"hindu"
|
|
861
|
+
],
|
|
862
|
+
["1P 2M 3M 4A 5P 6M 7m", "lydian dominant", "lydian b7", "overtone"],
|
|
863
|
+
["1P 2M 3M 4A 5A 6M 7M", "lydian augmented"],
|
|
864
|
+
[
|
|
865
|
+
"1P 2m 3m 4P 5P 6M 7m",
|
|
866
|
+
"dorian b2",
|
|
867
|
+
"phrygian #6",
|
|
868
|
+
"melodic minor second mode"
|
|
869
|
+
],
|
|
870
|
+
[
|
|
871
|
+
"1P 2m 3m 4d 5d 6m 7d",
|
|
872
|
+
"ultralocrian",
|
|
873
|
+
"superlocrian bb7",
|
|
874
|
+
"superlocrian diminished"
|
|
875
|
+
],
|
|
876
|
+
["1P 2m 3m 4P 5d 6M 7m", "locrian 6", "locrian natural 6", "locrian sharp 6"],
|
|
877
|
+
["1P 2A 3M 4P 5P 5A 7M", "augmented heptatonic"],
|
|
878
|
+
// Source https://en.wikipedia.org/wiki/Ukrainian_Dorian_scale
|
|
879
|
+
[
|
|
880
|
+
"1P 2M 3m 4A 5P 6M 7m",
|
|
881
|
+
"dorian #4",
|
|
882
|
+
"ukrainian dorian",
|
|
883
|
+
"romanian minor",
|
|
884
|
+
"altered dorian"
|
|
885
|
+
],
|
|
886
|
+
["1P 2M 3m 4A 5P 6M 7M", "lydian diminished"],
|
|
887
|
+
["1P 2M 3M 4A 5A 7m 7M", "leading whole tone"],
|
|
888
|
+
["1P 2M 3M 4A 5P 6m 7m", "lydian minor"],
|
|
889
|
+
["1P 2m 3M 4P 5P 6m 7m", "phrygian dominant", "spanish", "phrygian major"],
|
|
890
|
+
["1P 2m 3m 4P 5P 6m 7M", "balinese"],
|
|
891
|
+
["1P 2m 3m 4P 5P 6M 7M", "neopolitan major"],
|
|
892
|
+
["1P 2M 3M 4P 5P 6m 7M", "harmonic major"],
|
|
893
|
+
["1P 2m 3M 4P 5P 6m 7M", "double harmonic major", "gypsy"],
|
|
894
|
+
["1P 2M 3m 4A 5P 6m 7M", "hungarian minor"],
|
|
895
|
+
["1P 2A 3M 4A 5P 6M 7m", "hungarian major"],
|
|
896
|
+
["1P 2m 3M 4P 5d 6M 7m", "oriental"],
|
|
897
|
+
["1P 2m 3m 3M 4A 5P 7m", "flamenco"],
|
|
898
|
+
["1P 2m 3m 4A 5P 6m 7M", "todi raga"],
|
|
899
|
+
["1P 2m 3M 4P 5d 6m 7M", "persian"],
|
|
900
|
+
["1P 2m 3M 5d 6m 7m 7M", "enigmatic"],
|
|
901
|
+
[
|
|
902
|
+
"1P 2M 3M 4P 5A 6M 7M",
|
|
903
|
+
"major augmented",
|
|
904
|
+
"major #5",
|
|
905
|
+
"ionian augmented",
|
|
906
|
+
"ionian #5"
|
|
907
|
+
],
|
|
908
|
+
["1P 2A 3M 4A 5P 6M 7M", "lydian #9"],
|
|
909
|
+
// 8-note scales
|
|
910
|
+
["1P 2m 2M 4P 4A 5P 6m 7M", "messiaen's mode #4"],
|
|
911
|
+
["1P 2m 3M 4P 4A 5P 6m 7M", "purvi raga"],
|
|
912
|
+
["1P 2m 3m 3M 4P 5P 6m 7m", "spanish heptatonic"],
|
|
913
|
+
["1P 2M 3m 3M 4P 5P 6M 7m", "bebop minor"],
|
|
914
|
+
["1P 2M 3M 4P 5P 5A 6M 7M", "bebop major"],
|
|
915
|
+
["1P 2m 3m 4P 5d 5P 6m 7m", "bebop locrian"],
|
|
916
|
+
["1P 2M 3m 4P 5P 6m 7m 7M", "minor bebop"],
|
|
917
|
+
["1P 2M 3M 4P 5d 5P 6M 7M", "ichikosucho"],
|
|
918
|
+
["1P 2M 3m 4P 5P 6m 6M 7M", "minor six diminished"],
|
|
919
|
+
[
|
|
920
|
+
"1P 2m 3m 3M 4A 5P 6M 7m",
|
|
921
|
+
"half-whole diminished",
|
|
922
|
+
"dominant diminished",
|
|
923
|
+
"messiaen's mode #2"
|
|
924
|
+
],
|
|
925
|
+
["1P 3m 3M 4P 5P 6M 7m 7M", "kafi raga"],
|
|
926
|
+
["1P 2M 3M 4P 4A 5A 6A 7M", "messiaen's mode #6"],
|
|
927
|
+
// 9-note scales
|
|
928
|
+
["1P 2M 3m 3M 4P 5d 5P 6M 7m", "composite blues"],
|
|
929
|
+
["1P 2M 3m 3M 4A 5P 6m 7m 7M", "messiaen's mode #3"],
|
|
930
|
+
// 10-note scales
|
|
931
|
+
["1P 2m 2M 3m 4P 4A 5P 6m 6M 7M", "messiaen's mode #7"],
|
|
932
|
+
// 12-note scales
|
|
933
|
+
["1P 2m 2M 3m 3M 4P 5d 5P 6m 6M 7m 7M", "chromatic"]
|
|
934
|
+
];
|
|
935
|
+
var data_default$1 = SCALES;
|
|
936
|
+
var NoScaleType = {
|
|
937
|
+
...EmptyPcset,
|
|
938
|
+
intervals: [],
|
|
939
|
+
aliases: []
|
|
940
|
+
};
|
|
941
|
+
var index$1 = {};
|
|
942
|
+
function get$3(type) {
|
|
943
|
+
return index$1[type] || NoScaleType;
|
|
944
|
+
}
|
|
945
|
+
function add$1(intervals, name, aliases = []) {
|
|
946
|
+
const scale = { ...get$5(intervals), name, intervals, aliases };
|
|
947
|
+
index$1[scale.name] = scale;
|
|
948
|
+
index$1[scale.setNum] = scale;
|
|
949
|
+
index$1[scale.chroma] = scale;
|
|
950
|
+
scale.aliases.forEach((alias) => addAlias(scale, alias));
|
|
951
|
+
return scale;
|
|
952
|
+
}
|
|
953
|
+
function addAlias(scale, alias) {
|
|
954
|
+
index$1[alias] = scale;
|
|
955
|
+
}
|
|
956
|
+
data_default$1.forEach(
|
|
957
|
+
([ivls, name, ...aliases]) => add$1(ivls.split(" "), name, aliases)
|
|
958
|
+
);
|
|
959
|
+
var NoChord = {
|
|
960
|
+
empty: true,
|
|
961
|
+
name: "",
|
|
962
|
+
symbol: "",
|
|
963
|
+
root: "",
|
|
964
|
+
bass: "",
|
|
965
|
+
rootDegree: 0,
|
|
966
|
+
type: "",
|
|
967
|
+
tonic: null,
|
|
968
|
+
setNum: NaN,
|
|
969
|
+
quality: "Unknown",
|
|
970
|
+
chroma: "",
|
|
971
|
+
normalized: "",
|
|
972
|
+
aliases: [],
|
|
973
|
+
notes: [],
|
|
974
|
+
intervals: []
|
|
975
|
+
};
|
|
976
|
+
function tokenize(name) {
|
|
977
|
+
const [letter, acc, oct, type] = tokenizeNote(name);
|
|
978
|
+
if (letter === "") {
|
|
979
|
+
return tokenizeBass("", name);
|
|
980
|
+
} else if (letter === "A" && type === "ug") {
|
|
981
|
+
return tokenizeBass("", "aug");
|
|
982
|
+
} else {
|
|
983
|
+
return tokenizeBass(letter + acc, oct + type);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
function tokenizeBass(note2, chord2) {
|
|
987
|
+
const split = chord2.split("/");
|
|
988
|
+
if (split.length === 1) {
|
|
989
|
+
return [note2, split[0], ""];
|
|
990
|
+
}
|
|
991
|
+
const [letter, acc, oct, type] = tokenizeNote(split[1]);
|
|
992
|
+
if (letter !== "" && oct === "" && type === "") {
|
|
993
|
+
return [note2, split[0], letter + acc];
|
|
994
|
+
} else {
|
|
995
|
+
return [note2, chord2, ""];
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function get$2(src) {
|
|
999
|
+
if (Array.isArray(src)) {
|
|
1000
|
+
return getChord(src[1] || "", src[0], src[2]);
|
|
1001
|
+
} else if (src === "") {
|
|
1002
|
+
return NoChord;
|
|
1003
|
+
} else {
|
|
1004
|
+
const [tonic, type, bass] = tokenize(src);
|
|
1005
|
+
const chord2 = getChord(type, tonic, bass);
|
|
1006
|
+
return chord2.empty ? getChord(src) : chord2;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function getChord(typeName, optionalTonic, optionalBass) {
|
|
1010
|
+
const type = get$4(typeName);
|
|
1011
|
+
const tonic = note(optionalTonic || "");
|
|
1012
|
+
const bass = note(optionalBass || "");
|
|
1013
|
+
if (type.empty || optionalTonic && tonic.empty || optionalBass && bass.empty) {
|
|
1014
|
+
return NoChord;
|
|
1015
|
+
}
|
|
1016
|
+
const bassInterval = distance(tonic.pc, bass.pc);
|
|
1017
|
+
const bassIndex = type.intervals.indexOf(bassInterval);
|
|
1018
|
+
const hasRoot = bassIndex >= 0;
|
|
1019
|
+
const root = hasRoot ? bass : note("");
|
|
1020
|
+
const rootDegree = bassIndex === -1 ? NaN : bassIndex + 1;
|
|
1021
|
+
const hasBass = bass.pc && bass.pc !== tonic.pc;
|
|
1022
|
+
const intervals = Array.from(type.intervals);
|
|
1023
|
+
if (hasRoot) {
|
|
1024
|
+
for (let i = 1; i < rootDegree; i++) {
|
|
1025
|
+
const num = intervals[0][0];
|
|
1026
|
+
const quality = intervals[0][1];
|
|
1027
|
+
const newNum = parseInt(num, 10) + 7;
|
|
1028
|
+
intervals.push(`${newNum}${quality}`);
|
|
1029
|
+
intervals.shift();
|
|
1030
|
+
}
|
|
1031
|
+
} else if (hasBass) {
|
|
1032
|
+
const ivl = subtract(distance(tonic.pc, bass.pc), "8P");
|
|
1033
|
+
if (ivl) intervals.unshift(ivl);
|
|
1034
|
+
}
|
|
1035
|
+
const notes2 = tonic.empty ? [] : intervals.map((i) => transpose(tonic.pc, i));
|
|
1036
|
+
typeName = type.aliases.indexOf(typeName) !== -1 ? typeName : type.aliases[0];
|
|
1037
|
+
const symbol = `${tonic.empty ? "" : tonic.pc}${typeName}${hasRoot && rootDegree > 1 ? "/" + root.pc : hasBass ? "/" + bass.pc : ""}`;
|
|
1038
|
+
const name = `${optionalTonic ? tonic.pc + " " : ""}${type.name}${hasRoot && rootDegree > 1 ? " over " + root.pc : hasBass ? " over " + bass.pc : ""}`;
|
|
1039
|
+
return {
|
|
1040
|
+
...type,
|
|
1041
|
+
name,
|
|
1042
|
+
symbol,
|
|
1043
|
+
tonic: tonic.pc,
|
|
1044
|
+
type: type.name,
|
|
1045
|
+
root: root.pc,
|
|
1046
|
+
bass: hasBass ? bass.pc : "",
|
|
1047
|
+
intervals,
|
|
1048
|
+
rootDegree,
|
|
1049
|
+
notes: notes2
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
var DATA = [
|
|
1053
|
+
[
|
|
1054
|
+
0.125,
|
|
1055
|
+
"dl",
|
|
1056
|
+
["large", "duplex longa", "maxima", "octuple", "octuple whole"]
|
|
1057
|
+
],
|
|
1058
|
+
[0.25, "l", ["long", "longa"]],
|
|
1059
|
+
[0.5, "d", ["double whole", "double", "breve"]],
|
|
1060
|
+
[1, "w", ["whole", "semibreve"]],
|
|
1061
|
+
[2, "h", ["half", "minim"]],
|
|
1062
|
+
[4, "q", ["quarter", "crotchet"]],
|
|
1063
|
+
[8, "e", ["eighth", "quaver"]],
|
|
1064
|
+
[16, "s", ["sixteenth", "semiquaver"]],
|
|
1065
|
+
[32, "t", ["thirty-second", "demisemiquaver"]],
|
|
1066
|
+
[64, "sf", ["sixty-fourth", "hemidemisemiquaver"]],
|
|
1067
|
+
[128, "h", ["hundred twenty-eighth"]],
|
|
1068
|
+
[256, "th", ["two hundred fifty-sixth"]]
|
|
1069
|
+
];
|
|
1070
|
+
var data_default = DATA;
|
|
1071
|
+
data_default.forEach(
|
|
1072
|
+
([denominator, shorthand, names2]) => add()
|
|
1073
|
+
);
|
|
1074
|
+
function add(denominator, shorthand, names2) {
|
|
1075
|
+
}
|
|
1076
|
+
function midiToFreq(midi, tuning = 440) {
|
|
1077
|
+
return Math.pow(2, (midi - 69) / 12) * tuning;
|
|
1078
|
+
}
|
|
1079
|
+
var get$1 = note;
|
|
1080
|
+
var freq = (note2) => get$1(note2).freq;
|
|
1081
|
+
var MODES = [
|
|
1082
|
+
[0, 2773, 0, "ionian", "", "Maj7", "major"],
|
|
1083
|
+
[1, 2902, 2, "dorian", "m", "m7"],
|
|
1084
|
+
[2, 3418, 4, "phrygian", "m", "m7"],
|
|
1085
|
+
[3, 2741, -1, "lydian", "", "Maj7"],
|
|
1086
|
+
[4, 2774, 1, "mixolydian", "", "7"],
|
|
1087
|
+
[5, 2906, 3, "aeolian", "m", "m7", "minor"],
|
|
1088
|
+
[6, 3434, 5, "locrian", "dim", "m7b5"]
|
|
1089
|
+
];
|
|
1090
|
+
var NoMode = {
|
|
1091
|
+
...EmptyPcset,
|
|
1092
|
+
name: "",
|
|
1093
|
+
alt: 0,
|
|
1094
|
+
modeNum: NaN,
|
|
1095
|
+
triad: "",
|
|
1096
|
+
seventh: "",
|
|
1097
|
+
aliases: []
|
|
1098
|
+
};
|
|
1099
|
+
var modes = MODES.map(toMode);
|
|
1100
|
+
var index = {};
|
|
1101
|
+
modes.forEach((mode2) => {
|
|
1102
|
+
index[mode2.name] = mode2;
|
|
1103
|
+
mode2.aliases.forEach((alias) => {
|
|
1104
|
+
index[alias] = mode2;
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
function get(name) {
|
|
1108
|
+
return typeof name === "string" ? index[name.toLowerCase()] || NoMode : name && name.name ? get(name.name) : NoMode;
|
|
1109
|
+
}
|
|
1110
|
+
function toMode(mode2) {
|
|
1111
|
+
const [modeNum, setNum, alt, name, triad, seventh, alias] = mode2;
|
|
1112
|
+
const aliases = alias ? [alias] : [];
|
|
1113
|
+
const chroma = Number(setNum).toString(2);
|
|
1114
|
+
const intervals = get$3(name).intervals;
|
|
1115
|
+
return {
|
|
1116
|
+
empty: false,
|
|
1117
|
+
intervals,
|
|
1118
|
+
modeNum,
|
|
1119
|
+
chroma,
|
|
1120
|
+
normalized: chroma,
|
|
1121
|
+
name,
|
|
1122
|
+
setNum,
|
|
1123
|
+
alt,
|
|
1124
|
+
triad,
|
|
1125
|
+
seventh,
|
|
1126
|
+
aliases
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function chords(chords2) {
|
|
1130
|
+
return (modeName, tonic) => {
|
|
1131
|
+
const mode2 = get(modeName);
|
|
1132
|
+
if (mode2.empty) return [];
|
|
1133
|
+
const triads2 = rotate(mode2.modeNum, chords2);
|
|
1134
|
+
const tonics = mode2.intervals.map((i) => transpose(tonic, i));
|
|
1135
|
+
return triads2.map((triad, i) => tonics[i] + triad);
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
chords(MODES.map((x) => x[4]));
|
|
1139
|
+
chords(MODES.map((x) => x[5]));
|
|
1140
|
+
function midiToFrequency(midi) {
|
|
1141
|
+
return midiToFreq(midi);
|
|
1142
|
+
}
|
|
1143
|
+
function noteToFrequency(note2) {
|
|
1144
|
+
const freq$1 = freq(note2);
|
|
1145
|
+
if (freq$1 === null) {
|
|
1146
|
+
console.warn(`Invalid note: ${note2}, defaulting to A4 (440Hz)`);
|
|
1147
|
+
return 440;
|
|
1148
|
+
}
|
|
1149
|
+
return freq$1;
|
|
1150
|
+
}
|
|
1151
|
+
function resolveChordNotes(notes) {
|
|
1152
|
+
if (Array.isArray(notes)) {
|
|
1153
|
+
return notes;
|
|
1154
|
+
}
|
|
1155
|
+
const [chordName, octaveStr] = notes.split(":");
|
|
1156
|
+
const octave = octaveStr ? parseInt(octaveStr, 10) : 3;
|
|
1157
|
+
const chord = get$2(chordName);
|
|
1158
|
+
if (!chord.notes.length) {
|
|
1159
|
+
console.warn(`Unknown chord: ${chordName}, defaulting to root note`);
|
|
1160
|
+
return [chordName + octave];
|
|
1161
|
+
}
|
|
1162
|
+
const rootPc = chord.notes[0];
|
|
1163
|
+
const noteOrder = ["C", "D", "E", "F", "G", "A", "B"];
|
|
1164
|
+
const rootIndex = noteOrder.findIndex((n) => rootPc.startsWith(n));
|
|
1165
|
+
return chord.notes.map((note2) => {
|
|
1166
|
+
const noteIndex = noteOrder.findIndex((n) => note2.startsWith(n));
|
|
1167
|
+
const noteOctave = noteIndex < rootIndex ? octave + 1 : octave;
|
|
1168
|
+
return note2 + noteOctave;
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
function line(start, end, steps) {
|
|
1172
|
+
if (steps < 1) {
|
|
1173
|
+
throw new Error("Line must have at least 1 step");
|
|
1174
|
+
}
|
|
1175
|
+
const increment = (end - start) / steps;
|
|
1176
|
+
return Array.from({ length: steps }, (_, i) => start + increment * i);
|
|
1177
|
+
}
|
|
1178
|
+
function mirror(values) {
|
|
1179
|
+
return [...values, ...values.toReversed()];
|
|
1180
|
+
}
|
|
1181
|
+
function resolveCutoff(cutoff, stepIndex) {
|
|
1182
|
+
if (typeof cutoff === "number") {
|
|
1183
|
+
return midiToFrequency(cutoff);
|
|
1184
|
+
}
|
|
1185
|
+
const values = line(cutoff.from, cutoff.to, cutoff.steps);
|
|
1186
|
+
const sequence = cutoff.mirror ? mirror(values) : values;
|
|
1187
|
+
const effectiveStep = cutoff.step ?? stepIndex;
|
|
1188
|
+
return midiToFrequency(sequence[effectiveStep % sequence.length]);
|
|
1189
|
+
}
|
|
1190
|
+
const SYNTH_PRESETS = {
|
|
1191
|
+
/**
|
|
1192
|
+
* Pure sine wave - clean, fundamental tone
|
|
1193
|
+
*/
|
|
1194
|
+
sine: {
|
|
1195
|
+
oscillator: "sine",
|
|
1196
|
+
filter: { type: "lowpass", cutoff: 135.08, resonance: 1 },
|
|
1197
|
+
voices: { count: 1, detune: 0, spread: 0 }
|
|
1198
|
+
},
|
|
1199
|
+
/**
|
|
1200
|
+
* Sawtooth wave - bright, harmonically rich
|
|
1201
|
+
*/
|
|
1202
|
+
saw: {
|
|
1203
|
+
oscillator: "sawtooth",
|
|
1204
|
+
filter: { type: "lowpass", cutoff: 135.08, resonance: 1 },
|
|
1205
|
+
voices: { count: 1, detune: 0, spread: 0 }
|
|
1206
|
+
},
|
|
1207
|
+
/**
|
|
1208
|
+
* Square wave - hollow, clarinet-like
|
|
1209
|
+
*/
|
|
1210
|
+
square: {
|
|
1211
|
+
oscillator: "square",
|
|
1212
|
+
filter: { type: "lowpass", cutoff: 135.08, resonance: 1 },
|
|
1213
|
+
voices: { count: 1, detune: 0, spread: 0 }
|
|
1214
|
+
},
|
|
1215
|
+
/**
|
|
1216
|
+
* Triangle wave - soft, flute-like
|
|
1217
|
+
*/
|
|
1218
|
+
tri: {
|
|
1219
|
+
oscillator: "triangle",
|
|
1220
|
+
filter: { type: "lowpass", cutoff: 135.08, resonance: 1 },
|
|
1221
|
+
voices: { count: 1, detune: 0, spread: 0 }
|
|
1222
|
+
},
|
|
1223
|
+
/**
|
|
1224
|
+
* Prophet - Sequential Circuits Prophet-5 inspired
|
|
1225
|
+
* Warm analog sound with detuned voices and resonant filter
|
|
1226
|
+
* Great for leads and pads
|
|
1227
|
+
*/
|
|
1228
|
+
prophet: {
|
|
1229
|
+
oscillator: "sawtooth",
|
|
1230
|
+
filter: { type: "lowpass", cutoff: 102.23, resonance: 4 },
|
|
1231
|
+
voices: { count: 3, detune: 12, spread: 0.5 }
|
|
1232
|
+
},
|
|
1233
|
+
/**
|
|
1234
|
+
* Hollow - Ethereal pad sound
|
|
1235
|
+
* Filtered square wave with soft resonance
|
|
1236
|
+
* Good for atmospheric backgrounds
|
|
1237
|
+
*/
|
|
1238
|
+
hollow: {
|
|
1239
|
+
oscillator: "square",
|
|
1240
|
+
filter: { type: "bandpass", cutoff: 86.37, resonance: 2 },
|
|
1241
|
+
voices: { count: 2, detune: 8, spread: 0.7 }
|
|
1242
|
+
},
|
|
1243
|
+
/**
|
|
1244
|
+
* Dark Ambience - Deep atmospheric pad
|
|
1245
|
+
* Low-passed sawtooth with multiple detuned voices
|
|
1246
|
+
*/
|
|
1247
|
+
dark_ambience: {
|
|
1248
|
+
oscillator: "sawtooth",
|
|
1249
|
+
filter: { type: "lowpass", cutoff: 79.35, resonance: 3 },
|
|
1250
|
+
voices: { count: 4, detune: 15, spread: 0.8 }
|
|
1251
|
+
},
|
|
1252
|
+
/**
|
|
1253
|
+
* Bass - Punchy bass sound
|
|
1254
|
+
* Filtered sawtooth with slight detune for thickness
|
|
1255
|
+
*/
|
|
1256
|
+
bass: {
|
|
1257
|
+
oscillator: "sawtooth",
|
|
1258
|
+
filter: { type: "lowpass", cutoff: 74.37, resonance: 5 },
|
|
1259
|
+
voices: { count: 2, detune: 5, spread: 0.2 }
|
|
1260
|
+
},
|
|
1261
|
+
/**
|
|
1262
|
+
* Pluck - Bright plucked string sound
|
|
1263
|
+
* Square wave with high cutoff for attack brightness
|
|
1264
|
+
*/
|
|
1265
|
+
pluck: {
|
|
1266
|
+
oscillator: "square",
|
|
1267
|
+
filter: { type: "lowpass", cutoff: 111.08, resonance: 2 },
|
|
1268
|
+
voices: { count: 2, detune: 6, spread: 0.3 }
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
function getSynthPreset(type) {
|
|
1272
|
+
return SYNTH_PRESETS[type];
|
|
1273
|
+
}
|
|
1274
|
+
const DEFAULT_SYNTH_CONFIG = SYNTH_PRESETS.sine;
|
|
1275
|
+
const SynthContext = React.createContext(null);
|
|
1276
|
+
function Synth({
|
|
1277
|
+
type,
|
|
1278
|
+
oscillator,
|
|
1279
|
+
filter,
|
|
1280
|
+
voices,
|
|
1281
|
+
children
|
|
1282
|
+
}) {
|
|
1283
|
+
const config = React.useMemo(() => {
|
|
1284
|
+
const preset = getSynthPreset(type);
|
|
1285
|
+
return {
|
|
1286
|
+
oscillator: oscillator ?? preset.oscillator,
|
|
1287
|
+
filter: {
|
|
1288
|
+
type: filter?.type ?? preset.filter.type,
|
|
1289
|
+
cutoff: filter?.cutoff ?? preset.filter.cutoff,
|
|
1290
|
+
resonance: filter?.resonance ?? preset.filter.resonance
|
|
1291
|
+
},
|
|
1292
|
+
voices: {
|
|
1293
|
+
count: voices?.count ?? preset.voices.count,
|
|
1294
|
+
detune: voices?.detune ?? preset.voices.detune,
|
|
1295
|
+
spread: voices?.spread ?? preset.voices.spread
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
}, [type, oscillator, filter, voices]);
|
|
1299
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SynthContext.Provider, { value: config, children });
|
|
1300
|
+
}
|
|
1301
|
+
function useSynth() {
|
|
1302
|
+
const ctx = React.useContext(SynthContext);
|
|
1303
|
+
return ctx ?? DEFAULT_SYNTH_CONFIG;
|
|
1304
|
+
}
|
|
1305
|
+
function Note({
|
|
1306
|
+
note: note2,
|
|
1307
|
+
amp = 0.3,
|
|
1308
|
+
oscillator,
|
|
1309
|
+
filter,
|
|
1310
|
+
voices,
|
|
1311
|
+
attack = ADSR_DEFAULTS.attack,
|
|
1312
|
+
attack_level = ADSR_DEFAULTS.attack_level,
|
|
1313
|
+
decay = ADSR_DEFAULTS.decay,
|
|
1314
|
+
decay_level,
|
|
1315
|
+
sustain = ADSR_DEFAULTS.sustain,
|
|
1316
|
+
sustain_level = ADSR_DEFAULTS.sustain_level,
|
|
1317
|
+
release = ADSR_DEFAULTS.release,
|
|
1318
|
+
__stepIndex
|
|
1319
|
+
}) {
|
|
1320
|
+
const uniqueId = React.useId();
|
|
1321
|
+
const { audioContext, scheduler } = useTrack();
|
|
1322
|
+
const scheduleNote = useScheduleNote();
|
|
1323
|
+
const synthConfig = useSynth();
|
|
1324
|
+
const oscillatorType = oscillator ?? synthConfig.oscillator;
|
|
1325
|
+
const filterType = filter?.type ?? synthConfig.filter.type;
|
|
1326
|
+
const filterCutoff = resolveCutoff(
|
|
1327
|
+
filter?.cutoff ?? synthConfig.filter.cutoff,
|
|
1328
|
+
__stepIndex ?? 0
|
|
1329
|
+
);
|
|
1330
|
+
const filterResonance = filter?.resonance ?? synthConfig.filter.resonance;
|
|
1331
|
+
const voiceCount = voices?.count ?? synthConfig.voices.count;
|
|
1332
|
+
const voiceDetune = voices?.detune ?? synthConfig.voices.detune;
|
|
1333
|
+
const voiceSpread = voices?.spread ?? synthConfig.voices.spread;
|
|
1334
|
+
const frequency = typeof note2 === "number" ? note2 : noteToFrequency(note2);
|
|
1335
|
+
const adsrProps = {
|
|
1336
|
+
attack,
|
|
1337
|
+
attack_level,
|
|
1338
|
+
decay,
|
|
1339
|
+
decay_level,
|
|
1340
|
+
sustain,
|
|
1341
|
+
sustain_level,
|
|
1342
|
+
release
|
|
1343
|
+
};
|
|
1344
|
+
React.useEffect(() => {
|
|
1345
|
+
const playNote = (audioTime) => {
|
|
1346
|
+
const gainNode = audioContext.createGain();
|
|
1347
|
+
if (filterCutoff < 2e4) {
|
|
1348
|
+
const filter2 = audioContext.createBiquadFilter();
|
|
1349
|
+
filter2.type = filterType;
|
|
1350
|
+
filter2.frequency.value = filterCutoff;
|
|
1351
|
+
filter2.Q.value = filterResonance;
|
|
1352
|
+
gainNode.connect(filter2);
|
|
1353
|
+
filter2.connect(audioContext.destination);
|
|
1354
|
+
} else {
|
|
1355
|
+
gainNode.connect(audioContext.destination);
|
|
1356
|
+
}
|
|
1357
|
+
const endTime = applyADSREnvelope(
|
|
1358
|
+
gainNode,
|
|
1359
|
+
audioTime,
|
|
1360
|
+
adsrProps,
|
|
1361
|
+
amp / voiceCount,
|
|
1362
|
+
// Normalize amplitude across voices
|
|
1363
|
+
(beats) => scheduler.beatsToSeconds(beats)
|
|
1364
|
+
);
|
|
1365
|
+
for (let i = 0; i < voiceCount; i++) {
|
|
1366
|
+
const oscillator2 = audioContext.createOscillator();
|
|
1367
|
+
oscillator2.type = oscillatorType;
|
|
1368
|
+
oscillator2.frequency.value = frequency;
|
|
1369
|
+
if (voiceCount > 1 && voiceDetune > 0) {
|
|
1370
|
+
const detuneOffset = (i / (voiceCount - 1) - 0.5) * voiceDetune;
|
|
1371
|
+
oscillator2.detune.value = detuneOffset;
|
|
1372
|
+
}
|
|
1373
|
+
if (voiceCount > 1 && voiceSpread > 0) {
|
|
1374
|
+
const panner = audioContext.createStereoPanner();
|
|
1375
|
+
const panValue = (i / (voiceCount - 1) - 0.5) * 2 * voiceSpread;
|
|
1376
|
+
panner.pan.value = panValue;
|
|
1377
|
+
oscillator2.connect(panner);
|
|
1378
|
+
panner.connect(gainNode);
|
|
1379
|
+
} else {
|
|
1380
|
+
oscillator2.connect(gainNode);
|
|
1381
|
+
}
|
|
1382
|
+
oscillator2.start(audioTime);
|
|
1383
|
+
oscillator2.stop(endTime + 0.01);
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
scheduleNote.scheduleNote(uniqueId, playNote, __stepIndex);
|
|
1387
|
+
return () => {
|
|
1388
|
+
scheduleNote.unscheduleNote(uniqueId);
|
|
1389
|
+
};
|
|
1390
|
+
}, [
|
|
1391
|
+
note2,
|
|
1392
|
+
frequency,
|
|
1393
|
+
amp,
|
|
1394
|
+
oscillatorType,
|
|
1395
|
+
filterCutoff,
|
|
1396
|
+
filterResonance,
|
|
1397
|
+
filterType,
|
|
1398
|
+
voiceCount,
|
|
1399
|
+
voiceDetune,
|
|
1400
|
+
voiceSpread,
|
|
1401
|
+
adsrProps,
|
|
1402
|
+
audioContext,
|
|
1403
|
+
scheduler,
|
|
1404
|
+
scheduleNote,
|
|
1405
|
+
__stepIndex,
|
|
1406
|
+
uniqueId
|
|
1407
|
+
]);
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
const sampleCache = /* @__PURE__ */ new Map();
|
|
1411
|
+
function getSamplesDir() {
|
|
1412
|
+
try {
|
|
1413
|
+
const require$1 = module$1.createRequire(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
1414
|
+
const packageMain = require$1.resolve("@react-synth/synth");
|
|
1415
|
+
const packageDir = path.dirname(packageMain);
|
|
1416
|
+
return path.join(packageDir, "..", "src", "samples");
|
|
1417
|
+
} catch {
|
|
1418
|
+
const currentFile = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
1419
|
+
const currentDir = path.dirname(currentFile);
|
|
1420
|
+
return path.join(currentDir, "..", "samples");
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const samplesDir = getSamplesDir();
|
|
1424
|
+
function getSamplePath(name) {
|
|
1425
|
+
return path.join(samplesDir, `${name}.flac`);
|
|
1426
|
+
}
|
|
1427
|
+
async function loadSample(audioContext, name) {
|
|
1428
|
+
if (sampleCache.has(name)) {
|
|
1429
|
+
return sampleCache.get(name);
|
|
1430
|
+
}
|
|
1431
|
+
const path2 = getSamplePath(name);
|
|
1432
|
+
const fileData = await promises.readFile(path2);
|
|
1433
|
+
const audioBuffer = await audioContext.decodeAudioData(
|
|
1434
|
+
fileData.buffer
|
|
1435
|
+
);
|
|
1436
|
+
sampleCache.set(name, audioBuffer);
|
|
1437
|
+
return audioBuffer;
|
|
1438
|
+
}
|
|
1439
|
+
function Sample({
|
|
1440
|
+
name,
|
|
1441
|
+
amp = 1,
|
|
1442
|
+
cutoff,
|
|
1443
|
+
rate = 1,
|
|
1444
|
+
pan = 0,
|
|
1445
|
+
__stepIndex
|
|
1446
|
+
}) {
|
|
1447
|
+
const uniqueId = React.useId();
|
|
1448
|
+
const { audioContext } = useTrack();
|
|
1449
|
+
const scheduleNote = useScheduleNote();
|
|
1450
|
+
const filterCutoff = cutoff !== void 0 ? resolveCutoff(cutoff, __stepIndex ?? 0) : 2e4;
|
|
1451
|
+
React.useEffect(() => {
|
|
1452
|
+
let buffer = null;
|
|
1453
|
+
loadSample(audioContext, name).then((loaded) => {
|
|
1454
|
+
buffer = loaded;
|
|
1455
|
+
});
|
|
1456
|
+
const playSample = (audioTime) => {
|
|
1457
|
+
if (!buffer) return;
|
|
1458
|
+
const source = audioContext.createBufferSource();
|
|
1459
|
+
source.buffer = buffer;
|
|
1460
|
+
source.playbackRate.value = rate;
|
|
1461
|
+
let currentNode = source;
|
|
1462
|
+
if (filterCutoff < 2e4) {
|
|
1463
|
+
const filter = audioContext.createBiquadFilter();
|
|
1464
|
+
filter.type = "lowpass";
|
|
1465
|
+
filter.frequency.value = filterCutoff;
|
|
1466
|
+
currentNode.connect(filter);
|
|
1467
|
+
currentNode = filter;
|
|
1468
|
+
}
|
|
1469
|
+
if (pan !== 0) {
|
|
1470
|
+
const panner = audioContext.createStereoPanner();
|
|
1471
|
+
panner.pan.value = pan;
|
|
1472
|
+
currentNode.connect(panner);
|
|
1473
|
+
currentNode = panner;
|
|
1474
|
+
}
|
|
1475
|
+
const gainNode = audioContext.createGain();
|
|
1476
|
+
gainNode.gain.value = amp;
|
|
1477
|
+
currentNode.connect(gainNode);
|
|
1478
|
+
gainNode.connect(audioContext.destination);
|
|
1479
|
+
source.start(audioTime);
|
|
1480
|
+
};
|
|
1481
|
+
scheduleNote.scheduleNote(uniqueId, playSample, __stepIndex);
|
|
1482
|
+
return () => {
|
|
1483
|
+
scheduleNote.unscheduleNote(uniqueId);
|
|
1484
|
+
};
|
|
1485
|
+
}, [
|
|
1486
|
+
audioContext,
|
|
1487
|
+
name,
|
|
1488
|
+
scheduleNote,
|
|
1489
|
+
uniqueId,
|
|
1490
|
+
__stepIndex,
|
|
1491
|
+
amp,
|
|
1492
|
+
filterCutoff,
|
|
1493
|
+
rate,
|
|
1494
|
+
pan
|
|
1495
|
+
]);
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
function Chord({
|
|
1499
|
+
notes,
|
|
1500
|
+
amp = 0.3,
|
|
1501
|
+
oscillator,
|
|
1502
|
+
filter,
|
|
1503
|
+
voices,
|
|
1504
|
+
attack = ADSR_DEFAULTS.attack,
|
|
1505
|
+
attack_level = ADSR_DEFAULTS.attack_level,
|
|
1506
|
+
decay = ADSR_DEFAULTS.decay,
|
|
1507
|
+
decay_level,
|
|
1508
|
+
sustain = ADSR_DEFAULTS.sustain,
|
|
1509
|
+
sustain_level = ADSR_DEFAULTS.sustain_level,
|
|
1510
|
+
release = ADSR_DEFAULTS.release,
|
|
1511
|
+
__stepIndex
|
|
1512
|
+
}) {
|
|
1513
|
+
const uniqueId = React.useId();
|
|
1514
|
+
const { audioContext, scheduler } = useTrack();
|
|
1515
|
+
const { scheduleNote, unscheduleNote } = useScheduleNote();
|
|
1516
|
+
const synthConfig = useSynth();
|
|
1517
|
+
const oscillatorType = oscillator ?? synthConfig.oscillator;
|
|
1518
|
+
const filterType = filter?.type ?? synthConfig.filter.type;
|
|
1519
|
+
const filterCutoff = resolveCutoff(
|
|
1520
|
+
filter?.cutoff ?? synthConfig.filter.cutoff,
|
|
1521
|
+
__stepIndex ?? 0
|
|
1522
|
+
);
|
|
1523
|
+
const filterResonance = filter?.resonance ?? synthConfig.filter.resonance;
|
|
1524
|
+
const voiceCount = voices?.count ?? synthConfig.voices.count;
|
|
1525
|
+
const voiceDetune = voices?.detune ?? synthConfig.voices.detune;
|
|
1526
|
+
const voiceSpread = voices?.spread ?? synthConfig.voices.spread;
|
|
1527
|
+
const resolvedNotes = resolveChordNotes(notes);
|
|
1528
|
+
const frequencies = resolvedNotes.map(
|
|
1529
|
+
(note2) => typeof note2 === "number" ? note2 : noteToFrequency(note2)
|
|
1530
|
+
);
|
|
1531
|
+
const adsrProps = {
|
|
1532
|
+
attack,
|
|
1533
|
+
attack_level,
|
|
1534
|
+
decay,
|
|
1535
|
+
decay_level,
|
|
1536
|
+
sustain,
|
|
1537
|
+
sustain_level,
|
|
1538
|
+
release
|
|
1539
|
+
};
|
|
1540
|
+
const totalOscillators = frequencies.length * voiceCount;
|
|
1541
|
+
React.useEffect(() => {
|
|
1542
|
+
const playChord = (audioTime) => {
|
|
1543
|
+
const gainNode = audioContext.createGain();
|
|
1544
|
+
if (filterCutoff < 2e4) {
|
|
1545
|
+
const filter2 = audioContext.createBiquadFilter();
|
|
1546
|
+
filter2.type = filterType;
|
|
1547
|
+
filter2.frequency.value = filterCutoff;
|
|
1548
|
+
filter2.Q.value = filterResonance;
|
|
1549
|
+
gainNode.connect(filter2);
|
|
1550
|
+
filter2.connect(audioContext.destination);
|
|
1551
|
+
} else {
|
|
1552
|
+
gainNode.connect(audioContext.destination);
|
|
1553
|
+
}
|
|
1554
|
+
const endTime = applyADSREnvelope(
|
|
1555
|
+
gainNode,
|
|
1556
|
+
audioTime,
|
|
1557
|
+
adsrProps,
|
|
1558
|
+
amp / totalOscillators,
|
|
1559
|
+
// Normalize amplitude across all oscillators
|
|
1560
|
+
(beats) => scheduler.beatsToSeconds(beats)
|
|
1561
|
+
);
|
|
1562
|
+
for (const frequency of frequencies) {
|
|
1563
|
+
for (let i = 0; i < voiceCount; i++) {
|
|
1564
|
+
const oscillator2 = audioContext.createOscillator();
|
|
1565
|
+
oscillator2.type = oscillatorType;
|
|
1566
|
+
oscillator2.frequency.value = frequency;
|
|
1567
|
+
if (voiceCount > 1 && voiceDetune > 0) {
|
|
1568
|
+
const detuneOffset = (i / (voiceCount - 1) - 0.5) * voiceDetune;
|
|
1569
|
+
oscillator2.detune.value = detuneOffset;
|
|
1570
|
+
}
|
|
1571
|
+
if (voiceCount > 1 && voiceSpread > 0) {
|
|
1572
|
+
const panner = audioContext.createStereoPanner();
|
|
1573
|
+
const panValue = (i / (voiceCount - 1) - 0.5) * 2 * voiceSpread;
|
|
1574
|
+
panner.pan.value = panValue;
|
|
1575
|
+
oscillator2.connect(panner);
|
|
1576
|
+
panner.connect(gainNode);
|
|
1577
|
+
} else {
|
|
1578
|
+
oscillator2.connect(gainNode);
|
|
1579
|
+
}
|
|
1580
|
+
oscillator2.start(audioTime);
|
|
1581
|
+
oscillator2.stop(endTime + 0.01);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
scheduleNote(uniqueId, playChord, __stepIndex);
|
|
1586
|
+
return () => {
|
|
1587
|
+
unscheduleNote(uniqueId);
|
|
1588
|
+
};
|
|
1589
|
+
}, [
|
|
1590
|
+
notes,
|
|
1591
|
+
frequencies,
|
|
1592
|
+
amp,
|
|
1593
|
+
oscillatorType,
|
|
1594
|
+
filterCutoff,
|
|
1595
|
+
filterResonance,
|
|
1596
|
+
filterType,
|
|
1597
|
+
voiceCount,
|
|
1598
|
+
voiceDetune,
|
|
1599
|
+
voiceSpread,
|
|
1600
|
+
totalOscillators,
|
|
1601
|
+
adsrProps,
|
|
1602
|
+
audioContext,
|
|
1603
|
+
scheduler,
|
|
1604
|
+
scheduleNote,
|
|
1605
|
+
unscheduleNote,
|
|
1606
|
+
__stepIndex,
|
|
1607
|
+
uniqueId
|
|
1608
|
+
]);
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
function Sequence({
|
|
1612
|
+
interval: interval2,
|
|
1613
|
+
children,
|
|
1614
|
+
__stepIndex
|
|
1615
|
+
}) {
|
|
1616
|
+
const uniqueId = React.useId();
|
|
1617
|
+
const { scheduler } = useTrack();
|
|
1618
|
+
const { scheduleNote, unscheduleNote } = useScheduleNote();
|
|
1619
|
+
const childArray = React.Children.toArray(children);
|
|
1620
|
+
const totalSteps = childArray.length;
|
|
1621
|
+
const stepCallbacks = React.useRef(/* @__PURE__ */ new Map());
|
|
1622
|
+
const idToStepIndex = React.useRef(/* @__PURE__ */ new Map());
|
|
1623
|
+
React.useEffect(() => {
|
|
1624
|
+
const sequenceCallback = (audioTime, beatTime) => {
|
|
1625
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
1626
|
+
const stepOffset = scheduler.beatsToSeconds(i * interval2);
|
|
1627
|
+
const stepAudioTime = audioTime + stepOffset;
|
|
1628
|
+
const stepBeatTime = beatTime + i * interval2;
|
|
1629
|
+
const callback = stepCallbacks.current.get(i);
|
|
1630
|
+
if (callback) {
|
|
1631
|
+
callback(stepAudioTime, stepBeatTime);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
scheduleNote(`seq-${uniqueId}`, sequenceCallback, __stepIndex);
|
|
1636
|
+
return () => {
|
|
1637
|
+
unscheduleNote(`seq-${uniqueId}`);
|
|
1638
|
+
};
|
|
1639
|
+
}, [
|
|
1640
|
+
interval2,
|
|
1641
|
+
totalSteps,
|
|
1642
|
+
scheduler,
|
|
1643
|
+
uniqueId,
|
|
1644
|
+
scheduleNote,
|
|
1645
|
+
unscheduleNote,
|
|
1646
|
+
__stepIndex
|
|
1647
|
+
]);
|
|
1648
|
+
const scheduleContextValue = React.useMemo(
|
|
1649
|
+
() => ({
|
|
1650
|
+
scheduleNote: (noteId, callback, stepIndex) => {
|
|
1651
|
+
if (stepIndex !== void 0) {
|
|
1652
|
+
idToStepIndex.current.set(noteId, stepIndex);
|
|
1653
|
+
stepCallbacks.current.set(stepIndex, callback);
|
|
1654
|
+
}
|
|
1655
|
+
},
|
|
1656
|
+
unscheduleNote: (noteId) => {
|
|
1657
|
+
const stepIndex = idToStepIndex.current.get(noteId);
|
|
1658
|
+
if (stepIndex !== void 0) {
|
|
1659
|
+
stepCallbacks.current.delete(stepIndex);
|
|
1660
|
+
idToStepIndex.current.delete(noteId);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}),
|
|
1664
|
+
[]
|
|
1665
|
+
);
|
|
1666
|
+
const childrenWithIndex = childArray.map((child, index2) => {
|
|
1667
|
+
if (React.isValidElement(child)) {
|
|
1668
|
+
return React.cloneElement(child, {
|
|
1669
|
+
key: `step-${index2}`,
|
|
1670
|
+
__stepIndex: index2
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
return child;
|
|
1674
|
+
});
|
|
1675
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ScheduleNoteContext.Provider, { value: scheduleContextValue, children: childrenWithIndex });
|
|
1676
|
+
}
|
|
1677
|
+
exports.ADSR_DEFAULTS = ADSR_DEFAULTS;
|
|
1678
|
+
exports.Chord = Chord;
|
|
1679
|
+
exports.DEFAULT_SYNTH_CONFIG = DEFAULT_SYNTH_CONFIG;
|
|
1680
|
+
exports.Loop = Loop;
|
|
1681
|
+
exports.Note = Note;
|
|
1682
|
+
exports.SYNTH_PRESETS = SYNTH_PRESETS;
|
|
1683
|
+
exports.Sample = Sample;
|
|
1684
|
+
exports.ScheduleNoteContext = ScheduleNoteContext;
|
|
1685
|
+
exports.Scheduler = Scheduler;
|
|
1686
|
+
exports.Sequence = Sequence;
|
|
1687
|
+
exports.Synth = Synth;
|
|
1688
|
+
exports.Track = Track;
|
|
1689
|
+
exports.applyADSREnvelope = applyADSREnvelope;
|
|
1690
|
+
exports.getScheduler = getScheduler;
|
|
1691
|
+
exports.getSynthPreset = getSynthPreset;
|
|
1692
|
+
exports.line = line;
|
|
1693
|
+
exports.midiToFrequency = midiToFrequency;
|
|
1694
|
+
exports.mirror = mirror;
|
|
1695
|
+
exports.noteToFrequency = noteToFrequency;
|
|
1696
|
+
exports.resetScheduler = resetScheduler;
|
|
1697
|
+
exports.resolveChordNotes = resolveChordNotes;
|
|
1698
|
+
exports.resolveCutoff = resolveCutoff;
|
|
1699
|
+
exports.useScheduleNote = useScheduleNote;
|
|
1700
|
+
exports.useSynth = useSynth;
|
|
1701
|
+
exports.useTrack = useTrack;
|
|
1702
|
+
//# sourceMappingURL=index.cjs.map
|