scribbletune 5.1.2 → 5.4.0
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/dist/browser.cjs +1183 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.js +1135 -1
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +571 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +323 -0
- package/dist/index.d.ts +303 -350
- package/dist/index.js +524 -1
- package/dist/index.js.map +1 -1
- package/dist/scribbletune.global.js +1944 -0
- package/dist/scribbletune.global.js.map +1 -0
- package/package.json +32 -45
- package/dist/scribbletune.es.js +0 -588
- package/dist/scribbletune.js +0 -2
- package/dist/scribbletune.js.map +0 -1
- package/dist/scribbletune.umd.js +0 -1
|
@@ -0,0 +1,1944 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var scribble = (() => {
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
10
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
11
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
12
|
+
}) : x)(function(x) {
|
|
13
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
14
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
|
+
});
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
38
|
+
|
|
39
|
+
// src/browser-index.ts
|
|
40
|
+
var browser_index_exports = {};
|
|
41
|
+
__export(browser_index_exports, {
|
|
42
|
+
Session: () => Session,
|
|
43
|
+
arp: () => arp,
|
|
44
|
+
chord: () => chord,
|
|
45
|
+
chords: () => chords,
|
|
46
|
+
clip: () => clip,
|
|
47
|
+
getChordDegrees: () => getChordDegrees,
|
|
48
|
+
getChordsByProgression: () => getChordsByProgression,
|
|
49
|
+
midi: () => midi,
|
|
50
|
+
mode: () => scale,
|
|
51
|
+
modes: () => scales,
|
|
52
|
+
progression: () => progression,
|
|
53
|
+
scale: () => scale,
|
|
54
|
+
scales: () => scales
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// node_modules/harmonics/dist/index.mjs
|
|
58
|
+
var scaleMaps_default = {
|
|
59
|
+
"major pentatonic": "101010010100",
|
|
60
|
+
"ionian pentatonic": "100011010001",
|
|
61
|
+
"mixolydian pentatonic": "100011010010",
|
|
62
|
+
ritusen: "101001010100",
|
|
63
|
+
egyptian: "101001010010",
|
|
64
|
+
"neopolitan major pentatonic": "100011100010",
|
|
65
|
+
"vietnamese 1": "100101011000",
|
|
66
|
+
pelog: "110100011000",
|
|
67
|
+
kumoijoshi: "110001011000",
|
|
68
|
+
hirajoshi: "101100011000",
|
|
69
|
+
iwato: "110001100010",
|
|
70
|
+
"in-sen": "110001010010",
|
|
71
|
+
"lydian pentatonic": "100010110001",
|
|
72
|
+
"malkos raga": "100101001010",
|
|
73
|
+
"locrian pentatonic": "100101100010",
|
|
74
|
+
"minor pentatonic": "100101010010",
|
|
75
|
+
"minor six pentatonic": "100101010100",
|
|
76
|
+
"flat three pentatonic": "101100010100",
|
|
77
|
+
"flat six pentatonic": "101010011000",
|
|
78
|
+
scriabin: "110010010100",
|
|
79
|
+
"whole tone pentatonic": "100010101010",
|
|
80
|
+
"lydian #5P pentatonic": "100010101001",
|
|
81
|
+
"lydian dominant pentatonic": "100010110010",
|
|
82
|
+
"minor #7M pentatonic": "100101010001",
|
|
83
|
+
"super locrian pentatonic": "100110100010",
|
|
84
|
+
"minor hexatonic": "101101010001",
|
|
85
|
+
augmented: "100110011001",
|
|
86
|
+
"major blues": "101110010100",
|
|
87
|
+
piongio: "101001010110",
|
|
88
|
+
"prometheus neopolitan": "110010100110",
|
|
89
|
+
prometheus: "101010100110",
|
|
90
|
+
"mystery #1": "110010101010",
|
|
91
|
+
"six tone symmetric": "110011001100",
|
|
92
|
+
"whole tone": "101010101010",
|
|
93
|
+
"messiaen's mode #5": "110001110001",
|
|
94
|
+
"minor blues": "100101110010",
|
|
95
|
+
"locrian major": "101011101010",
|
|
96
|
+
"double harmonic lydian": "110010111001",
|
|
97
|
+
"harmonic minor": "101101011001",
|
|
98
|
+
altered: "110110101010",
|
|
99
|
+
"locrian #2": "101101101010",
|
|
100
|
+
"mixolydian b6": "101011011010",
|
|
101
|
+
"lydian dominant": "101010110110",
|
|
102
|
+
lydian: "101010110101",
|
|
103
|
+
"lydian augmented": "101010101101",
|
|
104
|
+
"dorian b2": "110101010110",
|
|
105
|
+
"melodic minor": "101101010101",
|
|
106
|
+
locrian: "110101101010",
|
|
107
|
+
ultralocrian: "110110101100",
|
|
108
|
+
"locrian 6": "110101100110",
|
|
109
|
+
"augmented heptatonic": "100111011001",
|
|
110
|
+
"romanian minor": "101100110110",
|
|
111
|
+
"dorian #4": "101100110110",
|
|
112
|
+
"lydian diminished": "101100110101",
|
|
113
|
+
phrygian: "110101011010",
|
|
114
|
+
"leading whole tone": "101010101011",
|
|
115
|
+
"lydian minor": "101010111010",
|
|
116
|
+
"phrygian dominant": "110011011010",
|
|
117
|
+
balinese: "110101011001",
|
|
118
|
+
"neopolitan major": "110101010101",
|
|
119
|
+
aeolian: "101101011010",
|
|
120
|
+
"harmonic major": "101011011001",
|
|
121
|
+
"double harmonic major": "110011011001",
|
|
122
|
+
dorian: "101101010110",
|
|
123
|
+
"hungarian minor": "101100111001",
|
|
124
|
+
"hungarian major": "100110110110",
|
|
125
|
+
oriental: "110011100110",
|
|
126
|
+
flamenco: "110110110010",
|
|
127
|
+
"todi raga": "110100111001",
|
|
128
|
+
mixolydian: "101011010110",
|
|
129
|
+
persian: "110011101001",
|
|
130
|
+
major: "101011010101",
|
|
131
|
+
enigmatic: "110010101011",
|
|
132
|
+
"major augmented": "101011001101",
|
|
133
|
+
"lydian #9": "100110110101",
|
|
134
|
+
"messiaen's mode #4": "111001111001",
|
|
135
|
+
"purvi raga": "110011111001",
|
|
136
|
+
"spanish heptatonic": "110111011010",
|
|
137
|
+
bebop: "101011010111",
|
|
138
|
+
"bebop minor": "101111010110",
|
|
139
|
+
"bebop major": "101011011101",
|
|
140
|
+
"bebop locrian": "110101111010",
|
|
141
|
+
"minor bebop": "101101011011",
|
|
142
|
+
diminished: "101101101101",
|
|
143
|
+
ichikosucho: "101011110101",
|
|
144
|
+
"minor six diminished": "101101011101",
|
|
145
|
+
"half-whole diminished": "110110110110",
|
|
146
|
+
"kafi raga": "100111010111",
|
|
147
|
+
"messiaen's mode #6": "101011101011",
|
|
148
|
+
"composite blues": "101111110110",
|
|
149
|
+
"messiaen's mode #3": "101110111011",
|
|
150
|
+
"messiaen's mode #7": "111101111101",
|
|
151
|
+
chromatic: "111111111111",
|
|
152
|
+
ionian: "101011010101",
|
|
153
|
+
minor: "101101011010",
|
|
154
|
+
Kanakangi: "111001011100",
|
|
155
|
+
Ratnangi: "111001011010",
|
|
156
|
+
Ganamurti: "111001011001",
|
|
157
|
+
Vanaspati: "111001010110",
|
|
158
|
+
Manavati: "111001010101",
|
|
159
|
+
Tanarupi: "111001010011",
|
|
160
|
+
Senavati: "110101011100",
|
|
161
|
+
Hanumatodi: "110101011010",
|
|
162
|
+
Dhenuka: "110101011001",
|
|
163
|
+
Natakapriya: "110101010110",
|
|
164
|
+
Kokilapriya: "110101010101",
|
|
165
|
+
Rupavati: "110101010011",
|
|
166
|
+
Gayakapriya: "110011011100",
|
|
167
|
+
Vakulabharanam: "110011011010",
|
|
168
|
+
Mayamalavagowla: "110011011001",
|
|
169
|
+
Chakravakam: "110011010110",
|
|
170
|
+
Suryakantam: "110011010101",
|
|
171
|
+
Hatakambari: "110011010011",
|
|
172
|
+
Jhankaradhwani: "101101011100",
|
|
173
|
+
Natabhairavi: "101101011010",
|
|
174
|
+
Keeravani: "101101011001",
|
|
175
|
+
Kharaharapriya: "101101010110",
|
|
176
|
+
Gourimanohari: "101101010101",
|
|
177
|
+
Varunapriya: "101101010011",
|
|
178
|
+
Mararanjani: "101011011100",
|
|
179
|
+
Charukesi: "101011011010",
|
|
180
|
+
Sarasangi: "101011011001",
|
|
181
|
+
Harikambhoji: "101011010110",
|
|
182
|
+
Dheerasankarabaranam: "101011010101",
|
|
183
|
+
Naganandini: "101011010011",
|
|
184
|
+
Yagapriya: "100111011100",
|
|
185
|
+
Ragavardhini: "100111011010",
|
|
186
|
+
Gangeyabhushani: "100111011001",
|
|
187
|
+
Vagadheeswari: "100111010110",
|
|
188
|
+
Shulini: "100111010101",
|
|
189
|
+
Chalanata: "100111010011",
|
|
190
|
+
Salagam: "111000111100",
|
|
191
|
+
Jalarnavam: "111000111010",
|
|
192
|
+
Jhalavarali: "111000111001",
|
|
193
|
+
Navaneetam: "111000110110",
|
|
194
|
+
Pavani: "111000110101",
|
|
195
|
+
Raghupriya: "111000110011",
|
|
196
|
+
Gavambhodi: "110100111100",
|
|
197
|
+
Bhavapriya: "110100111010",
|
|
198
|
+
Shubhapantuvarali: "110100111001",
|
|
199
|
+
Shadvidamargini: "110100110110",
|
|
200
|
+
Suvarnangi: "110100110101",
|
|
201
|
+
Divyamani: "110100110011",
|
|
202
|
+
Dhavalambari: "110010111100",
|
|
203
|
+
Namanarayani: "110010111010",
|
|
204
|
+
Kamavardhini: "110010111001",
|
|
205
|
+
Ramapriya: "110010110110",
|
|
206
|
+
Gamanashrama: "110010110101",
|
|
207
|
+
Vishwambari: "110010110011",
|
|
208
|
+
Shamalangi: "101100111100",
|
|
209
|
+
Shanmukhapriya: "101100111010",
|
|
210
|
+
Simhendramadhyamam: "101100111001",
|
|
211
|
+
Hemavati: "101100110110",
|
|
212
|
+
Dharmavati: "101100110101",
|
|
213
|
+
Neetimati: "101100110011",
|
|
214
|
+
Kantamani: "101010111100",
|
|
215
|
+
Rishabhapriya: "101010111010",
|
|
216
|
+
Latangi: "101010111001",
|
|
217
|
+
Vachaspati: "101010110110",
|
|
218
|
+
Mechakalyani: "101010110101",
|
|
219
|
+
Chitrambari: "101010110011",
|
|
220
|
+
Sucharitra: "100110111100",
|
|
221
|
+
"Jyoti swarupini": "100110111010",
|
|
222
|
+
Dhatuvardani: "100110111001",
|
|
223
|
+
Nasikabhushini: "100110110110",
|
|
224
|
+
Kosalam: "100110110101",
|
|
225
|
+
Rasikapriya: "100110110011"
|
|
226
|
+
};
|
|
227
|
+
var chordMaps_default = {
|
|
228
|
+
"5th": "100000010000",
|
|
229
|
+
"M7#5sus4": "100001001001",
|
|
230
|
+
"7#5sus4": "100001001010",
|
|
231
|
+
sus4: "100001010000",
|
|
232
|
+
M7sus4: "100001010001",
|
|
233
|
+
"7sus4": "100001010010",
|
|
234
|
+
"7no5": "100010000010",
|
|
235
|
+
aug: "100010001000",
|
|
236
|
+
M7b6: "100010001001",
|
|
237
|
+
"maj7#5": "100010001001",
|
|
238
|
+
"7#5": "100010001010",
|
|
239
|
+
"7b13": "100010001010",
|
|
240
|
+
M: "100010010000",
|
|
241
|
+
maj7: "100010010001",
|
|
242
|
+
"7th": "100010010010",
|
|
243
|
+
"6th": "100010010100",
|
|
244
|
+
"7add6": "100010010110",
|
|
245
|
+
"7b6": "100010011010",
|
|
246
|
+
Mb5: "100010100000",
|
|
247
|
+
M7b5: "100010100001",
|
|
248
|
+
"7b5": "100010100010",
|
|
249
|
+
"maj#4": "100010110001",
|
|
250
|
+
"7#11": "100010110010",
|
|
251
|
+
"M6#11": "100010110100",
|
|
252
|
+
"7#11b13": "100010111010",
|
|
253
|
+
"m#5": "100100001000",
|
|
254
|
+
mb6M7: "100100001001",
|
|
255
|
+
"m7#5": "100100001010",
|
|
256
|
+
m: "100100010000",
|
|
257
|
+
"m/ma7": "100100010001",
|
|
258
|
+
m7: "100100010010",
|
|
259
|
+
m6: "100100010100",
|
|
260
|
+
mMaj7b6: "100100011001",
|
|
261
|
+
dim: "100100100000",
|
|
262
|
+
oM7: "100100100001",
|
|
263
|
+
m7b5: "100100100010",
|
|
264
|
+
dim7: "100100100100",
|
|
265
|
+
o7M7: "100100100101",
|
|
266
|
+
"4th": "100101000010",
|
|
267
|
+
madd4: "100101010000",
|
|
268
|
+
m7add11: "100101010010",
|
|
269
|
+
"+add#9": "100110001000",
|
|
270
|
+
"7#5#9": "100110001010",
|
|
271
|
+
"7#9": "100110010010",
|
|
272
|
+
"13#9": "100110010110",
|
|
273
|
+
"7#9b13": "100110011010",
|
|
274
|
+
"maj7#9#11": "100110110001",
|
|
275
|
+
"7#9#11": "100110110010",
|
|
276
|
+
"13#9#11": "100110110110",
|
|
277
|
+
"7#9#11b13": "100110111010",
|
|
278
|
+
sus2: "101000010000",
|
|
279
|
+
"M9#5sus4": "101001001001",
|
|
280
|
+
sus24: "101001010000",
|
|
281
|
+
M9sus4: "101001010001",
|
|
282
|
+
"11th": "101001010010",
|
|
283
|
+
"9sus4": "101001010010",
|
|
284
|
+
"13sus4": "101001010110",
|
|
285
|
+
"9no5": "101010000010",
|
|
286
|
+
"13no5": "101010000110",
|
|
287
|
+
"M#5add9": "101010001000",
|
|
288
|
+
"maj9#5": "101010001001",
|
|
289
|
+
"9#5": "101010001010",
|
|
290
|
+
"9b13": "101010001010",
|
|
291
|
+
Madd9: "101010010000",
|
|
292
|
+
maj9: "101010010001",
|
|
293
|
+
"9th": "101010010010",
|
|
294
|
+
"6/9": "101010010100",
|
|
295
|
+
maj13: "101010010101",
|
|
296
|
+
M7add13: "101010010101",
|
|
297
|
+
"13th": "101010010110",
|
|
298
|
+
M9b5: "101010100001",
|
|
299
|
+
"9b5": "101010100010",
|
|
300
|
+
"13b5": "101010100110",
|
|
301
|
+
"9#5#11": "101010101010",
|
|
302
|
+
"maj9#11": "101010110001",
|
|
303
|
+
"9#11": "101010110010",
|
|
304
|
+
"69#11": "101010110100",
|
|
305
|
+
"M13#11": "101010110101",
|
|
306
|
+
"13#11": "101010110110",
|
|
307
|
+
"9#11b13": "101010111010",
|
|
308
|
+
"m9#5": "101100001010",
|
|
309
|
+
madd9: "101100010000",
|
|
310
|
+
mM9: "101100010001",
|
|
311
|
+
m9: "101100010010",
|
|
312
|
+
m69: "101100010100",
|
|
313
|
+
m13: "101100010110",
|
|
314
|
+
mMaj9b6: "101100011001",
|
|
315
|
+
m9b5: "101100100010",
|
|
316
|
+
m11A: "101101001010",
|
|
317
|
+
m11: "101101010010",
|
|
318
|
+
b9sus: "110001010010",
|
|
319
|
+
"11b9": "110001010010",
|
|
320
|
+
"7sus4b9b13": "110001011010",
|
|
321
|
+
alt7: "110010000010",
|
|
322
|
+
"7#5b9": "110010001010",
|
|
323
|
+
Maddb9: "110010010000",
|
|
324
|
+
M7b9: "110010010001",
|
|
325
|
+
"7b9": "110010010010",
|
|
326
|
+
"13b9": "110010010110",
|
|
327
|
+
"7b9b13": "110010011010",
|
|
328
|
+
"7#5b9#11": "110010101010",
|
|
329
|
+
"7b9#11": "110010110010",
|
|
330
|
+
"13b9#11": "110010110110",
|
|
331
|
+
"7b9b13#11": "110010111010",
|
|
332
|
+
mb6b9: "110100001000",
|
|
333
|
+
"7b9#9": "110110010010"
|
|
334
|
+
};
|
|
335
|
+
var DEFAULT_OCTAVE = 4;
|
|
336
|
+
var sharpToFlatMap = {
|
|
337
|
+
"C#": "Db",
|
|
338
|
+
"D#": "Eb",
|
|
339
|
+
"F#": "Gb",
|
|
340
|
+
"G#": "Ab",
|
|
341
|
+
"A#": "Bb",
|
|
342
|
+
"CB": "B",
|
|
343
|
+
"FB": "E",
|
|
344
|
+
"E#": "F",
|
|
345
|
+
"B#": "C"
|
|
346
|
+
};
|
|
347
|
+
function sharpToFlat(root) {
|
|
348
|
+
return sharpToFlatMap[root.toUpperCase()] || root.charAt(0).toUpperCase() + root.slice(1);
|
|
349
|
+
}
|
|
350
|
+
var CHROMATIC = [
|
|
351
|
+
"C",
|
|
352
|
+
"Db",
|
|
353
|
+
"D",
|
|
354
|
+
"Eb",
|
|
355
|
+
"E",
|
|
356
|
+
"F",
|
|
357
|
+
"Gb",
|
|
358
|
+
"G",
|
|
359
|
+
"Ab",
|
|
360
|
+
"A",
|
|
361
|
+
"Bb",
|
|
362
|
+
"B"
|
|
363
|
+
];
|
|
364
|
+
function getChromatic(root, octave) {
|
|
365
|
+
const index = CHROMATIC.indexOf(root);
|
|
366
|
+
if (index === -1) {
|
|
367
|
+
throw new Error(`${root} is not a valid root note`);
|
|
368
|
+
}
|
|
369
|
+
const o1 = CHROMATIC.map((n) => n + octave);
|
|
370
|
+
const o2 = CHROMATIC.map((n) => n + (octave + 1));
|
|
371
|
+
const c = o1.concat(o2);
|
|
372
|
+
return c.slice(index);
|
|
373
|
+
}
|
|
374
|
+
var scaleMap = scaleMaps_default;
|
|
375
|
+
var chordMap = chordMaps_default;
|
|
376
|
+
function _getNotesForScaleOrChord({ scale: scale2, chord: chord2 }) {
|
|
377
|
+
const input = scale2 || chord2;
|
|
378
|
+
const SCALE_OR_CHORD = scale2 ? "scale" : "chord";
|
|
379
|
+
if (typeof input !== "string") {
|
|
380
|
+
throw new Error(`${input} is not a valid input for ${SCALE_OR_CHORD}`);
|
|
381
|
+
}
|
|
382
|
+
const rootOctaveScale = input.trim();
|
|
383
|
+
const indexOfFirstSpace = rootOctaveScale.indexOf(" ");
|
|
384
|
+
let scaleOrChord;
|
|
385
|
+
let rootOctave;
|
|
386
|
+
if (indexOfFirstSpace === -1) {
|
|
387
|
+
scaleOrChord = rootOctaveScale.slice(1);
|
|
388
|
+
rootOctave = rootOctaveScale[0];
|
|
389
|
+
if (rootOctaveScale[1] === "b" || rootOctaveScale[1] === "#") {
|
|
390
|
+
scaleOrChord = rootOctaveScale.slice(2);
|
|
391
|
+
rootOctave += rootOctaveScale[1];
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
scaleOrChord = rootOctaveScale.slice(indexOfFirstSpace === -1 ? 1 : indexOfFirstSpace + 1);
|
|
395
|
+
rootOctave = rootOctaveScale.slice(0, indexOfFirstSpace);
|
|
396
|
+
}
|
|
397
|
+
const root = sharpToFlat(rootOctave.replace(/\d/g, ""));
|
|
398
|
+
const octaveNumber = rootOctave.replace(/\D/g, "");
|
|
399
|
+
const octave = octaveNumber !== "" ? +rootOctave.replace(/\D/g, "") : DEFAULT_OCTAVE;
|
|
400
|
+
if (isNaN(octave)) {
|
|
401
|
+
throw new Error(`${rootOctave[0]} does not have a valid octave`);
|
|
402
|
+
}
|
|
403
|
+
if (!scaleMap[scaleOrChord] && !chordMap[scaleOrChord]) {
|
|
404
|
+
throw new Error(`${rootOctaveScale} is not a valid ${SCALE_OR_CHORD}`);
|
|
405
|
+
}
|
|
406
|
+
const chroma = getChromatic(root, octave);
|
|
407
|
+
const acc = [];
|
|
408
|
+
let p1 = 0, p2 = 0;
|
|
409
|
+
const map = scale2 ? scaleMap : chordMap;
|
|
410
|
+
while (p1 < map[scaleOrChord].length) {
|
|
411
|
+
if (map[scaleOrChord][p1] === "1") {
|
|
412
|
+
acc.push(chroma[p2]);
|
|
413
|
+
}
|
|
414
|
+
p1++;
|
|
415
|
+
p2++;
|
|
416
|
+
}
|
|
417
|
+
return acc;
|
|
418
|
+
}
|
|
419
|
+
function inlineChord(rootChord_Oct) {
|
|
420
|
+
const B9SUS = "b9sus";
|
|
421
|
+
let root, chord2, octave = DEFAULT_OCTAVE;
|
|
422
|
+
if (rootChord_Oct.includes(B9SUS)) {
|
|
423
|
+
chord2 = B9SUS;
|
|
424
|
+
root = rootChord_Oct.slice(0, rootChord_Oct.indexOf(B9SUS));
|
|
425
|
+
} else {
|
|
426
|
+
root = rootChord_Oct[0];
|
|
427
|
+
chord2 = rootChord_Oct.slice(1);
|
|
428
|
+
if (rootChord_Oct[1] === "b" || rootChord_Oct[1] === "#") {
|
|
429
|
+
root += rootChord_Oct[1];
|
|
430
|
+
chord2 = rootChord_Oct.slice(2);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (rootChord_Oct.includes("_")) {
|
|
434
|
+
octave = +rootChord_Oct.split("_")[1];
|
|
435
|
+
chord2 = chord2.slice(0, chord2.indexOf("_"));
|
|
436
|
+
}
|
|
437
|
+
return _getNotesForScaleOrChord({ chord: root + octave + " " + chord2 });
|
|
438
|
+
}
|
|
439
|
+
function chords() {
|
|
440
|
+
return Object.keys(chordMap);
|
|
441
|
+
}
|
|
442
|
+
function scales() {
|
|
443
|
+
return Object.keys(scaleMap);
|
|
444
|
+
}
|
|
445
|
+
function chord(chord2) {
|
|
446
|
+
return _getNotesForScaleOrChord({ chord: chord2 });
|
|
447
|
+
}
|
|
448
|
+
function scale(scale2) {
|
|
449
|
+
return _getNotesForScaleOrChord({ scale: scale2 });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/utils.ts
|
|
453
|
+
var isNote = (str) => /^[a-gA-G](?:#|b)?\d$/.test(str);
|
|
454
|
+
var expandStr = (str) => {
|
|
455
|
+
str = JSON.stringify(str.split(""));
|
|
456
|
+
str = str.replace(/,"\[",/g, ", [");
|
|
457
|
+
str = str.replace(/"\[",/g, "[");
|
|
458
|
+
str = str.replace(/,"\]"/g, "]");
|
|
459
|
+
return JSON.parse(str);
|
|
460
|
+
};
|
|
461
|
+
var shuffle = (arr, fullShuffle = true) => {
|
|
462
|
+
const lastIndex = arr.length - 1;
|
|
463
|
+
arr.forEach((el, idx) => {
|
|
464
|
+
if (idx >= lastIndex) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const rnd = fullShuffle ? (
|
|
468
|
+
// Pick random number from idx+1 to lastIndex (Modified algorithm, (N-1)! combinations)
|
|
469
|
+
// Math.random -> [0, 1) -> [0, lastIndex-idx ) --floor-> [0, lastIndex-idx-1]
|
|
470
|
+
// rnd = [0, lastIndex-idx-1] + 1 + idx = [1 + idx, lastIndex]
|
|
471
|
+
// (Original algorithm would pick rnd = [idx, lastIndex], thus any element could arrive back into its slot)
|
|
472
|
+
Math.floor(Math.random() * (lastIndex - idx)) + 1 + idx
|
|
473
|
+
) : (
|
|
474
|
+
// Pick random number from idx to lastIndex (Unmodified Richard Durstenfeld, N! combinations)
|
|
475
|
+
Math.floor(Math.random() * (lastIndex + 1 - idx)) + idx
|
|
476
|
+
);
|
|
477
|
+
arr[idx] = arr[rnd];
|
|
478
|
+
arr[rnd] = el;
|
|
479
|
+
});
|
|
480
|
+
return arr;
|
|
481
|
+
};
|
|
482
|
+
var pickOne = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
483
|
+
var dice = () => !!Math.round(Math.random());
|
|
484
|
+
var errorHasMessage = (x) => {
|
|
485
|
+
return typeof x === "object" && x !== null && "message" in x && typeof x.message === "string";
|
|
486
|
+
};
|
|
487
|
+
var convertChordToNotes = (el) => {
|
|
488
|
+
let c1;
|
|
489
|
+
let c2;
|
|
490
|
+
let e1;
|
|
491
|
+
let e2;
|
|
492
|
+
try {
|
|
493
|
+
c1 = inlineChord(el);
|
|
494
|
+
} catch (e) {
|
|
495
|
+
e1 = e;
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
c2 = chord(el.replace(/_/g, " "));
|
|
499
|
+
} catch (e) {
|
|
500
|
+
e2 = e;
|
|
501
|
+
}
|
|
502
|
+
if (!e1 && !e2) {
|
|
503
|
+
if (c1.toString() !== c2.toString()) {
|
|
504
|
+
throw new Error(`Chord ${el} cannot decode, guessing ${c1} or ${c2}`);
|
|
505
|
+
}
|
|
506
|
+
return c1;
|
|
507
|
+
}
|
|
508
|
+
if (!e1) {
|
|
509
|
+
return c1;
|
|
510
|
+
}
|
|
511
|
+
if (!e2) {
|
|
512
|
+
return c2;
|
|
513
|
+
}
|
|
514
|
+
return chord(el);
|
|
515
|
+
};
|
|
516
|
+
var convertChordsToNotes = (el) => {
|
|
517
|
+
if (typeof el === "string" && isNote(el)) {
|
|
518
|
+
return [el];
|
|
519
|
+
}
|
|
520
|
+
if (Array.isArray(el)) {
|
|
521
|
+
el.forEach((n) => {
|
|
522
|
+
if (Array.isArray(n)) {
|
|
523
|
+
n.forEach((n1) => {
|
|
524
|
+
if (typeof n1 !== "string" || !isNote(n1)) {
|
|
525
|
+
throw new TypeError("array of arrays must comprise valid notes");
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
} else if (typeof n !== "string" || !isNote(n)) {
|
|
529
|
+
throw new TypeError("array must comprise valid notes");
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
return el;
|
|
533
|
+
}
|
|
534
|
+
if (!Array.isArray(el)) {
|
|
535
|
+
const c = convertChordToNotes(el);
|
|
536
|
+
if (c?.length) {
|
|
537
|
+
return c;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
throw new Error(`Chord ${el} not found`);
|
|
541
|
+
};
|
|
542
|
+
var randomInt = (num = 1) => Math.round(Math.random() * num);
|
|
543
|
+
|
|
544
|
+
// src/arp.ts
|
|
545
|
+
var DEFAULT_OCTAVE2 = 4;
|
|
546
|
+
var fillArr = (arr, len) => {
|
|
547
|
+
const bumpOctave = (el) => {
|
|
548
|
+
if (!el) {
|
|
549
|
+
throw new Error("Empty element");
|
|
550
|
+
}
|
|
551
|
+
const note = el.replace(/\d/, "");
|
|
552
|
+
const oct = el.replace(/\D/g, "") || DEFAULT_OCTAVE2;
|
|
553
|
+
if (!note) {
|
|
554
|
+
throw new Error("Incorrect note");
|
|
555
|
+
}
|
|
556
|
+
return note + (+oct + 1);
|
|
557
|
+
};
|
|
558
|
+
const arr1 = arr.map(bumpOctave);
|
|
559
|
+
const arr2 = arr1.map(bumpOctave);
|
|
560
|
+
const finalArr = [...arr, ...arr1, ...arr2];
|
|
561
|
+
return finalArr.slice(0, len);
|
|
562
|
+
};
|
|
563
|
+
var arp = (chordsOrParams) => {
|
|
564
|
+
let finalArr = [];
|
|
565
|
+
const params = {
|
|
566
|
+
count: 4,
|
|
567
|
+
order: "0123",
|
|
568
|
+
chords: ""
|
|
569
|
+
};
|
|
570
|
+
if (typeof chordsOrParams === "string") {
|
|
571
|
+
params.chords = chordsOrParams;
|
|
572
|
+
} else {
|
|
573
|
+
if (chordsOrParams.order?.match(/\D/g)) {
|
|
574
|
+
throw new TypeError("Invalid value for order");
|
|
575
|
+
}
|
|
576
|
+
if (chordsOrParams.count > 8 || chordsOrParams.count < 2) {
|
|
577
|
+
throw new TypeError("Invalid value for count");
|
|
578
|
+
}
|
|
579
|
+
if (chordsOrParams.count && !chordsOrParams.order) {
|
|
580
|
+
params.order = Array.from(Array(chordsOrParams.count).keys()).join("");
|
|
581
|
+
}
|
|
582
|
+
Object.assign(params, chordsOrParams);
|
|
583
|
+
}
|
|
584
|
+
if (typeof params.chords === "string") {
|
|
585
|
+
const chordsArr = params.chords.split(" ");
|
|
586
|
+
chordsArr.forEach((c, i) => {
|
|
587
|
+
try {
|
|
588
|
+
const filledArr = fillArr(inlineChord(c), params.count);
|
|
589
|
+
const reorderedArr = params.order.split("").map((idx) => filledArr[Number(idx)]);
|
|
590
|
+
finalArr = [...finalArr, ...reorderedArr];
|
|
591
|
+
} catch (_e) {
|
|
592
|
+
throw new Error(
|
|
593
|
+
`Cannot decode chord ${i + 1} "${c}" in given "${params.chords}"`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
} else if (Array.isArray(params.chords)) {
|
|
598
|
+
params.chords.forEach((c, i) => {
|
|
599
|
+
try {
|
|
600
|
+
const filledArr = fillArr(c, params.count);
|
|
601
|
+
const reorderedArr = params.order.split("").map((idx) => filledArr[Number(idx)]);
|
|
602
|
+
finalArr = [...finalArr, ...reorderedArr];
|
|
603
|
+
} catch (e) {
|
|
604
|
+
throw new Error(
|
|
605
|
+
`${errorHasMessage(e) ? e.message : e} in chord ${i + 1} "${c}"`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
} else {
|
|
610
|
+
throw new TypeError("Invalid value for chords");
|
|
611
|
+
}
|
|
612
|
+
return finalArr;
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/channel/instrument-factory.ts
|
|
616
|
+
function checkToneObjLoaded(toneObject, resolve) {
|
|
617
|
+
const skipRecursion = toneObject instanceof Tone.Sampler;
|
|
618
|
+
if ("loaded" in toneObject) {
|
|
619
|
+
if (toneObject.loaded) {
|
|
620
|
+
resolve();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (skipRecursion) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
let handled = false;
|
|
627
|
+
["buffer", "_buffer", "_buffers"].forEach((key) => {
|
|
628
|
+
if (key in toneObject) {
|
|
629
|
+
checkToneObjLoaded(toneObject[key], resolve);
|
|
630
|
+
handled = true;
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
if (handled) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const hasOnload = toneObject instanceof Tone.ToneAudioBuffer || toneObject instanceof Tone.ToneBufferSource || // Falback for "future" objects
|
|
638
|
+
"loaded" in toneObject && "onload" in toneObject;
|
|
639
|
+
if (!hasOnload) {
|
|
640
|
+
resolve();
|
|
641
|
+
} else {
|
|
642
|
+
const oldOnLoad = toneObject.onload;
|
|
643
|
+
toneObject.onload = () => {
|
|
644
|
+
if (oldOnLoad && typeof oldOnLoad === "function") {
|
|
645
|
+
toneObject.onload = oldOnLoad;
|
|
646
|
+
oldOnLoad();
|
|
647
|
+
}
|
|
648
|
+
resolve();
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function recreateToneObjectInContext(toneObject, context) {
|
|
653
|
+
context = context || Tone.getContext();
|
|
654
|
+
return new Promise((resolve, _reject) => {
|
|
655
|
+
if (toneObject instanceof Tone.PolySynth) {
|
|
656
|
+
const newObj = new Tone[toneObject._dummyVoice.name]({
|
|
657
|
+
...toneObject.get(),
|
|
658
|
+
context
|
|
659
|
+
});
|
|
660
|
+
checkToneObjLoaded(newObj, () => resolve(newObj));
|
|
661
|
+
} else if (toneObject instanceof Tone.Player) {
|
|
662
|
+
const newObj = new Tone.Player({
|
|
663
|
+
url: toneObject._buffer,
|
|
664
|
+
context,
|
|
665
|
+
onload: () => checkToneObjLoaded(newObj, () => resolve(newObj))
|
|
666
|
+
});
|
|
667
|
+
} else if (toneObject instanceof Tone.Sampler) {
|
|
668
|
+
const { attack, curve, release, volume } = toneObject.get();
|
|
669
|
+
const paramsFromSampler = {
|
|
670
|
+
attack,
|
|
671
|
+
curve,
|
|
672
|
+
release,
|
|
673
|
+
volume
|
|
674
|
+
};
|
|
675
|
+
const paramsFromBuffers = {
|
|
676
|
+
baseUrl: toneObject._buffers.baseUrl,
|
|
677
|
+
urls: Object.fromEntries(toneObject._buffers._buffers.entries())
|
|
678
|
+
};
|
|
679
|
+
const newObj = new Tone.Sampler({
|
|
680
|
+
...paramsFromSampler,
|
|
681
|
+
...paramsFromBuffers,
|
|
682
|
+
context,
|
|
683
|
+
onload: () => checkToneObjLoaded(
|
|
684
|
+
newObj,
|
|
685
|
+
() => resolve(newObj)
|
|
686
|
+
)
|
|
687
|
+
});
|
|
688
|
+
} else {
|
|
689
|
+
const newObj = new Tone[toneObject.name]({
|
|
690
|
+
...toneObject.get(),
|
|
691
|
+
context,
|
|
692
|
+
onload: () => checkToneObjLoaded(newObj, () => resolve(newObj))
|
|
693
|
+
});
|
|
694
|
+
checkToneObjLoaded(newObj, () => resolve(newObj));
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
function createInstrument(context, params, channelMeta) {
|
|
699
|
+
let instrument;
|
|
700
|
+
let external;
|
|
701
|
+
context = context || Tone.getContext();
|
|
702
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
703
|
+
if (params.synth) {
|
|
704
|
+
if (params.instrument) {
|
|
705
|
+
throw new Error(
|
|
706
|
+
"Either synth or instrument can be provided, but not both."
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
if (params.synth.synth) {
|
|
710
|
+
const synthName = params.synth.synth;
|
|
711
|
+
const preset = params.synth.preset || {};
|
|
712
|
+
instrument = new Tone[synthName]({
|
|
713
|
+
...preset,
|
|
714
|
+
context,
|
|
715
|
+
// Use onload for cases when synthName calls out Tone.Sample/Player/Sampler.
|
|
716
|
+
// It could be a universal way to load Tone.js instruments.
|
|
717
|
+
onload: () => checkToneObjLoaded(instrument, resolve)
|
|
718
|
+
// This onload is ignored in all synths. Therefore we call checkToneObjLoaded() again below.
|
|
719
|
+
// It is safe to call resolve() multiple times for Promise<void>
|
|
720
|
+
});
|
|
721
|
+
checkToneObjLoaded(instrument, resolve);
|
|
722
|
+
} else {
|
|
723
|
+
instrument = params.synth;
|
|
724
|
+
console.warn(
|
|
725
|
+
'The "synth" parameter with instrument will be deprecated in the future. Please use the "instrument" parameter instead.'
|
|
726
|
+
);
|
|
727
|
+
checkToneObjLoaded(instrument, resolve);
|
|
728
|
+
}
|
|
729
|
+
} else if (typeof params.instrument === "string") {
|
|
730
|
+
instrument = new Tone[params.instrument]({ context });
|
|
731
|
+
checkToneObjLoaded(instrument, resolve);
|
|
732
|
+
} else if (params.instrument) {
|
|
733
|
+
instrument = params.instrument;
|
|
734
|
+
checkToneObjLoaded(instrument, resolve);
|
|
735
|
+
} else if (params.sample || params.buffer) {
|
|
736
|
+
instrument = new Tone.Player({
|
|
737
|
+
url: params.sample || params.buffer,
|
|
738
|
+
context,
|
|
739
|
+
onload: () => checkToneObjLoaded(instrument, resolve)
|
|
740
|
+
});
|
|
741
|
+
} else if (params.samples) {
|
|
742
|
+
instrument = new Tone.Sampler({
|
|
743
|
+
urls: params.samples,
|
|
744
|
+
context,
|
|
745
|
+
onload: () => checkToneObjLoaded(instrument, resolve)
|
|
746
|
+
});
|
|
747
|
+
} else if (params.sampler) {
|
|
748
|
+
instrument = params.sampler;
|
|
749
|
+
checkToneObjLoaded(instrument, resolve);
|
|
750
|
+
} else if (params.player) {
|
|
751
|
+
instrument = params.player;
|
|
752
|
+
checkToneObjLoaded(instrument, resolve);
|
|
753
|
+
} else if (params.external) {
|
|
754
|
+
external = { ...params.external };
|
|
755
|
+
instrument = {
|
|
756
|
+
context,
|
|
757
|
+
volume: { value: 0 }
|
|
758
|
+
};
|
|
759
|
+
if (params.external.init) {
|
|
760
|
+
return params.external.init(context.rawContext).then(() => {
|
|
761
|
+
resolve();
|
|
762
|
+
}).catch((e) => {
|
|
763
|
+
reject(
|
|
764
|
+
new Error(
|
|
765
|
+
`${errorHasMessage(e) ? e.message : e} loading external output module of channel idx ${channelMeta.idx}, ${channelMeta.name ?? "(no name)"}`
|
|
766
|
+
)
|
|
767
|
+
);
|
|
768
|
+
});
|
|
769
|
+
} else {
|
|
770
|
+
resolve();
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
throw new Error(
|
|
774
|
+
"One of required synth|instrument|sample|sampler|samples|buffer|player|external is not provided!"
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
if (!instrument) {
|
|
778
|
+
throw new Error("Failed instantiating instrument from given params.");
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
const initPromise = loadPromise.then(() => {
|
|
782
|
+
if (!external && instrument?.context !== context) {
|
|
783
|
+
return recreateToneObjectInContext(instrument, context).then((newObj) => {
|
|
784
|
+
instrument = newObj;
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}).then(() => {
|
|
788
|
+
if (params.volume) {
|
|
789
|
+
instrument.volume.value = params.volume;
|
|
790
|
+
external?.setVolume?.(params.volume);
|
|
791
|
+
}
|
|
792
|
+
return instrument;
|
|
793
|
+
});
|
|
794
|
+
return { instrument, external, initPromise };
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/channel/sequence-builder.ts
|
|
798
|
+
function buildSequenceCallback(params, host, playerCb, eventCb) {
|
|
799
|
+
if (host.external) {
|
|
800
|
+
const ext = host.external;
|
|
801
|
+
return (time, el) => {
|
|
802
|
+
if (el === "x" || el === "R") {
|
|
803
|
+
const counter = host.clipNoteCount;
|
|
804
|
+
if (host.hasLoaded) {
|
|
805
|
+
const note = getNote(el, params, counter)[0];
|
|
806
|
+
const duration = getDuration(params, counter);
|
|
807
|
+
const durSeconds = Tone.Time(duration).toSeconds();
|
|
808
|
+
playerCb({ note, duration, time, counter });
|
|
809
|
+
try {
|
|
810
|
+
ext.triggerAttackRelease?.(note, durSeconds, time);
|
|
811
|
+
} catch (e) {
|
|
812
|
+
eventCb("error", { e });
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
host.clipNoteCount++;
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (host.instrument instanceof Tone.Player) {
|
|
820
|
+
return (time, el) => {
|
|
821
|
+
if (el === "x" || el === "R") {
|
|
822
|
+
const counter = host.clipNoteCount;
|
|
823
|
+
if (host.hasLoaded) {
|
|
824
|
+
playerCb({ note: "", duration: "", time, counter });
|
|
825
|
+
try {
|
|
826
|
+
host.instrument.start(time);
|
|
827
|
+
} catch (e) {
|
|
828
|
+
eventCb("error", { e });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
host.clipNoteCount++;
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
if (host.instrument instanceof Tone.PolySynth || host.instrument instanceof Tone.Sampler) {
|
|
836
|
+
return (time, el) => {
|
|
837
|
+
if (el === "x" || el === "R") {
|
|
838
|
+
const counter = host.clipNoteCount;
|
|
839
|
+
if (host.hasLoaded) {
|
|
840
|
+
const note = getNote(el, params, counter);
|
|
841
|
+
const duration = getDuration(params, counter);
|
|
842
|
+
playerCb({ note, duration, time, counter });
|
|
843
|
+
try {
|
|
844
|
+
host.instrument.triggerAttackRelease(note, duration, time);
|
|
845
|
+
} catch (e) {
|
|
846
|
+
eventCb("error", { e });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
host.clipNoteCount++;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (host.instrument instanceof Tone.NoiseSynth) {
|
|
854
|
+
return (time, el) => {
|
|
855
|
+
if (el === "x" || el === "R") {
|
|
856
|
+
const counter = host.clipNoteCount;
|
|
857
|
+
if (host.hasLoaded) {
|
|
858
|
+
const duration = getDuration(params, counter);
|
|
859
|
+
playerCb({ note: "", duration, time, counter });
|
|
860
|
+
try {
|
|
861
|
+
host.instrument.triggerAttackRelease(
|
|
862
|
+
duration,
|
|
863
|
+
time
|
|
864
|
+
);
|
|
865
|
+
} catch (e) {
|
|
866
|
+
eventCb("error", { e });
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
host.clipNoteCount++;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
return (time, el) => {
|
|
874
|
+
if (el === "x" || el === "R") {
|
|
875
|
+
const counter = host.clipNoteCount;
|
|
876
|
+
if (host.hasLoaded) {
|
|
877
|
+
const note = getNote(el, params, counter)[0];
|
|
878
|
+
const duration = getDuration(params, counter);
|
|
879
|
+
playerCb({ note, duration, time, counter });
|
|
880
|
+
try {
|
|
881
|
+
host.instrument.triggerAttackRelease(note, duration, time);
|
|
882
|
+
} catch (e) {
|
|
883
|
+
eventCb("error", { e });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
host.clipNoteCount++;
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/clip-utils.ts
|
|
892
|
+
var defaultParams = {
|
|
893
|
+
notes: ["C4"],
|
|
894
|
+
pattern: "x",
|
|
895
|
+
shuffle: false,
|
|
896
|
+
sizzle: false,
|
|
897
|
+
sizzleReps: 1,
|
|
898
|
+
arpegiate: false,
|
|
899
|
+
subdiv: "4n",
|
|
900
|
+
amp: 100,
|
|
901
|
+
accentLow: 70,
|
|
902
|
+
randomNotes: null,
|
|
903
|
+
offlineRendering: false
|
|
904
|
+
};
|
|
905
|
+
var validatePattern = (pattern) => {
|
|
906
|
+
if (/[^x\-_[\]R]/.test(pattern)) {
|
|
907
|
+
throw new TypeError(
|
|
908
|
+
`pattern can only comprise x - _ [ ] R, found ${pattern}`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
var preprocessClipParams = (params, extraDefaults) => {
|
|
913
|
+
params = { ...defaultParams, ...extraDefaults, ...params || {} };
|
|
914
|
+
if (typeof params.notes === "string") {
|
|
915
|
+
params.notes = params.notes.replace(/\s{2,}/g, " ").split(" ");
|
|
916
|
+
}
|
|
917
|
+
params.notes = params.notes ? params.notes.map(convertChordsToNotes) : [];
|
|
918
|
+
validatePattern(params.pattern);
|
|
919
|
+
if (params.shuffle) {
|
|
920
|
+
params.notes = shuffle(params.notes);
|
|
921
|
+
}
|
|
922
|
+
if (params.randomNotes && typeof params.randomNotes === "string") {
|
|
923
|
+
params.randomNotes = params.randomNotes.replace(/\s{2,}/g, " ").split(/\s/);
|
|
924
|
+
}
|
|
925
|
+
if (params.randomNotes) {
|
|
926
|
+
params.randomNotes = params.randomNotes.map(
|
|
927
|
+
convertChordsToNotes
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
return params;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
// src/browser-clip.ts
|
|
934
|
+
var defaultSubdiv = "4n";
|
|
935
|
+
var defaultDur = "8n";
|
|
936
|
+
var getNote = (el, params, counter) => {
|
|
937
|
+
if (el === "R" && params.randomNotes && params.randomNotes.length > 0) {
|
|
938
|
+
return params.randomNotes[randomInt(params.randomNotes.length - 1)];
|
|
939
|
+
}
|
|
940
|
+
if (params.notes) {
|
|
941
|
+
return params.notes[counter % (params.notes.length || 1)];
|
|
942
|
+
}
|
|
943
|
+
return "";
|
|
944
|
+
};
|
|
945
|
+
var getDuration = (params, counter) => {
|
|
946
|
+
return params.durations ? params.durations[counter % params.durations.length] : params.dur || params.subdiv || defaultDur;
|
|
947
|
+
};
|
|
948
|
+
var recursivelyApplyPatternToDurations = (patternArr, length, durations = []) => {
|
|
949
|
+
patternArr.forEach((char) => {
|
|
950
|
+
if (typeof char === "string") {
|
|
951
|
+
if (char === "x" || char === "R") {
|
|
952
|
+
durations.push(length);
|
|
953
|
+
}
|
|
954
|
+
if (char === "_" && durations.length) {
|
|
955
|
+
durations[durations.length - 1] += length;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (Array.isArray(char)) {
|
|
959
|
+
recursivelyApplyPatternToDurations(char, length / char.length, durations);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
return durations;
|
|
963
|
+
};
|
|
964
|
+
var generateSequence = (params, channel, context) => {
|
|
965
|
+
context = context || Tone.getContext();
|
|
966
|
+
if (!params.pattern) {
|
|
967
|
+
throw new Error("No pattern provided!");
|
|
968
|
+
}
|
|
969
|
+
if (!params.durations && !params.dur) {
|
|
970
|
+
params.durations = recursivelyApplyPatternToDurations(
|
|
971
|
+
expandStr(params.pattern),
|
|
972
|
+
Tone.Ticks(params.subdiv || defaultSubdiv).toSeconds()
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
let callback;
|
|
976
|
+
if (channel) {
|
|
977
|
+
callback = channel.getSeqFn(params);
|
|
978
|
+
} else if (params.sample) {
|
|
979
|
+
const player = new Tone.Player({ url: params.sample, context });
|
|
980
|
+
player.toDestination();
|
|
981
|
+
player.sync();
|
|
982
|
+
const host = {
|
|
983
|
+
instrument: player,
|
|
984
|
+
hasLoaded: false,
|
|
985
|
+
clipNoteCount: 0
|
|
986
|
+
};
|
|
987
|
+
checkToneObjLoaded(player, () => {
|
|
988
|
+
host.hasLoaded = true;
|
|
989
|
+
});
|
|
990
|
+
const noop = () => {
|
|
991
|
+
};
|
|
992
|
+
callback = buildSequenceCallback(params, host, noop, noop);
|
|
993
|
+
} else {
|
|
994
|
+
throw new Error(
|
|
995
|
+
"Either a Channel or a sample URL must be provided to create a clip."
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
return new Tone.Sequence({
|
|
999
|
+
callback,
|
|
1000
|
+
events: expandStr(params.pattern),
|
|
1001
|
+
subdivision: params.subdiv || defaultSubdiv,
|
|
1002
|
+
context
|
|
1003
|
+
});
|
|
1004
|
+
};
|
|
1005
|
+
var totalPatternDuration = (pattern, subdivOrLength) => {
|
|
1006
|
+
return typeof subdivOrLength === "number" ? subdivOrLength * expandStr(pattern).length : Tone.Ticks(subdivOrLength).toSeconds() * expandStr(pattern).length;
|
|
1007
|
+
};
|
|
1008
|
+
var leastCommonMultiple = (n1, n2) => {
|
|
1009
|
+
const [smallest, largest] = n1 < n2 ? [n1, n2] : [n2, n1];
|
|
1010
|
+
let i = largest;
|
|
1011
|
+
while (i % smallest !== 0) {
|
|
1012
|
+
i += largest;
|
|
1013
|
+
}
|
|
1014
|
+
return i;
|
|
1015
|
+
};
|
|
1016
|
+
var renderingDuration = (pattern, subdivOrLength, notes, randomNotes) => {
|
|
1017
|
+
const patternRegularNotesCount = pattern.split("").filter((c) => {
|
|
1018
|
+
return c === "x";
|
|
1019
|
+
}).length;
|
|
1020
|
+
const patternRandomNotesCount = pattern.split("").filter((c) => {
|
|
1021
|
+
return c === "R";
|
|
1022
|
+
}).length;
|
|
1023
|
+
const patternNotesCount = randomNotes?.length ? patternRegularNotesCount : patternRegularNotesCount + patternRandomNotesCount;
|
|
1024
|
+
const notesCount = notes.length || 1;
|
|
1025
|
+
return totalPatternDuration(pattern, subdivOrLength) / patternNotesCount * leastCommonMultiple(notesCount, patternNotesCount);
|
|
1026
|
+
};
|
|
1027
|
+
var ongoingRenderingCounter = 0;
|
|
1028
|
+
var originalContext;
|
|
1029
|
+
var offlineRenderClip = (params, duration) => {
|
|
1030
|
+
if (!originalContext) {
|
|
1031
|
+
originalContext = Tone.getContext();
|
|
1032
|
+
}
|
|
1033
|
+
ongoingRenderingCounter++;
|
|
1034
|
+
const player = new Tone.Player({ context: originalContext, loop: true });
|
|
1035
|
+
Tone.Offline(async (context) => {
|
|
1036
|
+
const sequence = generateSequence(params, context);
|
|
1037
|
+
await Tone.loaded();
|
|
1038
|
+
sequence.start();
|
|
1039
|
+
context.transport.start();
|
|
1040
|
+
}, duration).then((buffer) => {
|
|
1041
|
+
player.buffer = buffer;
|
|
1042
|
+
ongoingRenderingCounter--;
|
|
1043
|
+
if (ongoingRenderingCounter === 0) {
|
|
1044
|
+
Tone.setContext(originalContext);
|
|
1045
|
+
params.offlineRenderingCallback?.();
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
player.toDestination();
|
|
1049
|
+
player.sync();
|
|
1050
|
+
return player;
|
|
1051
|
+
};
|
|
1052
|
+
var clip = (params, channel) => {
|
|
1053
|
+
params = preprocessClipParams(params, { align: "1m", alignOffset: "0" });
|
|
1054
|
+
if (params.offlineRendering) {
|
|
1055
|
+
return offlineRenderClip(
|
|
1056
|
+
params,
|
|
1057
|
+
renderingDuration(
|
|
1058
|
+
params.pattern,
|
|
1059
|
+
params.subdiv || defaultSubdiv,
|
|
1060
|
+
params.notes || [],
|
|
1061
|
+
params.randomNotes
|
|
1062
|
+
)
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
return generateSequence(params, channel, originalContext);
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
// src/midi.ts
|
|
1069
|
+
var import_node_fs = __toESM(__require("fs"), 1);
|
|
1070
|
+
|
|
1071
|
+
// node_modules/@scribbletune/midi/dist/index.js
|
|
1072
|
+
var midi_letter_pitches = {
|
|
1073
|
+
a: 21,
|
|
1074
|
+
b: 23,
|
|
1075
|
+
c: 12,
|
|
1076
|
+
d: 14,
|
|
1077
|
+
e: 16,
|
|
1078
|
+
f: 17,
|
|
1079
|
+
g: 19
|
|
1080
|
+
};
|
|
1081
|
+
function midiPitchFromNote(n) {
|
|
1082
|
+
const matches = /([a-g])(#+|b+)?([0-9]+)$/i.exec(n);
|
|
1083
|
+
if (!matches) {
|
|
1084
|
+
throw new Error(`Invalid note name: ${n}`);
|
|
1085
|
+
}
|
|
1086
|
+
const note = matches[1].toLowerCase();
|
|
1087
|
+
const accidental = matches[2] || "";
|
|
1088
|
+
const octave = parseInt(matches[3], 10);
|
|
1089
|
+
return 12 * octave + midi_letter_pitches[note] + (accidental.substring(0, 1) === "#" ? 1 : -1) * accidental.length;
|
|
1090
|
+
}
|
|
1091
|
+
function ensureMidiPitch(p) {
|
|
1092
|
+
if (typeof p === "number" || !/[^0-9]/.test(p)) {
|
|
1093
|
+
return parseInt(String(p), 10);
|
|
1094
|
+
}
|
|
1095
|
+
return midiPitchFromNote(p);
|
|
1096
|
+
}
|
|
1097
|
+
function mpqnFromBpm(bpm) {
|
|
1098
|
+
let mpqn = Math.floor(6e7 / bpm);
|
|
1099
|
+
const ret = [];
|
|
1100
|
+
do {
|
|
1101
|
+
ret.unshift(mpqn & 255);
|
|
1102
|
+
mpqn >>= 8;
|
|
1103
|
+
} while (mpqn);
|
|
1104
|
+
while (ret.length < 3) {
|
|
1105
|
+
ret.push(0);
|
|
1106
|
+
}
|
|
1107
|
+
return ret;
|
|
1108
|
+
}
|
|
1109
|
+
function codes2Str(byteArray) {
|
|
1110
|
+
return String.fromCharCode(...byteArray);
|
|
1111
|
+
}
|
|
1112
|
+
function str2Bytes(str, finalBytes) {
|
|
1113
|
+
let s = str;
|
|
1114
|
+
if (finalBytes) {
|
|
1115
|
+
while (s.length / 2 < finalBytes) {
|
|
1116
|
+
s = "0" + s;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
const bytes = [];
|
|
1120
|
+
for (let i = s.length - 1; i >= 0; i = i - 2) {
|
|
1121
|
+
const chars = i === 0 ? s[i] : s[i - 1] + s[i];
|
|
1122
|
+
bytes.unshift(parseInt(chars, 16));
|
|
1123
|
+
}
|
|
1124
|
+
return bytes;
|
|
1125
|
+
}
|
|
1126
|
+
function translateTickTime(ticks) {
|
|
1127
|
+
let buffer = ticks & 127;
|
|
1128
|
+
let t = ticks >> 7;
|
|
1129
|
+
while (t) {
|
|
1130
|
+
buffer <<= 8;
|
|
1131
|
+
buffer |= t & 127 | 128;
|
|
1132
|
+
t = t >> 7;
|
|
1133
|
+
}
|
|
1134
|
+
const bList = [];
|
|
1135
|
+
while (true) {
|
|
1136
|
+
bList.push(buffer & 255);
|
|
1137
|
+
if (buffer & 128) {
|
|
1138
|
+
buffer >>= 8;
|
|
1139
|
+
} else {
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return bList;
|
|
1144
|
+
}
|
|
1145
|
+
var MidiEventType = {
|
|
1146
|
+
NOTE_OFF: 128,
|
|
1147
|
+
NOTE_ON: 144,
|
|
1148
|
+
AFTER_TOUCH: 160,
|
|
1149
|
+
CONTROLLER: 176,
|
|
1150
|
+
PROGRAM_CHANGE: 192,
|
|
1151
|
+
CHANNEL_AFTERTOUCH: 208,
|
|
1152
|
+
PITCH_BEND: 224
|
|
1153
|
+
};
|
|
1154
|
+
var MetaEventType = {
|
|
1155
|
+
SEQUENCE: 0,
|
|
1156
|
+
TEXT: 1,
|
|
1157
|
+
COPYRIGHT: 2,
|
|
1158
|
+
TRACK_NAME: 3,
|
|
1159
|
+
INSTRUMENT: 4,
|
|
1160
|
+
LYRIC: 5,
|
|
1161
|
+
MARKER: 6,
|
|
1162
|
+
CUE_POINT: 7,
|
|
1163
|
+
CHANNEL_PREFIX: 32,
|
|
1164
|
+
END_OF_TRACK: 47,
|
|
1165
|
+
TEMPO: 81,
|
|
1166
|
+
SMPTE: 84,
|
|
1167
|
+
TIME_SIG: 88,
|
|
1168
|
+
KEY_SIG: 89,
|
|
1169
|
+
SEQ_EVENT: 127
|
|
1170
|
+
};
|
|
1171
|
+
var _a;
|
|
1172
|
+
var MidiEvent = (_a = class {
|
|
1173
|
+
constructor(params) {
|
|
1174
|
+
__publicField(this, "time");
|
|
1175
|
+
__publicField(this, "type");
|
|
1176
|
+
__publicField(this, "channel");
|
|
1177
|
+
__publicField(this, "param1");
|
|
1178
|
+
__publicField(this, "param2");
|
|
1179
|
+
this.setTime(params.time);
|
|
1180
|
+
this.setType(params.type);
|
|
1181
|
+
this.setChannel(params.channel);
|
|
1182
|
+
this.setParam1(params.param1);
|
|
1183
|
+
if (params.param2 !== void 0) {
|
|
1184
|
+
this.setParam2(params.param2);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
setTime(ticks) {
|
|
1188
|
+
this.time = translateTickTime(ticks || 0);
|
|
1189
|
+
}
|
|
1190
|
+
setType(type) {
|
|
1191
|
+
if (type < _a.NOTE_OFF || type > _a.PITCH_BEND) {
|
|
1192
|
+
throw new Error("Trying to set an unknown event: " + type);
|
|
1193
|
+
}
|
|
1194
|
+
this.type = type;
|
|
1195
|
+
}
|
|
1196
|
+
setChannel(channel) {
|
|
1197
|
+
if (channel < 0 || channel > 15) {
|
|
1198
|
+
throw new Error("Channel is out of bounds.");
|
|
1199
|
+
}
|
|
1200
|
+
this.channel = channel;
|
|
1201
|
+
}
|
|
1202
|
+
setParam1(p) {
|
|
1203
|
+
this.param1 = p;
|
|
1204
|
+
}
|
|
1205
|
+
setParam2(p) {
|
|
1206
|
+
this.param2 = p;
|
|
1207
|
+
}
|
|
1208
|
+
toBytes() {
|
|
1209
|
+
const byteArray = [];
|
|
1210
|
+
const typeChannelByte = this.type | this.channel & 15;
|
|
1211
|
+
byteArray.push(...this.time);
|
|
1212
|
+
byteArray.push(typeChannelByte);
|
|
1213
|
+
byteArray.push(this.param1);
|
|
1214
|
+
if (this.param2 !== void 0 && this.param2 !== null) {
|
|
1215
|
+
byteArray.push(this.param2);
|
|
1216
|
+
}
|
|
1217
|
+
return byteArray;
|
|
1218
|
+
}
|
|
1219
|
+
}, __publicField(_a, "NOTE_OFF", MidiEventType.NOTE_OFF), __publicField(_a, "NOTE_ON", MidiEventType.NOTE_ON), __publicField(_a, "AFTER_TOUCH", MidiEventType.AFTER_TOUCH), __publicField(_a, "CONTROLLER", MidiEventType.CONTROLLER), __publicField(_a, "PROGRAM_CHANGE", MidiEventType.PROGRAM_CHANGE), __publicField(_a, "CHANNEL_AFTERTOUCH", MidiEventType.CHANNEL_AFTERTOUCH), __publicField(_a, "PITCH_BEND", MidiEventType.PITCH_BEND), _a);
|
|
1220
|
+
var _a2;
|
|
1221
|
+
var MetaEvent = (_a2 = class {
|
|
1222
|
+
constructor(params) {
|
|
1223
|
+
__publicField(this, "time");
|
|
1224
|
+
__publicField(this, "type");
|
|
1225
|
+
__publicField(this, "data");
|
|
1226
|
+
this.time = translateTickTime(0);
|
|
1227
|
+
this.type = 0;
|
|
1228
|
+
this.setTime(params.time);
|
|
1229
|
+
this.setType(params.type);
|
|
1230
|
+
this.setData(params.data);
|
|
1231
|
+
}
|
|
1232
|
+
setTime(ticks) {
|
|
1233
|
+
this.time = translateTickTime(ticks || 0);
|
|
1234
|
+
}
|
|
1235
|
+
setType(t) {
|
|
1236
|
+
this.type = t;
|
|
1237
|
+
}
|
|
1238
|
+
setData(d) {
|
|
1239
|
+
this.data = d;
|
|
1240
|
+
}
|
|
1241
|
+
toBytes() {
|
|
1242
|
+
if (!this.type) {
|
|
1243
|
+
throw new Error("Type for meta-event not specified.");
|
|
1244
|
+
}
|
|
1245
|
+
const byteArray = [];
|
|
1246
|
+
byteArray.push(...this.time);
|
|
1247
|
+
byteArray.push(255, this.type);
|
|
1248
|
+
if (Array.isArray(this.data)) {
|
|
1249
|
+
byteArray.push(this.data.length);
|
|
1250
|
+
byteArray.push(...this.data);
|
|
1251
|
+
} else if (typeof this.data === "number") {
|
|
1252
|
+
byteArray.push(1, this.data);
|
|
1253
|
+
} else if (this.data !== null && this.data !== void 0) {
|
|
1254
|
+
byteArray.push(this.data.length);
|
|
1255
|
+
const dataBytes = this.data.split("").map((x) => x.charCodeAt(0));
|
|
1256
|
+
byteArray.push(...dataBytes);
|
|
1257
|
+
} else {
|
|
1258
|
+
byteArray.push(0);
|
|
1259
|
+
}
|
|
1260
|
+
return byteArray;
|
|
1261
|
+
}
|
|
1262
|
+
}, __publicField(_a2, "SEQUENCE", MetaEventType.SEQUENCE), __publicField(_a2, "TEXT", MetaEventType.TEXT), __publicField(_a2, "COPYRIGHT", MetaEventType.COPYRIGHT), __publicField(_a2, "TRACK_NAME", MetaEventType.TRACK_NAME), __publicField(_a2, "INSTRUMENT", MetaEventType.INSTRUMENT), __publicField(_a2, "LYRIC", MetaEventType.LYRIC), __publicField(_a2, "MARKER", MetaEventType.MARKER), __publicField(_a2, "CUE_POINT", MetaEventType.CUE_POINT), __publicField(_a2, "CHANNEL_PREFIX", MetaEventType.CHANNEL_PREFIX), __publicField(_a2, "END_OF_TRACK", MetaEventType.END_OF_TRACK), __publicField(_a2, "TEMPO", MetaEventType.TEMPO), __publicField(_a2, "SMPTE", MetaEventType.SMPTE), __publicField(_a2, "TIME_SIG", MetaEventType.TIME_SIG), __publicField(_a2, "KEY_SIG", MetaEventType.KEY_SIG), __publicField(_a2, "SEQ_EVENT", MetaEventType.SEQ_EVENT), _a2);
|
|
1263
|
+
var DEFAULT_VOLUME = 90;
|
|
1264
|
+
var _a3;
|
|
1265
|
+
var Track = (_a3 = class {
|
|
1266
|
+
constructor(config) {
|
|
1267
|
+
__publicField(this, "events");
|
|
1268
|
+
this.events = config?.events ?? [];
|
|
1269
|
+
}
|
|
1270
|
+
addEvent(event) {
|
|
1271
|
+
this.events.push(event);
|
|
1272
|
+
return this;
|
|
1273
|
+
}
|
|
1274
|
+
addNoteOn(channel, pitch, time, velocity) {
|
|
1275
|
+
this.events.push(
|
|
1276
|
+
new MidiEvent({
|
|
1277
|
+
type: MidiEvent.NOTE_ON,
|
|
1278
|
+
channel,
|
|
1279
|
+
param1: ensureMidiPitch(pitch),
|
|
1280
|
+
param2: velocity || DEFAULT_VOLUME,
|
|
1281
|
+
time: time || 0
|
|
1282
|
+
})
|
|
1283
|
+
);
|
|
1284
|
+
return this;
|
|
1285
|
+
}
|
|
1286
|
+
addNoteOff(channel, pitch, time, velocity) {
|
|
1287
|
+
this.events.push(
|
|
1288
|
+
new MidiEvent({
|
|
1289
|
+
type: MidiEvent.NOTE_OFF,
|
|
1290
|
+
channel,
|
|
1291
|
+
param1: ensureMidiPitch(pitch),
|
|
1292
|
+
param2: velocity || DEFAULT_VOLUME,
|
|
1293
|
+
time: time || 0
|
|
1294
|
+
})
|
|
1295
|
+
);
|
|
1296
|
+
return this;
|
|
1297
|
+
}
|
|
1298
|
+
addNote(channel, pitch, dur, time, velocity) {
|
|
1299
|
+
this.addNoteOn(channel, pitch, time, velocity);
|
|
1300
|
+
if (dur) {
|
|
1301
|
+
this.addNoteOff(channel, pitch, dur, velocity);
|
|
1302
|
+
}
|
|
1303
|
+
return this;
|
|
1304
|
+
}
|
|
1305
|
+
addChord(channel, chord2, dur, velocity) {
|
|
1306
|
+
if (!Array.isArray(chord2) || chord2.length === 0) {
|
|
1307
|
+
throw new Error("Chord must be a non-empty array of pitches");
|
|
1308
|
+
}
|
|
1309
|
+
chord2.forEach((note) => {
|
|
1310
|
+
this.addNoteOn(channel, note, 0, velocity);
|
|
1311
|
+
});
|
|
1312
|
+
chord2.forEach((note, index) => {
|
|
1313
|
+
if (index === 0) {
|
|
1314
|
+
this.addNoteOff(channel, note, dur);
|
|
1315
|
+
} else {
|
|
1316
|
+
this.addNoteOff(channel, note);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
return this;
|
|
1320
|
+
}
|
|
1321
|
+
setInstrument(channel, instrument, time) {
|
|
1322
|
+
this.events.push(
|
|
1323
|
+
new MidiEvent({
|
|
1324
|
+
type: MidiEvent.PROGRAM_CHANGE,
|
|
1325
|
+
channel,
|
|
1326
|
+
param1: instrument,
|
|
1327
|
+
time: time || 0
|
|
1328
|
+
})
|
|
1329
|
+
);
|
|
1330
|
+
return this;
|
|
1331
|
+
}
|
|
1332
|
+
setTempo(bpm, time) {
|
|
1333
|
+
this.events.push(
|
|
1334
|
+
new MetaEvent({
|
|
1335
|
+
type: MetaEvent.TEMPO,
|
|
1336
|
+
data: mpqnFromBpm(bpm),
|
|
1337
|
+
time: time || 0
|
|
1338
|
+
})
|
|
1339
|
+
);
|
|
1340
|
+
return this;
|
|
1341
|
+
}
|
|
1342
|
+
setTimeSignature(numerator, denominator, time) {
|
|
1343
|
+
const ddlog2 = Math.log2(denominator);
|
|
1344
|
+
if (ddlog2 !== Math.floor(ddlog2)) {
|
|
1345
|
+
throw new Error(
|
|
1346
|
+
"Time signature denominator must be an exact power of 2!"
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
this.events.push(
|
|
1350
|
+
new MetaEvent({
|
|
1351
|
+
type: MetaEvent.TIME_SIG,
|
|
1352
|
+
data: [numerator & 255, Math.floor(ddlog2) & 255, 24, 8],
|
|
1353
|
+
time: time || 0
|
|
1354
|
+
})
|
|
1355
|
+
);
|
|
1356
|
+
return this;
|
|
1357
|
+
}
|
|
1358
|
+
setKeySignature(accidentals, minor, time) {
|
|
1359
|
+
this.events.push(
|
|
1360
|
+
new MetaEvent({
|
|
1361
|
+
type: MetaEvent.KEY_SIG,
|
|
1362
|
+
data: [accidentals & 255, minor ? 1 : 0],
|
|
1363
|
+
time: time || 0
|
|
1364
|
+
})
|
|
1365
|
+
);
|
|
1366
|
+
return this;
|
|
1367
|
+
}
|
|
1368
|
+
toBytes() {
|
|
1369
|
+
let trackLength = 0;
|
|
1370
|
+
const eventBytes = [];
|
|
1371
|
+
const startBytes = _a3.START_BYTES;
|
|
1372
|
+
const endBytes = _a3.END_BYTES;
|
|
1373
|
+
this.events.forEach((event) => {
|
|
1374
|
+
const bytes = event.toBytes();
|
|
1375
|
+
trackLength += bytes.length;
|
|
1376
|
+
eventBytes.push(...bytes);
|
|
1377
|
+
});
|
|
1378
|
+
trackLength += endBytes.length;
|
|
1379
|
+
const lengthBytes = str2Bytes(trackLength.toString(16), 4);
|
|
1380
|
+
return startBytes.concat(lengthBytes, eventBytes, endBytes);
|
|
1381
|
+
}
|
|
1382
|
+
}, __publicField(_a3, "START_BYTES", [77, 84, 114, 107]), __publicField(_a3, "END_BYTES", [0, 255, 47, 0]), _a3);
|
|
1383
|
+
Track.prototype.noteOn = Track.prototype.addNoteOn;
|
|
1384
|
+
Track.prototype.noteOff = Track.prototype.addNoteOff;
|
|
1385
|
+
Track.prototype.note = Track.prototype.addNote;
|
|
1386
|
+
Track.prototype.chord = Track.prototype.addChord;
|
|
1387
|
+
Track.prototype.instrument = Track.prototype.setInstrument;
|
|
1388
|
+
Track.prototype.tempo = Track.prototype.setTempo;
|
|
1389
|
+
Track.prototype.timeSignature = Track.prototype.setTimeSignature;
|
|
1390
|
+
Track.prototype.keySignature = Track.prototype.setKeySignature;
|
|
1391
|
+
var _a4;
|
|
1392
|
+
var File = (_a4 = class {
|
|
1393
|
+
constructor(config) {
|
|
1394
|
+
__publicField(this, "ticks");
|
|
1395
|
+
__publicField(this, "tracks");
|
|
1396
|
+
const c = config || {};
|
|
1397
|
+
if (c.ticks !== void 0) {
|
|
1398
|
+
if (typeof c.ticks !== "number") {
|
|
1399
|
+
throw new Error("Ticks per beat must be a number!");
|
|
1400
|
+
}
|
|
1401
|
+
if (c.ticks <= 0 || c.ticks >= 1 << 15 || c.ticks % 1 !== 0) {
|
|
1402
|
+
throw new Error("Ticks per beat must be an integer between 1 and 32767!");
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
this.ticks = c.ticks || 128;
|
|
1406
|
+
this.tracks = [];
|
|
1407
|
+
}
|
|
1408
|
+
addTrack(track) {
|
|
1409
|
+
if (track) {
|
|
1410
|
+
this.tracks.push(track);
|
|
1411
|
+
return this;
|
|
1412
|
+
} else {
|
|
1413
|
+
const newTrack = new Track();
|
|
1414
|
+
this.tracks.push(newTrack);
|
|
1415
|
+
return newTrack;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
toBytes() {
|
|
1419
|
+
const trackCount = this.tracks.length.toString(16);
|
|
1420
|
+
let bytes = _a4.HDR_CHUNKID + _a4.HDR_CHUNK_SIZE;
|
|
1421
|
+
if (parseInt(trackCount, 16) > 1) {
|
|
1422
|
+
bytes += _a4.HDR_TYPE1;
|
|
1423
|
+
} else {
|
|
1424
|
+
bytes += _a4.HDR_TYPE0;
|
|
1425
|
+
}
|
|
1426
|
+
bytes += codes2Str(str2Bytes(trackCount, 2));
|
|
1427
|
+
bytes += String.fromCharCode(this.ticks / 256, this.ticks % 256);
|
|
1428
|
+
this.tracks.forEach((track) => {
|
|
1429
|
+
bytes += codes2Str(track.toBytes());
|
|
1430
|
+
});
|
|
1431
|
+
return bytes;
|
|
1432
|
+
}
|
|
1433
|
+
toUint8Array() {
|
|
1434
|
+
const str = this.toBytes();
|
|
1435
|
+
const arr = new Uint8Array(str.length);
|
|
1436
|
+
for (let i = 0; i < str.length; i++) {
|
|
1437
|
+
arr[i] = str.charCodeAt(i);
|
|
1438
|
+
}
|
|
1439
|
+
return arr;
|
|
1440
|
+
}
|
|
1441
|
+
toBlob(genericType) {
|
|
1442
|
+
return new Blob([this.toUint8Array()], {
|
|
1443
|
+
type: genericType ? "application/octet-stream" : "audio/x-midi"
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
}, __publicField(_a4, "HDR_CHUNKID", "MThd"), __publicField(_a4, "HDR_CHUNK_SIZE", "\0\0\0"), __publicField(_a4, "HDR_TYPE0", "\0\0"), __publicField(_a4, "HDR_TYPE1", "\0"), _a4);
|
|
1447
|
+
|
|
1448
|
+
// src/midi.ts
|
|
1449
|
+
var midi = (notes, fileName = "music.mid", bpm) => {
|
|
1450
|
+
const file = createFileFromNotes(notes, bpm);
|
|
1451
|
+
const bytes = file.toBytes();
|
|
1452
|
+
if (fileName === null) {
|
|
1453
|
+
return bytes;
|
|
1454
|
+
}
|
|
1455
|
+
if (!fileName.endsWith(".mid")) {
|
|
1456
|
+
fileName = `${fileName}.mid`;
|
|
1457
|
+
}
|
|
1458
|
+
if (typeof window !== "undefined" && window.URL && typeof window.URL.createObjectURL === "function") {
|
|
1459
|
+
return createDownloadLink(bytes, fileName);
|
|
1460
|
+
}
|
|
1461
|
+
import_node_fs.default.writeFileSync(fileName, bytes, "binary");
|
|
1462
|
+
console.log(`MIDI file generated: ${fileName}.`);
|
|
1463
|
+
};
|
|
1464
|
+
var createDownloadLink = (b, fileName) => {
|
|
1465
|
+
const bytes = new Uint8Array(b.length);
|
|
1466
|
+
for (let i = 0; i < b.length; i++) {
|
|
1467
|
+
const ascii = b.charCodeAt(i);
|
|
1468
|
+
bytes[i] = ascii;
|
|
1469
|
+
}
|
|
1470
|
+
const blob = new Blob([bytes], { type: "audio/midi" });
|
|
1471
|
+
const link = document.createElement("a");
|
|
1472
|
+
link.href = typeof window !== "undefined" && typeof window.URL !== "undefined" && typeof window.URL.createObjectURL !== "undefined" && window.URL.createObjectURL(blob) || "";
|
|
1473
|
+
link.download = fileName;
|
|
1474
|
+
link.innerText = "Download MIDI file";
|
|
1475
|
+
return link;
|
|
1476
|
+
};
|
|
1477
|
+
var createFileFromNotes = (notes, bpm) => {
|
|
1478
|
+
const file = new File();
|
|
1479
|
+
const track = new Track();
|
|
1480
|
+
if (typeof bpm === "number") {
|
|
1481
|
+
track.setTempo(bpm);
|
|
1482
|
+
}
|
|
1483
|
+
file.addTrack(track);
|
|
1484
|
+
for (const noteObj of notes) {
|
|
1485
|
+
const level = noteObj.level || 127;
|
|
1486
|
+
if (noteObj.note) {
|
|
1487
|
+
if (typeof noteObj.note === "string") {
|
|
1488
|
+
track.noteOn(0, noteObj.note, noteObj.length, level);
|
|
1489
|
+
track.noteOff(0, noteObj.note, noteObj.length, level);
|
|
1490
|
+
} else {
|
|
1491
|
+
track.addChord(0, noteObj.note, noteObj.length, level);
|
|
1492
|
+
}
|
|
1493
|
+
} else {
|
|
1494
|
+
track.noteOff(0, "", noteObj.length);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return file;
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
// src/progression.ts
|
|
1501
|
+
var getChordDegrees = (mode) => {
|
|
1502
|
+
const theRomans = {
|
|
1503
|
+
ionian: ["I", "ii", "iii", "IV", "V", "vi", "vii\xB0"],
|
|
1504
|
+
dorian: ["i", "ii", "III", "IV", "v", "vi\xB0", "VII"],
|
|
1505
|
+
phrygian: ["i", "II", "III", "iv", "v\xB0", "VI", "vii"],
|
|
1506
|
+
lydian: ["I", "II", "iii", "iv\xB0", "V", "vi", "vii"],
|
|
1507
|
+
mixolydian: ["I", "ii", "iii\xB0", "IV", "v", "vi", "VII"],
|
|
1508
|
+
aeolian: ["i", "ii\xB0", "III", "iv", "v", "VI", "VII"],
|
|
1509
|
+
locrian: ["i\xB0", "II", "iii", "iv", "V", "VI", "vii"],
|
|
1510
|
+
"melodic minor": ["i", "ii", "III+", "IV", "V", "vi\xB0", "vii\xB0"],
|
|
1511
|
+
"harmonic minor": ["i", "ii\xB0", "III+", "iv", "V", "VI", "vii\xB0"]
|
|
1512
|
+
};
|
|
1513
|
+
theRomans.major = theRomans.ionian;
|
|
1514
|
+
theRomans.minor = theRomans.aeolian;
|
|
1515
|
+
return theRomans[mode] || [];
|
|
1516
|
+
};
|
|
1517
|
+
var idxByDegree = {
|
|
1518
|
+
i: 0,
|
|
1519
|
+
ii: 1,
|
|
1520
|
+
iii: 2,
|
|
1521
|
+
iv: 3,
|
|
1522
|
+
v: 4,
|
|
1523
|
+
vi: 5,
|
|
1524
|
+
vii: 6
|
|
1525
|
+
};
|
|
1526
|
+
var getChordName = (roman) => {
|
|
1527
|
+
const str = roman.replace(/\W/g, "");
|
|
1528
|
+
let prefix = "M";
|
|
1529
|
+
if (str.toLowerCase() === str) {
|
|
1530
|
+
prefix = "m";
|
|
1531
|
+
}
|
|
1532
|
+
if (roman.indexOf("\xB0") > -1) {
|
|
1533
|
+
return `${prefix}7b5`;
|
|
1534
|
+
}
|
|
1535
|
+
if (roman.indexOf("+") > -1) {
|
|
1536
|
+
return `${prefix}#5`;
|
|
1537
|
+
}
|
|
1538
|
+
if (roman.indexOf("7") > -1) {
|
|
1539
|
+
return prefix === "M" ? "maj7" : "m7";
|
|
1540
|
+
}
|
|
1541
|
+
return prefix;
|
|
1542
|
+
};
|
|
1543
|
+
var getChordsByProgression = (noteOctaveScale, chordDegress) => {
|
|
1544
|
+
const noteOctaveScaleArr = noteOctaveScale.split(" ");
|
|
1545
|
+
if (!noteOctaveScaleArr[0].match(/\d/)) {
|
|
1546
|
+
noteOctaveScaleArr[0] += "4";
|
|
1547
|
+
noteOctaveScale = noteOctaveScaleArr.join(" ");
|
|
1548
|
+
}
|
|
1549
|
+
const mode = scale(noteOctaveScale);
|
|
1550
|
+
const chordDegreesArr = chordDegress.replace(/\s*,+\s*/g, " ").split(" ");
|
|
1551
|
+
const chordFamily = chordDegreesArr.map((roman) => {
|
|
1552
|
+
const chordName = getChordName(roman);
|
|
1553
|
+
const scaleId = idxByDegree[roman.replace(/\W|\d/g, "").toLowerCase()];
|
|
1554
|
+
const note = mode[scaleId];
|
|
1555
|
+
const oct = note.replace(/\D+/, "");
|
|
1556
|
+
return `${note.replace(/\d/, "") + chordName}_${oct}`;
|
|
1557
|
+
});
|
|
1558
|
+
return chordFamily.toString().replace(/,/g, " ");
|
|
1559
|
+
};
|
|
1560
|
+
var getProgFactory = ({ T, P, D }) => {
|
|
1561
|
+
return (count = 4) => {
|
|
1562
|
+
const chords2 = [];
|
|
1563
|
+
chords2.push(pickOne(T));
|
|
1564
|
+
let i = 1;
|
|
1565
|
+
if (i < count - 1) {
|
|
1566
|
+
chords2.push(pickOne(P));
|
|
1567
|
+
i++;
|
|
1568
|
+
}
|
|
1569
|
+
if (i < count - 1 && dice()) {
|
|
1570
|
+
chords2.push(pickOne(P));
|
|
1571
|
+
i++;
|
|
1572
|
+
}
|
|
1573
|
+
if (i < count - 1) {
|
|
1574
|
+
chords2.push(pickOne(D));
|
|
1575
|
+
i++;
|
|
1576
|
+
}
|
|
1577
|
+
if (i < count - 1) {
|
|
1578
|
+
chords2.push(pickOne(P));
|
|
1579
|
+
i++;
|
|
1580
|
+
}
|
|
1581
|
+
if (i < count - 1) {
|
|
1582
|
+
chords2.push(pickOne(D));
|
|
1583
|
+
i++;
|
|
1584
|
+
}
|
|
1585
|
+
if (i < count - 1 && dice()) {
|
|
1586
|
+
chords2.push(pickOne(P));
|
|
1587
|
+
i++;
|
|
1588
|
+
}
|
|
1589
|
+
while (i < count) {
|
|
1590
|
+
chords2.push(pickOne(D));
|
|
1591
|
+
i++;
|
|
1592
|
+
}
|
|
1593
|
+
return chords2;
|
|
1594
|
+
};
|
|
1595
|
+
};
|
|
1596
|
+
var M = getProgFactory({ T: ["I", "vi"], P: ["ii", "IV"], D: ["V"] });
|
|
1597
|
+
var m = getProgFactory({ T: ["i", "VI"], P: ["ii", "iv"], D: ["V"] });
|
|
1598
|
+
var progression = (scaleType, count = 4) => {
|
|
1599
|
+
if (scaleType === "major" || scaleType === "M") {
|
|
1600
|
+
return M(count);
|
|
1601
|
+
}
|
|
1602
|
+
if (scaleType === "minor" || scaleType === "m") {
|
|
1603
|
+
return m(count);
|
|
1604
|
+
}
|
|
1605
|
+
return [];
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
// src/channel/effects-chain.ts
|
|
1609
|
+
function initEffects(instrument, context, params) {
|
|
1610
|
+
context = context || Tone.getContext();
|
|
1611
|
+
const createEffect = (effect) => {
|
|
1612
|
+
return new Promise((resolve, _reject) => {
|
|
1613
|
+
if (typeof effect === "string") {
|
|
1614
|
+
resolve(
|
|
1615
|
+
new Tone[effect]({
|
|
1616
|
+
context
|
|
1617
|
+
})
|
|
1618
|
+
);
|
|
1619
|
+
} else if (effect.context !== context) {
|
|
1620
|
+
return recreateToneObjectInContext(
|
|
1621
|
+
effect,
|
|
1622
|
+
context
|
|
1623
|
+
);
|
|
1624
|
+
} else {
|
|
1625
|
+
resolve(effect);
|
|
1626
|
+
}
|
|
1627
|
+
}).then((effectOut) => {
|
|
1628
|
+
return effectOut.toDestination();
|
|
1629
|
+
});
|
|
1630
|
+
};
|
|
1631
|
+
const startEffect = (eff) => {
|
|
1632
|
+
return typeof eff.start === "function" ? eff.start() : eff;
|
|
1633
|
+
};
|
|
1634
|
+
const toArray = (someVal) => {
|
|
1635
|
+
if (!someVal) {
|
|
1636
|
+
return [];
|
|
1637
|
+
}
|
|
1638
|
+
if (Array.isArray(someVal)) {
|
|
1639
|
+
return someVal;
|
|
1640
|
+
}
|
|
1641
|
+
return [someVal];
|
|
1642
|
+
};
|
|
1643
|
+
const effectsIn = toArray(params.effects);
|
|
1644
|
+
if (params.external) {
|
|
1645
|
+
if (effectsIn.length !== 0) {
|
|
1646
|
+
throw new Error("Effects cannot be used with external output");
|
|
1647
|
+
}
|
|
1648
|
+
return Promise.resolve();
|
|
1649
|
+
}
|
|
1650
|
+
return Promise.all(effectsIn.map(createEffect)).then((results) => results.map(startEffect)).then((effects) => {
|
|
1651
|
+
instrument.chain(...effects).toDestination();
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// src/channel.ts
|
|
1656
|
+
var getNextPos = (clip2) => {
|
|
1657
|
+
const transportPosTicks = Tone.Transport.ticks;
|
|
1658
|
+
if (transportPosTicks < Tone.Ticks("4n").toTicks()) {
|
|
1659
|
+
return 0;
|
|
1660
|
+
}
|
|
1661
|
+
const align = clip2?.align || "1m";
|
|
1662
|
+
const alignOffset = clip2?.alignOffset || "0";
|
|
1663
|
+
const alignTicks = Tone.Ticks(align).toTicks();
|
|
1664
|
+
const alignOffsetTicks = Tone.Ticks(alignOffset).toTicks();
|
|
1665
|
+
const nextPosTicks = Tone.Ticks(
|
|
1666
|
+
Math.floor(transportPosTicks / alignTicks + 1) * alignTicks + alignOffsetTicks
|
|
1667
|
+
);
|
|
1668
|
+
return nextPosTicks;
|
|
1669
|
+
};
|
|
1670
|
+
var Channel = class {
|
|
1671
|
+
constructor(params) {
|
|
1672
|
+
this.idx = params.idx || 0;
|
|
1673
|
+
this.name = params.name || `ch ${params.idx}`;
|
|
1674
|
+
this.activePatternIdx = -1;
|
|
1675
|
+
this.channelClips = [];
|
|
1676
|
+
this.clipNoteCount = 0;
|
|
1677
|
+
const { clips, samples, sample, synth, ...params1 } = params;
|
|
1678
|
+
const { external, sampler, buffer, ...params2 } = params1;
|
|
1679
|
+
const { player, instrument, volume, ...params3 } = params2;
|
|
1680
|
+
const { eventCb, playerCb, effects, ...params4 } = params3;
|
|
1681
|
+
const { context = Tone.getContext(), ...originalParamsFiltered } = params4;
|
|
1682
|
+
this.eventCbFn = eventCb;
|
|
1683
|
+
this.playerCbFn = playerCb;
|
|
1684
|
+
this.hasLoaded = false;
|
|
1685
|
+
this.hasFailed = false;
|
|
1686
|
+
const result = createInstrument(context, params, {
|
|
1687
|
+
idx: this.idx,
|
|
1688
|
+
name: this.name
|
|
1689
|
+
});
|
|
1690
|
+
this.instrument = result.instrument;
|
|
1691
|
+
this.external = result.external;
|
|
1692
|
+
this.initializerTask = result.initPromise.then((finalInstrument) => {
|
|
1693
|
+
this.instrument = finalInstrument;
|
|
1694
|
+
return initEffects(this.instrument, context, params);
|
|
1695
|
+
});
|
|
1696
|
+
let clipsFailed = false;
|
|
1697
|
+
try {
|
|
1698
|
+
(params.clips ?? []).forEach((c, i) => {
|
|
1699
|
+
try {
|
|
1700
|
+
this.addClip({
|
|
1701
|
+
...c,
|
|
1702
|
+
...originalParamsFiltered
|
|
1703
|
+
});
|
|
1704
|
+
} catch (e) {
|
|
1705
|
+
throw new Error(
|
|
1706
|
+
`${errorHasMessage(e) ? e.message : e} in clip ${i + 1}`
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}, this);
|
|
1710
|
+
} catch (e) {
|
|
1711
|
+
clipsFailed = e;
|
|
1712
|
+
}
|
|
1713
|
+
this.initializerTask.then(() => {
|
|
1714
|
+
if (clipsFailed) {
|
|
1715
|
+
throw clipsFailed;
|
|
1716
|
+
}
|
|
1717
|
+
this.hasLoaded = true;
|
|
1718
|
+
this.eventCb("loaded", {});
|
|
1719
|
+
}).catch((e) => {
|
|
1720
|
+
this.hasFailed = e;
|
|
1721
|
+
this.eventCb("error", { e });
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
/** Set the global transport tempo in BPM. */
|
|
1725
|
+
static setTransportTempo(valueBpm) {
|
|
1726
|
+
Tone.Transport.bpm.value = valueBpm;
|
|
1727
|
+
}
|
|
1728
|
+
/** Resume the audio context and start the global transport. */
|
|
1729
|
+
static startTransport() {
|
|
1730
|
+
Tone.start();
|
|
1731
|
+
Tone.Transport.start();
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Stop the global transport.
|
|
1735
|
+
* @param deleteEvents - If true (default), cancels all scheduled transport events.
|
|
1736
|
+
*/
|
|
1737
|
+
static stopTransport(deleteEvents = true) {
|
|
1738
|
+
Tone.Transport.stop();
|
|
1739
|
+
if (deleteEvents) {
|
|
1740
|
+
Tone.Transport.cancel();
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
/** Set the volume (in dB) of this channel's instrument and external output. */
|
|
1744
|
+
setVolume(volume) {
|
|
1745
|
+
if (this.instrument) {
|
|
1746
|
+
this.instrument.volume.value = volume;
|
|
1747
|
+
}
|
|
1748
|
+
if (this.external) {
|
|
1749
|
+
this.external.setVolume?.(volume);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Start the clip at the given index, stopping any other active clip first.
|
|
1754
|
+
* @param idx - Clip index in this channel
|
|
1755
|
+
* @param position - Transport time to start at; defaults to the next aligned position
|
|
1756
|
+
*/
|
|
1757
|
+
startClip(idx, position) {
|
|
1758
|
+
const clip2 = this.channelClips[idx];
|
|
1759
|
+
position = position || (position === 0 ? 0 : getNextPos(clip2));
|
|
1760
|
+
if (this.activePatternIdx > -1 && this.activePatternIdx !== idx) {
|
|
1761
|
+
this.stopClip(this.activePatternIdx, position);
|
|
1762
|
+
}
|
|
1763
|
+
if (clip2 && clip2.state !== "started") {
|
|
1764
|
+
this.counterResetTask = Tone.Transport.scheduleOnce(
|
|
1765
|
+
() => {
|
|
1766
|
+
this.clipNoteCount = 0;
|
|
1767
|
+
},
|
|
1768
|
+
position
|
|
1769
|
+
);
|
|
1770
|
+
this.activePatternIdx = idx;
|
|
1771
|
+
clip2?.start(position);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Stop the clip at the given index.
|
|
1776
|
+
* @param idx - Clip index in this channel
|
|
1777
|
+
* @param position - Transport time to stop at; defaults to the next aligned position
|
|
1778
|
+
*/
|
|
1779
|
+
stopClip(idx, position) {
|
|
1780
|
+
const clip2 = this.channelClips[idx];
|
|
1781
|
+
position = position || (position === 0 ? 0 : getNextPos(clip2));
|
|
1782
|
+
clip2?.stop(position);
|
|
1783
|
+
if (idx === this.activePatternIdx) {
|
|
1784
|
+
this.activePatternIdx = -1;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Add a clip to this channel. If the clip has a pattern, a Tone.Sequence is
|
|
1789
|
+
* created; otherwise an empty (null) slot is reserved.
|
|
1790
|
+
* @param clipParams - Clip configuration
|
|
1791
|
+
* @param idx - Slot index; defaults to the next available position
|
|
1792
|
+
*/
|
|
1793
|
+
addClip(clipParams, idx) {
|
|
1794
|
+
idx = idx || this.channelClips.length;
|
|
1795
|
+
if (clipParams.pattern) {
|
|
1796
|
+
this.channelClips[idx] = clip(
|
|
1797
|
+
{
|
|
1798
|
+
...clipParams
|
|
1799
|
+
},
|
|
1800
|
+
this
|
|
1801
|
+
);
|
|
1802
|
+
const seq = this.channelClips[idx];
|
|
1803
|
+
if (seq && clipParams.align) seq.align = clipParams.align;
|
|
1804
|
+
if (seq && clipParams.alignOffset)
|
|
1805
|
+
seq.alignOffset = clipParams.alignOffset;
|
|
1806
|
+
} else {
|
|
1807
|
+
this.channelClips[idx] = null;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* @param {Object} ClipParams clip parameters
|
|
1812
|
+
* @return {Function} function that can be used as the callback in Tone.Sequence https://tonejs.github.io/docs/Sequence
|
|
1813
|
+
*/
|
|
1814
|
+
getSeqFn(params) {
|
|
1815
|
+
return buildSequenceCallback(
|
|
1816
|
+
params,
|
|
1817
|
+
this,
|
|
1818
|
+
(p) => this.playerCb(p),
|
|
1819
|
+
(e, p) => this.eventCb(e, p)
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
/** Invoke the user-provided event callback, if set. */
|
|
1823
|
+
eventCb(event, params) {
|
|
1824
|
+
if (typeof this.eventCbFn === "function") {
|
|
1825
|
+
params.channel = this;
|
|
1826
|
+
this.eventCbFn(event, params);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
/** Invoke the user-provided player observer callback, if set. */
|
|
1830
|
+
playerCb(params) {
|
|
1831
|
+
if (typeof this.playerCbFn === "function") {
|
|
1832
|
+
params.channel = this;
|
|
1833
|
+
this.playerCbFn(params);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
/** All clips (sequences) belonging to this channel. */
|
|
1837
|
+
get clips() {
|
|
1838
|
+
return this.channelClips;
|
|
1839
|
+
}
|
|
1840
|
+
/** Index of the currently playing clip, or -1 if none. */
|
|
1841
|
+
get activeClipIdx() {
|
|
1842
|
+
return this.activePatternIdx;
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
// src/session.ts
|
|
1847
|
+
var Session = class {
|
|
1848
|
+
/** Create a session, optionally pre-populated with channels. */
|
|
1849
|
+
constructor(arr) {
|
|
1850
|
+
arr = arr || [];
|
|
1851
|
+
this.sessionChannels = arr.map((ch, i) => {
|
|
1852
|
+
ch.idx = ch.idx || i;
|
|
1853
|
+
ch.idx = this.uniqueIdx(this.sessionChannels, ch.idx);
|
|
1854
|
+
return new Channel(ch);
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
/** Return a unique channel index, generating a new one if `idx` is taken or missing. */
|
|
1858
|
+
uniqueIdx(channels, idx) {
|
|
1859
|
+
if (!channels) {
|
|
1860
|
+
return idx || 0;
|
|
1861
|
+
}
|
|
1862
|
+
const idxs = channels.reduce((acc, c) => {
|
|
1863
|
+
return !acc.find((i) => i === c.idx) && acc.concat(c.idx) || acc;
|
|
1864
|
+
}, []);
|
|
1865
|
+
if (!idx || idxs.find((i) => i === idx)) {
|
|
1866
|
+
let newIdx = channels.length;
|
|
1867
|
+
while (idxs.find((i) => i === newIdx)) {
|
|
1868
|
+
newIdx = newIdx + 1;
|
|
1869
|
+
}
|
|
1870
|
+
return newIdx;
|
|
1871
|
+
}
|
|
1872
|
+
return idx;
|
|
1873
|
+
}
|
|
1874
|
+
/** Create a new channel with a unique index and add it to the session. */
|
|
1875
|
+
createChannel(ch) {
|
|
1876
|
+
ch.idx = this.uniqueIdx(this.sessionChannels, ch.idx);
|
|
1877
|
+
const newChannel = new Channel(ch);
|
|
1878
|
+
this.sessionChannels.push(newChannel);
|
|
1879
|
+
return newChannel;
|
|
1880
|
+
}
|
|
1881
|
+
/** All channels in this session. */
|
|
1882
|
+
get channels() {
|
|
1883
|
+
return this.sessionChannels;
|
|
1884
|
+
}
|
|
1885
|
+
/** Set the global transport tempo in BPM. */
|
|
1886
|
+
setTransportTempo(valueBpm) {
|
|
1887
|
+
Channel.setTransportTempo(valueBpm);
|
|
1888
|
+
}
|
|
1889
|
+
/** Resume the audio context and start the global transport. */
|
|
1890
|
+
startTransport() {
|
|
1891
|
+
Channel.startTransport();
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Stop the global transport.
|
|
1895
|
+
* @param deleteEvents - If true (default), cancels all scheduled transport events.
|
|
1896
|
+
*/
|
|
1897
|
+
stopTransport(deleteEvents = true) {
|
|
1898
|
+
Channel.stopTransport(deleteEvents);
|
|
1899
|
+
}
|
|
1900
|
+
/** Start the clip at the given index across all channels simultaneously. */
|
|
1901
|
+
startRow(idx) {
|
|
1902
|
+
this.sessionChannels.forEach((ch) => {
|
|
1903
|
+
ch.startClip(idx);
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Schedule clip playback across channels using a song-structure pattern.
|
|
1908
|
+
* Each channel pattern is a string where each character is a clip index
|
|
1909
|
+
* (or `-` for silence, `_` to sustain the previous clip).
|
|
1910
|
+
*/
|
|
1911
|
+
play(params) {
|
|
1912
|
+
const channelPatterns = params.channelPatterns;
|
|
1913
|
+
const clipDuration = params.clipDuration || "4:0:0";
|
|
1914
|
+
const clipDurationInSeconds = Tone.Time(clipDuration).toSeconds();
|
|
1915
|
+
const stopClips = (clips, time) => {
|
|
1916
|
+
clips.forEach((c) => {
|
|
1917
|
+
c.stop(time);
|
|
1918
|
+
});
|
|
1919
|
+
};
|
|
1920
|
+
const startClips = (channelIdx, clipIdx, time) => {
|
|
1921
|
+
if (clipIdx === "-") return [];
|
|
1922
|
+
const clips = this.channels.filter((c) => c.idx === channelIdx).map((c) => c.clips[Number(clipIdx)]).filter((c) => c != null);
|
|
1923
|
+
for (const c of clips) c.start(time);
|
|
1924
|
+
return clips;
|
|
1925
|
+
};
|
|
1926
|
+
channelPatterns.forEach(({ channelIdx, pattern }) => {
|
|
1927
|
+
let clips = [];
|
|
1928
|
+
let time = 0;
|
|
1929
|
+
let prevClipIdx = "-";
|
|
1930
|
+
pattern.split("").forEach((clipIdx) => {
|
|
1931
|
+
if (clipIdx !== prevClipIdx && clipIdx !== "_") {
|
|
1932
|
+
stopClips(clips, time);
|
|
1933
|
+
clips = startClips(channelIdx, clipIdx, time);
|
|
1934
|
+
}
|
|
1935
|
+
prevClipIdx = clipIdx;
|
|
1936
|
+
time += clipDurationInSeconds;
|
|
1937
|
+
});
|
|
1938
|
+
stopClips(clips, time);
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
return __toCommonJS(browser_index_exports);
|
|
1943
|
+
})();
|
|
1944
|
+
//# sourceMappingURL=scribbletune.global.js.map
|