@onjmin/dtm 1.0.7 → 2.0.2
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 +115 -0
- package/dist/index.d.mts +268 -101
- package/dist/index.d.ts +268 -101
- package/dist/index.js +1384 -719
- package/dist/index.mjs +1355 -701
- package/dist/voice-worker.js +3 -3
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -148,7 +148,7 @@ async function buildNameToKeyMapping() {
|
|
|
148
148
|
}
|
|
149
149
|
var GM_INSTRUMENT_NAMES = FONT_NAME_SURIKOV.trim().split("\n").map((line) => line.slice(line.indexOf(" ") + 1));
|
|
150
150
|
|
|
151
|
-
// node_modules/.pnpm/@onjmin+chord-parser@1.
|
|
151
|
+
// node_modules/.pnpm/@onjmin+chord-parser@1.1.1/node_modules/@onjmin/chord-parser/dist/index.mjs
|
|
152
152
|
var SHARP_NAMES = [
|
|
153
153
|
"C",
|
|
154
154
|
"C#",
|
|
@@ -179,6 +179,10 @@ var FLAT_NAMES = [
|
|
|
179
179
|
];
|
|
180
180
|
var toPitchClass = (n) => (n % 12 + 12) % 12;
|
|
181
181
|
var noteName = (pc, flat = false) => (flat ? FLAT_NAMES : SHARP_NAMES)[toPitchClass(pc)];
|
|
182
|
+
var LETTER_FIFTH = [0, 2, 4, -1, 1, 3, 5];
|
|
183
|
+
var DEGREE_FIFTH = [0, 2, 4, -1, 1, 3, 5];
|
|
184
|
+
var deg2fifth = (deg) => DEGREE_FIFTH[(deg - 1) % 7];
|
|
185
|
+
var HALF_FIFTH = 7;
|
|
182
186
|
var SyntaxErrorWithPos = class extends Error {
|
|
183
187
|
constructor(input, msg) {
|
|
184
188
|
super(
|
|
@@ -225,6 +229,16 @@ var Input = class _Input {
|
|
|
225
229
|
};
|
|
226
230
|
var Output = class {
|
|
227
231
|
pitch = null;
|
|
232
|
+
/** ルート音の五度圏インデックス。`pitch` と対で持つ。 */
|
|
233
|
+
rootFifth = 0;
|
|
234
|
+
/**
|
|
235
|
+
* 構成音の五度圏オフセット。キーは「ルートからの相対半音 mod 12」。
|
|
236
|
+
*
|
|
237
|
+
* 五度圏インデックスはオクターブ不変なので、分数コードのオクターブシフト
|
|
238
|
+
* (`value` の配列を ±12 する処理)の影響を受けない。そのため構成音の集合とは
|
|
239
|
+
* 別にこの対応表を持っておけば、配列操作側に手を入れずに綴りを保てる。
|
|
240
|
+
*/
|
|
241
|
+
relFifths = /* @__PURE__ */ new Map();
|
|
228
242
|
chord = null;
|
|
229
243
|
isChord = false;
|
|
230
244
|
pending = null;
|
|
@@ -308,9 +322,17 @@ var parseFormula = (input, output = new Output(), nest = 0) => {
|
|
|
308
322
|
case DIVIDE: {
|
|
309
323
|
const o = parseFormula(input, new Output(), nest);
|
|
310
324
|
const v = [...output.value];
|
|
325
|
+
const rebase = (semi, fifth) => noteFifth(
|
|
326
|
+
output,
|
|
327
|
+
semi - output.pitch,
|
|
328
|
+
fifth - output.rootFifth
|
|
329
|
+
);
|
|
311
330
|
if (o.isChord) {
|
|
331
|
+
for (const [rel, f] of o.relFifths)
|
|
332
|
+
rebase(o.pitch + rel, o.rootFifth + f);
|
|
312
333
|
output.value = [...o.value].concat(v);
|
|
313
334
|
} else {
|
|
335
|
+
rebase(o.pitch, o.rootFifth);
|
|
314
336
|
const a = v.sort((x2, y) => x2 - y);
|
|
315
337
|
const pitch = (o.pitch + 3) % 12 - 3;
|
|
316
338
|
if (a[0] < pitch) {
|
|
@@ -357,63 +379,91 @@ var parsePitch = (input, output) => {
|
|
|
357
379
|
const pitch = pitchMatcher.parse(input);
|
|
358
380
|
if (pitch === null) err(input, "Not found pitch");
|
|
359
381
|
output.pitch = pitch;
|
|
382
|
+
output.rootFifth = LETTER_FIFTH[idx2pitch.slice(0, 7).indexOf(pitch)];
|
|
360
383
|
for (let i2 = 0; i2 < 2; i2++) {
|
|
361
384
|
const half = parseHalf(input, true);
|
|
362
385
|
if (half === null) break;
|
|
363
386
|
output.pitch += half;
|
|
387
|
+
output.rootFifth += half * HALF_FIFTH;
|
|
364
388
|
}
|
|
365
389
|
return parseBase(input, output);
|
|
366
390
|
};
|
|
391
|
+
var noteFifth = (out, semi, fifth) => {
|
|
392
|
+
out.relFifths.set((semi % 12 + 12) % 12, fifth);
|
|
393
|
+
};
|
|
367
394
|
var MAJOR = [0, 4, 7];
|
|
368
395
|
var DIM = [0, 3, 6];
|
|
396
|
+
var BASE_FIFTHS = /* @__PURE__ */ new Map();
|
|
397
|
+
var MINOR = [0, 3, 7];
|
|
398
|
+
var AUG = [0, 4, 8];
|
|
399
|
+
var HALF_DIM = [0, 3, 6, 10];
|
|
400
|
+
BASE_FIFTHS.set(MAJOR, [0, 4, 1]);
|
|
401
|
+
BASE_FIFTHS.set(MINOR, [0, -3, 1]);
|
|
402
|
+
BASE_FIFTHS.set(DIM, [0, -3, -6]);
|
|
403
|
+
BASE_FIFTHS.set(AUG, [0, 4, 8]);
|
|
404
|
+
BASE_FIFTHS.set(HALF_DIM, [0, -3, -6, -2]);
|
|
369
405
|
var baseMatcher = new Matcher();
|
|
370
|
-
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"],
|
|
406
|
+
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"], MINOR);
|
|
371
407
|
baseMatcher.set(["dim", "\u3007"], DIM);
|
|
372
|
-
baseMatcher.set("+",
|
|
373
|
-
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"],
|
|
408
|
+
baseMatcher.set("+", AUG);
|
|
409
|
+
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"], HALF_DIM);
|
|
374
410
|
var parseBase = (input, output) => {
|
|
375
411
|
const isMajMarker = /^maj/i.test(input.str.slice(input.idx));
|
|
376
412
|
const res = isMajMarker ? null : baseMatcher.parse(input);
|
|
377
413
|
if (res !== null) output.isChord = true;
|
|
378
|
-
|
|
414
|
+
const base = res || MAJOR;
|
|
415
|
+
output.chord = new Set(base);
|
|
416
|
+
const baseFifths = BASE_FIFTHS.get(base);
|
|
417
|
+
if (baseFifths)
|
|
418
|
+
for (const [i2, semi] of base.entries())
|
|
419
|
+
noteFifth(output, semi, baseFifths[i2]);
|
|
379
420
|
if (res === DIM) {
|
|
380
421
|
const { num } = input;
|
|
381
422
|
const chord = output.chord;
|
|
382
|
-
if (num !== null)
|
|
423
|
+
if (num !== null) {
|
|
424
|
+
chord.add(deg2pitch(num) - 2);
|
|
425
|
+
noteFifth(output, deg2pitch(num) - 2, deg2fifth(num) - 2 * HALF_FIFTH);
|
|
426
|
+
}
|
|
383
427
|
}
|
|
384
428
|
output.nest = input.nest;
|
|
385
429
|
return parseTerm(input, output);
|
|
386
430
|
};
|
|
387
|
-
var
|
|
388
|
-
chord.add(deg2pitch(
|
|
431
|
+
var put = (chord, out, deg, half = 0) => {
|
|
432
|
+
chord.add(deg2pitch(deg) + half);
|
|
433
|
+
noteFifth(out, deg2pitch(deg) + half, deg2fifth(deg) + half * HALF_FIFTH);
|
|
389
434
|
};
|
|
390
|
-
var
|
|
435
|
+
var add = (chord, n, half, out) => {
|
|
436
|
+
put(chord, out, n, half);
|
|
437
|
+
};
|
|
438
|
+
var aug = (chord, out) => {
|
|
391
439
|
chord.delete(deg2pitch(5));
|
|
392
|
-
chord
|
|
440
|
+
put(chord, out, 5, 1);
|
|
393
441
|
};
|
|
394
|
-
var _7th = (chord, n, _half2, isFlat = false) => {
|
|
442
|
+
var _7th = (chord, n, _half2, out, isFlat = false) => {
|
|
395
443
|
if (n === 5) chord.delete(deg2pitch(3));
|
|
396
|
-
else if (n === 6) chord
|
|
397
|
-
else if (n === 69)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (n >=
|
|
402
|
-
if (n >=
|
|
444
|
+
else if (n === 6) put(chord, out, 6);
|
|
445
|
+
else if (n === 69) {
|
|
446
|
+
put(chord, out, 6);
|
|
447
|
+
put(chord, out, 9);
|
|
448
|
+
} else {
|
|
449
|
+
if (n >= 7) put(chord, out, 7, isFlat ? -1 : 0);
|
|
450
|
+
if (n >= 9) put(chord, out, 9);
|
|
451
|
+
if (n >= 11) put(chord, out, 11);
|
|
452
|
+
if (n >= 13) put(chord, out, 13);
|
|
403
453
|
}
|
|
404
454
|
};
|
|
405
|
-
var _half = (chord, n, half) => {
|
|
455
|
+
var _half = (chord, n, half, out) => {
|
|
406
456
|
chord.delete(deg2pitch(n));
|
|
407
|
-
chord
|
|
457
|
+
put(chord, out, n, half);
|
|
408
458
|
};
|
|
409
459
|
var funcMatcher = new Matcher();
|
|
410
460
|
funcMatcher.set("add", add);
|
|
411
461
|
funcMatcher.set(["omit", "no"], (chord, n, half) => {
|
|
412
462
|
chord.delete(deg2pitch(n) + half);
|
|
413
463
|
});
|
|
414
|
-
funcMatcher.set("sus", (chord, n, half) => {
|
|
464
|
+
funcMatcher.set("sus", (chord, n, half, out) => {
|
|
415
465
|
chord.delete(deg2pitch(3));
|
|
416
|
-
chord
|
|
466
|
+
put(chord, out, n, half);
|
|
417
467
|
});
|
|
418
468
|
funcMatcher.set(
|
|
419
469
|
["M", "maj", "Maj", "major", "Major", "\u25B3", "\u0394"],
|
|
@@ -429,17 +479,18 @@ var parseFunc = (input, output) => {
|
|
|
429
479
|
const half = parseHalf(input);
|
|
430
480
|
const { num } = input;
|
|
431
481
|
if (num === null) {
|
|
432
|
-
if (isAug) aug(chord);
|
|
482
|
+
if (isAug) aug(chord, output);
|
|
433
483
|
else err(input, "Not found number");
|
|
434
484
|
}
|
|
435
485
|
if (half === null) {
|
|
436
|
-
if (input.nest === output.nest)
|
|
437
|
-
|
|
486
|
+
if (input.nest === output.nest)
|
|
487
|
+
_7th(chord, num, 0, output, true);
|
|
488
|
+
else add(chord, num, 0, output);
|
|
438
489
|
} else {
|
|
439
|
-
_half(chord, num, half);
|
|
490
|
+
_half(chord, num, half, output);
|
|
440
491
|
}
|
|
441
492
|
} else if (func === aug) {
|
|
442
|
-
aug(chord);
|
|
493
|
+
aug(chord, output);
|
|
443
494
|
} else {
|
|
444
495
|
output.pending = func;
|
|
445
496
|
}
|
|
@@ -453,7 +504,8 @@ var parsePending = (input, output) => {
|
|
|
453
504
|
pending(
|
|
454
505
|
chord,
|
|
455
506
|
num,
|
|
456
|
-
half === null ? 0 : half
|
|
507
|
+
half === null ? 0 : half,
|
|
508
|
+
output
|
|
457
509
|
);
|
|
458
510
|
output.pending = null;
|
|
459
511
|
return parseTerm(input, output);
|
|
@@ -465,12 +517,29 @@ var parseChord = (symbol) => {
|
|
|
465
517
|
const pitchClasses = [...new Set(notes.map(toPitchClass))].sort(
|
|
466
518
|
(a, b) => a - b
|
|
467
519
|
);
|
|
520
|
+
const rootPitch = output.pitch;
|
|
521
|
+
const rootFifth = output.rootFifth;
|
|
522
|
+
const noteFifths = notes.map((n) => {
|
|
523
|
+
const rel = ((n - rootPitch) % 12 + 12) % 12;
|
|
524
|
+
const f = output.relFifths.get(rel);
|
|
525
|
+
if (f !== void 0) return rootFifth + f;
|
|
526
|
+
let best = 0;
|
|
527
|
+
for (let k = -6; k <= 6; k++) {
|
|
528
|
+
if ((k * 7 % 12 + 12) % 12 === rel) {
|
|
529
|
+
best = k;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return rootFifth + best;
|
|
534
|
+
});
|
|
468
535
|
return {
|
|
469
536
|
symbol,
|
|
470
|
-
root: toPitchClass(
|
|
537
|
+
root: toPitchClass(rootPitch),
|
|
471
538
|
notes,
|
|
472
539
|
pitchClasses,
|
|
473
|
-
intervals
|
|
540
|
+
intervals,
|
|
541
|
+
rootFifth,
|
|
542
|
+
noteFifths
|
|
474
543
|
};
|
|
475
544
|
};
|
|
476
545
|
var QUALITY_SOURCE = [
|
|
@@ -1499,10 +1568,72 @@ var createChannelStrip = (ctx, destination, options = {}) => {
|
|
|
1499
1568
|
};
|
|
1500
1569
|
};
|
|
1501
1570
|
|
|
1571
|
+
// src/tuning.ts
|
|
1572
|
+
var UNITS_PER_OCTAVE = 372;
|
|
1573
|
+
var UNITS_PER_SEMITONE = 372 / 12;
|
|
1574
|
+
var UNITS_PER_EDO31_DEGREE = 372 / 31;
|
|
1575
|
+
var CENTS_PER_UNIT = 1200 / UNITS_PER_OCTAVE;
|
|
1576
|
+
var A4_UNITS = 69 * UNITS_PER_SEMITONE;
|
|
1577
|
+
var A4_HZ = 440;
|
|
1578
|
+
var DEFAULT_EDO = 12;
|
|
1579
|
+
var unitsPerStep = (edo) => UNITS_PER_OCTAVE / edo;
|
|
1580
|
+
var midiToUnits = (midi) => midi * UNITS_PER_SEMITONE;
|
|
1581
|
+
var unitsToMidi = (units) => units / UNITS_PER_SEMITONE;
|
|
1582
|
+
var unitsToHz = (units) => A4_HZ * 2 ** ((units - A4_UNITS) / UNITS_PER_OCTAVE);
|
|
1583
|
+
var unitsToMidiDetune = (units) => {
|
|
1584
|
+
const midi = Math.round(units / UNITS_PER_SEMITONE);
|
|
1585
|
+
return {
|
|
1586
|
+
midi,
|
|
1587
|
+
detuneCents: (units - midi * UNITS_PER_SEMITONE) * CENTS_PER_UNIT
|
|
1588
|
+
};
|
|
1589
|
+
};
|
|
1590
|
+
var NATURAL_STEPS = {
|
|
1591
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
1592
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
1593
|
+
};
|
|
1594
|
+
var chromaticStep = (edo) => edo === 31 ? 2 : 1;
|
|
1595
|
+
var MICRO_STEP = 1;
|
|
1596
|
+
var isNaturalLetter = (ch) => Object.hasOwn(NATURAL_STEPS[12], ch);
|
|
1597
|
+
var naturalStep = (ch, edo) => NATURAL_STEPS[edo][ch] ?? null;
|
|
1598
|
+
var spellingToUnits = (letter, octave, chromatic, micro, edo) => {
|
|
1599
|
+
const step = naturalStep(letter, edo);
|
|
1600
|
+
if (step === null) return null;
|
|
1601
|
+
const perStep = unitsPerStep(edo);
|
|
1602
|
+
const stepsFromC = step + chromatic * chromaticStep(edo) + micro * MICRO_STEP;
|
|
1603
|
+
return (octave + 1) * UNITS_PER_OCTAVE + stepsFromC * perStep;
|
|
1604
|
+
};
|
|
1605
|
+
var FIFTH_STEPS = { 12: 7, 31: 18 };
|
|
1606
|
+
var fifthToStep = (fifthIndex, edo) => {
|
|
1607
|
+
const n = fifthIndex * FIFTH_STEPS[edo];
|
|
1608
|
+
return (n % edo + edo) % edo;
|
|
1609
|
+
};
|
|
1610
|
+
var fifthToUnits = (fifthIndex, edo) => fifthToStep(fifthIndex, edo) * unitsPerStep(edo);
|
|
1611
|
+
var PITCH_ENCODING_VERSION = 2;
|
|
1612
|
+
var pitchV1ToUnits = (pitch) => pitch * UNITS_PER_SEMITONE;
|
|
1613
|
+
var unitsToPitchV1 = (units) => Math.round(units / UNITS_PER_SEMITONE);
|
|
1614
|
+
|
|
1502
1615
|
// src/chords.ts
|
|
1503
1616
|
var C3 = 48;
|
|
1617
|
+
var MEANTONE_STEP_BY_SEMITONE = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
1618
|
+
var chordToneToUnits = (relSemitone, relFifth, rootSemitone, rootFifth, edo) => {
|
|
1619
|
+
const perStep = UNITS_PER_OCTAVE / edo;
|
|
1620
|
+
const rootOct = Math.floor(rootSemitone / 12);
|
|
1621
|
+
const rootWithin = (rootSemitone % 12 + 12) % 12;
|
|
1622
|
+
const rootStep = edo === 31 ? (fifthToStep(rootFifth, 31) + MEANTONE_STEP_BY_SEMITONE[rootWithin] - fifthToStep(rootFifth, 31) + 31) % 31 : rootWithin;
|
|
1623
|
+
const relWithin12 = (relSemitone % 12 + 12) % 12;
|
|
1624
|
+
const relOct = Math.round((relSemitone - relWithin12) / 12);
|
|
1625
|
+
const relStep = (fifthToStep(relFifth, edo) - fifthToStep(rootFifth, edo) + edo) % edo;
|
|
1626
|
+
return (rootOct + relOct) * UNITS_PER_OCTAVE + (rootStep + relStep) * perStep;
|
|
1627
|
+
};
|
|
1504
1628
|
var buildChordPlacements = (options) => {
|
|
1505
|
-
const {
|
|
1629
|
+
const {
|
|
1630
|
+
chordStr,
|
|
1631
|
+
patternType,
|
|
1632
|
+
rootShift,
|
|
1633
|
+
bpm,
|
|
1634
|
+
stepsPerBar,
|
|
1635
|
+
edo = 12
|
|
1636
|
+
} = options;
|
|
1506
1637
|
const placements = [];
|
|
1507
1638
|
if (!chordStr.trim()) return placements;
|
|
1508
1639
|
const offset = rootShift;
|
|
@@ -1531,8 +1662,20 @@ var buildChordPlacements = (options) => {
|
|
|
1531
1662
|
for (const group of Object.values(chordGroups)) {
|
|
1532
1663
|
for (const chord of group) {
|
|
1533
1664
|
let notes;
|
|
1665
|
+
let toUnits;
|
|
1534
1666
|
try {
|
|
1535
|
-
|
|
1667
|
+
const parsed = parseChord(`${chord.key}${chord.chord}`);
|
|
1668
|
+
notes = [...parsed.notes];
|
|
1669
|
+
const fifthOf = new Map(
|
|
1670
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1671
|
+
);
|
|
1672
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1673
|
+
rel,
|
|
1674
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1675
|
+
C3 + offset,
|
|
1676
|
+
parsed.rootFifth,
|
|
1677
|
+
edo === 31 ? 31 : 12
|
|
1678
|
+
);
|
|
1536
1679
|
} catch {
|
|
1537
1680
|
continue;
|
|
1538
1681
|
}
|
|
@@ -1541,7 +1684,7 @@ var buildChordPlacements = (options) => {
|
|
|
1541
1684
|
for (const noteOffset of notes) {
|
|
1542
1685
|
placements.push({
|
|
1543
1686
|
startStep: chord.whenStep,
|
|
1544
|
-
|
|
1687
|
+
pitchUnits: toUnits(noteOffset),
|
|
1545
1688
|
durationSteps: noteLength,
|
|
1546
1689
|
velocity: 100
|
|
1547
1690
|
});
|
|
@@ -1551,7 +1694,7 @@ var buildChordPlacements = (options) => {
|
|
|
1551
1694
|
notes.forEach((noteOffset, i2) => {
|
|
1552
1695
|
placements.push({
|
|
1553
1696
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1554
|
-
|
|
1697
|
+
pitchUnits: toUnits(noteOffset),
|
|
1555
1698
|
durationSteps: noteLength - i2 * arpInterval,
|
|
1556
1699
|
velocity: 100
|
|
1557
1700
|
});
|
|
@@ -1561,7 +1704,7 @@ var buildChordPlacements = (options) => {
|
|
|
1561
1704
|
notes.forEach((noteOffset, i2) => {
|
|
1562
1705
|
placements.push({
|
|
1563
1706
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1564
|
-
|
|
1707
|
+
pitchUnits: toUnits(noteOffset),
|
|
1565
1708
|
durationSteps: Math.max(12, noteLength - i2 * arpInterval),
|
|
1566
1709
|
velocity: 100
|
|
1567
1710
|
});
|
|
@@ -1575,7 +1718,7 @@ var buildChordPlacements = (options) => {
|
|
|
1575
1718
|
for (const noteOffset of notes) {
|
|
1576
1719
|
placements.push({
|
|
1577
1720
|
startStep: syncopatedStep,
|
|
1578
|
-
|
|
1721
|
+
pitchUnits: toUnits(noteOffset),
|
|
1579
1722
|
durationSteps: Math.min(halfBeat, 12),
|
|
1580
1723
|
velocity: 100
|
|
1581
1724
|
});
|
|
@@ -1594,7 +1737,7 @@ var buildChordPlacements = (options) => {
|
|
|
1594
1737
|
for (const noteOffset of notes) {
|
|
1595
1738
|
placements.push({
|
|
1596
1739
|
startStep: noteStart,
|
|
1597
|
-
|
|
1740
|
+
pitchUnits: toUnits(noteOffset),
|
|
1598
1741
|
durationSteps: yatsumeLengthSteps,
|
|
1599
1742
|
velocity: 100
|
|
1600
1743
|
});
|
|
@@ -1606,7 +1749,7 @@ var buildChordPlacements = (options) => {
|
|
|
1606
1749
|
const stepOffset = i2 * Math.floor(stepsPerBar / 4);
|
|
1607
1750
|
placements.push({
|
|
1608
1751
|
startStep: chord.whenStep + stepOffset,
|
|
1609
|
-
|
|
1752
|
+
pitchUnits: toUnits(noteOffset),
|
|
1610
1753
|
durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
|
|
1611
1754
|
velocity: 100
|
|
1612
1755
|
});
|
|
@@ -1618,8 +1761,20 @@ var buildChordPlacements = (options) => {
|
|
|
1618
1761
|
const chordNames = chordStr.split(/[\s,]+/).filter((c) => c);
|
|
1619
1762
|
chordNames.forEach((chordName, barIndex) => {
|
|
1620
1763
|
let notes;
|
|
1764
|
+
let toUnits;
|
|
1621
1765
|
try {
|
|
1622
|
-
|
|
1766
|
+
const parsed = parseChord(chordName);
|
|
1767
|
+
notes = [...parsed.notes];
|
|
1768
|
+
const fifthOf = new Map(
|
|
1769
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1770
|
+
);
|
|
1771
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1772
|
+
rel,
|
|
1773
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1774
|
+
C3 + offset,
|
|
1775
|
+
parsed.rootFifth,
|
|
1776
|
+
edo === 31 ? 31 : 12
|
|
1777
|
+
);
|
|
1623
1778
|
} catch {
|
|
1624
1779
|
return;
|
|
1625
1780
|
}
|
|
@@ -1629,7 +1784,7 @@ var buildChordPlacements = (options) => {
|
|
|
1629
1784
|
const stepOffset = i2 * 3;
|
|
1630
1785
|
placements.push({
|
|
1631
1786
|
startStep: startStep + stepOffset,
|
|
1632
|
-
|
|
1787
|
+
pitchUnits: toUnits(noteOffset),
|
|
1633
1788
|
durationSteps: chordLength - stepOffset,
|
|
1634
1789
|
velocity: 100
|
|
1635
1790
|
});
|
|
@@ -2161,6 +2316,11 @@ var DEFAULT_VELOCITY = 100;
|
|
|
2161
2316
|
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
2162
2317
|
var DEFAULT_STEPS_PER_BAR = 192;
|
|
2163
2318
|
var MML_END_MARKER = "#end;";
|
|
2319
|
+
var PITCH_RANGE_START = 0;
|
|
2320
|
+
var PITCH_RANGE_END = 3937;
|
|
2321
|
+
var unitsPerRow = (edo) => 372 / edo;
|
|
2322
|
+
var keyCountFor = (edo) => Math.floor((PITCH_RANGE_END - PITCH_RANGE_START) / unitsPerRow(edo)) + 1;
|
|
2323
|
+
var KEY_COUNT = keyCountFor(12);
|
|
2164
2324
|
|
|
2165
2325
|
// src/vibrato.ts
|
|
2166
2326
|
var VIBRATO_MIN_SEC = 0.35;
|
|
@@ -2560,7 +2720,8 @@ var FORMANTS = {
|
|
|
2560
2720
|
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
2561
2721
|
N: [250, 1e3]
|
|
2562
2722
|
};
|
|
2563
|
-
var
|
|
2723
|
+
var unitsToFreq = (units) => 440 * 2 ** ((units - 2139) / 372);
|
|
2724
|
+
var unitsToMidiFloat = (units) => units / 31;
|
|
2564
2725
|
var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
2565
2726
|
const active = /* @__PURE__ */ new Set();
|
|
2566
2727
|
const voice = (syllable, e) => {
|
|
@@ -2594,7 +2755,7 @@ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
|
2594
2755
|
}
|
|
2595
2756
|
const osc = ctx.createOscillator();
|
|
2596
2757
|
osc.type = "sawtooth";
|
|
2597
|
-
osc.frequency.value =
|
|
2758
|
+
osc.frequency.value = unitsToFreq(e.pitchUnits);
|
|
2598
2759
|
const makeFormant = (freq, q2, gainScale) => {
|
|
2599
2760
|
const filter = ctx.createBiquadFilter();
|
|
2600
2761
|
filter.type = "bandpass";
|
|
@@ -2846,7 +3007,7 @@ var createLocalBackend = async (options) => {
|
|
|
2846
3007
|
if (!consonantPcm || !vowelPcm) return null;
|
|
2847
3008
|
const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
|
|
2848
3009
|
if (!spliced) return null;
|
|
2849
|
-
const targetHz =
|
|
3010
|
+
const targetHz = unitsToFreq(pitch);
|
|
2850
3011
|
const audio = worldline.renderNote({
|
|
2851
3012
|
pcm: spliced.pcm,
|
|
2852
3013
|
pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
|
|
@@ -2875,7 +3036,7 @@ var createLocalBackend = async (options) => {
|
|
|
2875
3036
|
if (!pcm || pcm.length === 0) return null;
|
|
2876
3037
|
const entry = bank.manifest.phonemes[alias];
|
|
2877
3038
|
const lead = leadInFromEntry(entry);
|
|
2878
|
-
const targetHz =
|
|
3039
|
+
const targetHz = unitsToFreq(pitch);
|
|
2879
3040
|
if (worldline) {
|
|
2880
3041
|
const audio = worldline.renderNote({
|
|
2881
3042
|
pcm,
|
|
@@ -3082,7 +3243,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3082
3243
|
backend.pitchTokens,
|
|
3083
3244
|
syllable,
|
|
3084
3245
|
prevVowel,
|
|
3085
|
-
e.
|
|
3246
|
+
unitsToMidiFloat(e.pitchUnits)
|
|
3086
3247
|
);
|
|
3087
3248
|
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
3088
3249
|
if (!alias) return;
|
|
@@ -3090,7 +3251,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3090
3251
|
const peak = Math.max(1e-4, e.volume);
|
|
3091
3252
|
const pan = e.pan ?? 0;
|
|
3092
3253
|
const durationMs = Math.max(60, e.duration * 1e3);
|
|
3093
|
-
void renderInto(alias, e.
|
|
3254
|
+
void renderInto(alias, e.pitchUnits, durationMs).then((r) => {
|
|
3094
3255
|
if (r)
|
|
3095
3256
|
schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
|
|
3096
3257
|
});
|
|
@@ -3102,7 +3263,10 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3102
3263
|
backend.pitchTokens,
|
|
3103
3264
|
syllable,
|
|
3104
3265
|
prevVowelArg,
|
|
3105
|
-
|
|
3266
|
+
// 多音階バンクのピッチトークン("_G4" 等)は録音の音名なので、
|
|
3267
|
+
// 最寄り選択はMIDIノート番号の尺度で行う。units のまま渡すと常に
|
|
3268
|
+
// 最高音のトークンが選ばれてしまう。
|
|
3269
|
+
unitsToMidiFloat(pitch)
|
|
3106
3270
|
);
|
|
3107
3271
|
if (!alias) return null;
|
|
3108
3272
|
const dMs = Math.max(60, durationMs);
|
|
@@ -3134,13 +3298,14 @@ var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
|
3134
3298
|
var STREAM_POLL_MS = 100;
|
|
3135
3299
|
var OCTAVE_UNISON_PEAK_SCALE = 0.6;
|
|
3136
3300
|
var octaveUnisonOffsets = (mode) => {
|
|
3301
|
+
const OCT = 372;
|
|
3137
3302
|
switch (mode) {
|
|
3138
3303
|
case "down":
|
|
3139
|
-
return [-
|
|
3304
|
+
return [-OCT];
|
|
3140
3305
|
case "up":
|
|
3141
|
-
return [
|
|
3306
|
+
return [OCT];
|
|
3142
3307
|
case "both":
|
|
3143
|
-
return [-
|
|
3308
|
+
return [-OCT, OCT];
|
|
3144
3309
|
default:
|
|
3145
3310
|
return [];
|
|
3146
3311
|
}
|
|
@@ -3350,7 +3515,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3350
3515
|
const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
|
|
3351
3516
|
model(note.syllable, {
|
|
3352
3517
|
trackId: track.id ?? "",
|
|
3353
|
-
pitch,
|
|
3518
|
+
pitchUnits: pitch,
|
|
3354
3519
|
velocity: 100,
|
|
3355
3520
|
volume: effPeak,
|
|
3356
3521
|
when,
|
|
@@ -3416,17 +3581,13 @@ var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
|
3416
3581
|
};
|
|
3417
3582
|
|
|
3418
3583
|
// src/mml-parser.ts
|
|
3419
|
-
var
|
|
3420
|
-
c: 0,
|
|
3421
|
-
d:
|
|
3422
|
-
e: 4,
|
|
3423
|
-
f: 5,
|
|
3424
|
-
g: 7,
|
|
3425
|
-
a: 9,
|
|
3426
|
-
b: 11
|
|
3584
|
+
var NATURAL_STEPS2 = {
|
|
3585
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
3586
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
3427
3587
|
};
|
|
3588
|
+
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
3428
3589
|
var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
3429
|
-
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode)=([\w-]+)/gi;
|
|
3590
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
|
|
3430
3591
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
3431
3592
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
3432
3593
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -3476,6 +3637,9 @@ var parseMmlMeta = (mml) => {
|
|
|
3476
3637
|
if (m[2] === "simple" || m[2] === "advanced") {
|
|
3477
3638
|
meta.mode = m[2];
|
|
3478
3639
|
}
|
|
3640
|
+
} else if (key === "edo") {
|
|
3641
|
+
const e = Number.parseInt(m[2], 10);
|
|
3642
|
+
if (e === 12 || e === 31) meta.edo = e;
|
|
3479
3643
|
}
|
|
3480
3644
|
}
|
|
3481
3645
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -3578,6 +3742,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
3578
3742
|
if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
|
|
3579
3743
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
3580
3744
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
3745
|
+
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
3581
3746
|
if (meta.trackInstruments) {
|
|
3582
3747
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
3583
3748
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -3652,6 +3817,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
3652
3817
|
const endMarkerBase = MML_END_MARKER.replace(/;+$/, "");
|
|
3653
3818
|
const endRegex = new RegExp(`(?<![cdafgCDAFG])${endMarkerBase}\\b;?`, "gi");
|
|
3654
3819
|
const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
|
|
3820
|
+
const edo = meta.edo === 31 ? 31 : 12;
|
|
3821
|
+
const naturals = NATURAL_STEPS2[edo];
|
|
3822
|
+
const unitsPerStep2 = 372 / edo;
|
|
3823
|
+
const chromaticStep2 = edo === 31 ? 2 : 1;
|
|
3655
3824
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
3656
3825
|
let trackIndex = 0;
|
|
3657
3826
|
let sourceTrackIndex = 0;
|
|
@@ -3699,6 +3868,19 @@ var parseMML = (mml, options = {}) => {
|
|
|
3699
3868
|
type
|
|
3700
3869
|
});
|
|
3701
3870
|
};
|
|
3871
|
+
const readAccidentals = () => {
|
|
3872
|
+
let delta = 0;
|
|
3873
|
+
while (j < body.length) {
|
|
3874
|
+
const a = body[j];
|
|
3875
|
+
if (a === "#") delta += chromaticStep2;
|
|
3876
|
+
else if (a === "-") delta -= chromaticStep2;
|
|
3877
|
+
else if (a === "+") delta += edo === 31 ? 1 : chromaticStep2;
|
|
3878
|
+
else if (a === "_") delta -= edo === 31 ? 1 : chromaticStep2;
|
|
3879
|
+
else break;
|
|
3880
|
+
j++;
|
|
3881
|
+
}
|
|
3882
|
+
return delta;
|
|
3883
|
+
};
|
|
3702
3884
|
const parseLength = () => {
|
|
3703
3885
|
let numStr = "";
|
|
3704
3886
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -3770,17 +3952,11 @@ var parseMML = (mml, options = {}) => {
|
|
|
3770
3952
|
const savedOctave = octave;
|
|
3771
3953
|
while (j < body.length && body[j] !== "]") {
|
|
3772
3954
|
const c = body[j];
|
|
3773
|
-
if (
|
|
3774
|
-
let
|
|
3955
|
+
if (isNatural(c)) {
|
|
3956
|
+
let pitchSteps = naturals[c];
|
|
3775
3957
|
j++;
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
j++;
|
|
3779
|
-
} else if (j < body.length && body[j] === "-") {
|
|
3780
|
-
pitch--;
|
|
3781
|
-
j++;
|
|
3782
|
-
}
|
|
3783
|
-
chordNotes.push((octave + 1) * 12 + pitch);
|
|
3958
|
+
pitchSteps += readAccidentals();
|
|
3959
|
+
chordNotes.push((octave + 1) * 372 + pitchSteps * unitsPerStep2);
|
|
3784
3960
|
} else if (c === ">") {
|
|
3785
3961
|
octave = Math.min(8, octave + 1);
|
|
3786
3962
|
j++;
|
|
@@ -3806,7 +3982,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
3806
3982
|
placements.push({
|
|
3807
3983
|
trackIndex,
|
|
3808
3984
|
startStep: currentStep,
|
|
3809
|
-
|
|
3985
|
+
pitchUnits: p,
|
|
3810
3986
|
durationSteps: Math.max(1, steps),
|
|
3811
3987
|
velocity
|
|
3812
3988
|
});
|
|
@@ -3814,23 +3990,17 @@ var parseMML = (mml, options = {}) => {
|
|
|
3814
3990
|
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
3815
3991
|
currentStep += steps;
|
|
3816
3992
|
octave = savedOctave;
|
|
3817
|
-
} else if (
|
|
3818
|
-
let
|
|
3993
|
+
} else if (isNatural(ch)) {
|
|
3994
|
+
let pitchSteps = naturals[ch];
|
|
3819
3995
|
j++;
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
j++;
|
|
3823
|
-
} else if (j < body.length && body[j] === "-") {
|
|
3824
|
-
pitch--;
|
|
3825
|
-
j++;
|
|
3826
|
-
}
|
|
3827
|
-
const midiPitch = (octave + 1) * 12 + pitch;
|
|
3996
|
+
pitchSteps += readAccidentals();
|
|
3997
|
+
const midiPitch = (octave + 1) * 372 + pitchSteps * unitsPerStep2;
|
|
3828
3998
|
const steps = parseLength();
|
|
3829
3999
|
recordContributor();
|
|
3830
4000
|
placements.push({
|
|
3831
4001
|
trackIndex,
|
|
3832
4002
|
startStep: currentStep,
|
|
3833
|
-
|
|
4003
|
+
pitchUnits: midiPitch,
|
|
3834
4004
|
durationSteps: Math.max(1, steps),
|
|
3835
4005
|
velocity
|
|
3836
4006
|
});
|
|
@@ -3962,7 +4132,7 @@ var createSequencer = (options) => {
|
|
|
3962
4132
|
maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
|
|
3963
4133
|
timeline.push({
|
|
3964
4134
|
trackId: track.id,
|
|
3965
|
-
pitch: note.
|
|
4135
|
+
pitch: note.pitchUnits,
|
|
3966
4136
|
volume: track.volume / 100,
|
|
3967
4137
|
velocity: note.velocity ?? DEFAULT_PLAYBACK_VELOCITY,
|
|
3968
4138
|
when,
|
|
@@ -4025,7 +4195,7 @@ var createSequencer = (options) => {
|
|
|
4025
4195
|
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
4026
4196
|
options.onPlayNote({
|
|
4027
4197
|
trackId: ev.trackId,
|
|
4028
|
-
|
|
4198
|
+
pitchUnits: ev.pitch,
|
|
4029
4199
|
velocity: ev.velocity,
|
|
4030
4200
|
volume: currentVolume * velocityVolume,
|
|
4031
4201
|
when: Math.max(0, _when),
|
|
@@ -6345,7 +6515,7 @@ var SONG_DRUM_PATTERNS = {
|
|
|
6345
6515
|
};
|
|
6346
6516
|
|
|
6347
6517
|
// src/synth.ts
|
|
6348
|
-
var freqFromPitch = (
|
|
6518
|
+
var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
|
|
6349
6519
|
var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
6350
6520
|
const wave = tone.wave ?? "square";
|
|
6351
6521
|
const attack = tone.attack ?? 0;
|
|
@@ -6361,7 +6531,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
|
6361
6531
|
const osc = ctx.createOscillator();
|
|
6362
6532
|
const gain = ctx.createGain();
|
|
6363
6533
|
osc.type = wave;
|
|
6364
|
-
osc.frequency.value = freqFromPitch(e.
|
|
6534
|
+
osc.frequency.value = freqFromPitch(e.pitchUnits);
|
|
6365
6535
|
const t0 = ctx.currentTime + e.when;
|
|
6366
6536
|
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
|
|
6367
6537
|
if (tone.decay) {
|
|
@@ -6466,7 +6636,7 @@ var playPlacements = (placements, options) => {
|
|
|
6466
6636
|
id: id++,
|
|
6467
6637
|
startStep: p.startStep,
|
|
6468
6638
|
durationSteps: p.durationSteps,
|
|
6469
|
-
|
|
6639
|
+
pitchUnits: p.pitchUnits,
|
|
6470
6640
|
velocity: p.velocity
|
|
6471
6641
|
}));
|
|
6472
6642
|
return {
|
|
@@ -6637,7 +6807,7 @@ var playNote = (options) => {
|
|
|
6637
6807
|
const dur = options.duration ?? 1;
|
|
6638
6808
|
synth.playNote({
|
|
6639
6809
|
trackId: "melody",
|
|
6640
|
-
|
|
6810
|
+
pitchUnits: options.pitchUnits,
|
|
6641
6811
|
velocity: 100,
|
|
6642
6812
|
volume: vol / 100,
|
|
6643
6813
|
when: 0,
|
|
@@ -6658,7 +6828,7 @@ var playChords = (chordStr, options = {}) => {
|
|
|
6658
6828
|
// 伴奏トラック
|
|
6659
6829
|
startStep: p.startStep,
|
|
6660
6830
|
durationSteps: p.durationSteps,
|
|
6661
|
-
|
|
6831
|
+
pitchUnits: p.pitchUnits,
|
|
6662
6832
|
velocity: p.velocity
|
|
6663
6833
|
}));
|
|
6664
6834
|
return playPlacements(placements, {
|
|
@@ -7191,38 +7361,66 @@ var DAW_CSS = `
|
|
|
7191
7361
|
flex-basis: 100%;
|
|
7192
7362
|
display: flex;
|
|
7193
7363
|
flex-wrap: nowrap;
|
|
7194
|
-
gap:
|
|
7364
|
+
gap: 4px;
|
|
7365
|
+
align-items: flex-end;
|
|
7195
7366
|
}
|
|
7196
7367
|
.dtm-pill {
|
|
7197
7368
|
--dtm-pill-color: var(--dtm-primary);
|
|
7369
|
+
position: relative;
|
|
7198
7370
|
display: inline-flex;
|
|
7199
7371
|
align-items: center;
|
|
7200
7372
|
justify-content: center;
|
|
7201
7373
|
flex: 1 1 0;
|
|
7202
7374
|
min-width: 0;
|
|
7203
|
-
height:
|
|
7375
|
+
height: 24px;
|
|
7204
7376
|
padding: 0;
|
|
7205
7377
|
border: 2px solid var(--c-black);
|
|
7206
|
-
background: color-mix(in srgb, var(--dtm-pill-color)
|
|
7207
|
-
color: var(--c-white);
|
|
7378
|
+
background: color-mix(in srgb, var(--dtm-pill-color) 22%, #0e121e);
|
|
7379
|
+
color: color-mix(in srgb, var(--c-white) 60%, transparent);
|
|
7208
7380
|
font-family: var(--dtm-font);
|
|
7209
7381
|
font-size: 11px;
|
|
7210
7382
|
font-weight: bold;
|
|
7211
7383
|
cursor: pointer;
|
|
7212
|
-
box-shadow:
|
|
7213
|
-
opacity: 0.
|
|
7384
|
+
box-shadow: 1px 1px 0 var(--c-black);
|
|
7385
|
+
opacity: 0.55;
|
|
7386
|
+
transition: opacity 120ms ease, background 120ms ease, transform 120ms ease;
|
|
7214
7387
|
}
|
|
7215
|
-
|
|
7388
|
+
.dtm-pill:hover:not(.dtm-pill--active) {
|
|
7389
|
+
opacity: 0.85;
|
|
7390
|
+
background: color-mix(in srgb, var(--dtm-pill-color) 45%, #0e121e);
|
|
7391
|
+
color: var(--c-white);
|
|
7392
|
+
}
|
|
7393
|
+
/* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u9BAE\u660E\u306A\u30C8\u30E9\u30C3\u30AF\u8272 + \u30B4\u30FC\u30EB\u30C9\u67A0 + \u30B0\u30ED\u30FC + \u4E0A\u6607\u7ACB\u4F53\u30BF\u30D6\u8868\u73FE */
|
|
7216
7394
|
.dtm-pill--active {
|
|
7217
7395
|
opacity: 1;
|
|
7396
|
+
height: 28px;
|
|
7397
|
+
background: var(--dtm-pill-color);
|
|
7398
|
+
color: #ffffff;
|
|
7399
|
+
text-shadow: 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
|
|
7218
7400
|
border-color: var(--dtm-gold);
|
|
7219
|
-
box-shadow:
|
|
7401
|
+
box-shadow:
|
|
7402
|
+
0 0 0 1.5px var(--dtm-gold),
|
|
7403
|
+
0 0 10px color-mix(in srgb, var(--dtm-pill-color) 70%, transparent),
|
|
7404
|
+
2px 2px 0 var(--c-black);
|
|
7405
|
+
z-index: 1;
|
|
7406
|
+
transform: translateY(-2px);
|
|
7220
7407
|
}
|
|
7221
|
-
.dtm-pill
|
|
7408
|
+
.dtm-pill--active::before {
|
|
7409
|
+
content: '';
|
|
7410
|
+
position: absolute;
|
|
7411
|
+
top: -2px;
|
|
7412
|
+
left: 2px;
|
|
7413
|
+
right: 2px;
|
|
7414
|
+
height: 2px;
|
|
7415
|
+
background: #ffffff;
|
|
7416
|
+
border-radius: 1px;
|
|
7417
|
+
opacity: 0.85;
|
|
7418
|
+
}
|
|
7419
|
+
.dtm-pill:not(.dtm-pill--active):active { transform: translate(1px,1px); box-shadow: none; }
|
|
7222
7420
|
/* \u518D\u751F\u4E2D\u3001\u5B9F\u969B\u306B\u767A\u97F3\u3057\u305F\u77AC\u9593\u3060\u3051\u70B9\u706F\uFF08\u3069\u306E\u30BF\u30D6\u304C\u4ECA\u9CF4\u3063\u3066\u3044\u308B\u304B\u8996\u899A\u7684\u306B\u5206\u304B\u308B\u3088\u3046\u306B\uFF09 */
|
|
7223
7421
|
.dtm-pill--sounding {
|
|
7224
|
-
|
|
7225
|
-
transition:
|
|
7422
|
+
filter: brightness(1.6);
|
|
7423
|
+
transition: filter 30ms linear;
|
|
7226
7424
|
}
|
|
7227
7425
|
|
|
7228
7426
|
/* \u2500\u2500\u2500 \u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\uFF08\u30C8\u30E9\u30C3\u30AB\u30FC\u98A8\uFF09 \u2500\u2500\u2500 */
|
|
@@ -7328,6 +7526,49 @@ var DAW_CSS = `
|
|
|
7328
7526
|
.dtm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
7329
7527
|
.dtm-track-body { display: flex; flex-direction: column; gap: 10px; }
|
|
7330
7528
|
|
|
7529
|
+
/* \u2500\u2500\u2500 \u30A2\u30AF\u30C6\u30A3\u30D6\u30C8\u30E9\u30C3\u30AF\u60C5\u5831\u30D0\u30CA\u30FC \u2500\u2500\u2500 */
|
|
7530
|
+
.dtm-active-track-banner {
|
|
7531
|
+
display: flex;
|
|
7532
|
+
align-items: center;
|
|
7533
|
+
gap: 8px;
|
|
7534
|
+
padding: 6px 10px;
|
|
7535
|
+
background: color-mix(in srgb, var(--dtm-track-color, var(--dtm-primary)) 16%, var(--dtm-deep));
|
|
7536
|
+
border: 2px solid var(--c-black);
|
|
7537
|
+
border-left: 5px solid var(--dtm-track-color, var(--dtm-primary));
|
|
7538
|
+
box-shadow: 2px 2px 0 var(--c-black);
|
|
7539
|
+
margin-bottom: 2px;
|
|
7540
|
+
}
|
|
7541
|
+
.dtm-active-track-badge {
|
|
7542
|
+
font-family: var(--dtm-font);
|
|
7543
|
+
font-size: 11px;
|
|
7544
|
+
font-weight: bold;
|
|
7545
|
+
background: var(--dtm-track-color, var(--dtm-primary));
|
|
7546
|
+
color: #ffffff;
|
|
7547
|
+
text-shadow: 1px 1px 0 #000, -1px -1px 0 #000;
|
|
7548
|
+
padding: 2px 6px;
|
|
7549
|
+
border: 1px solid var(--c-black);
|
|
7550
|
+
border-radius: 2px;
|
|
7551
|
+
letter-spacing: .06em;
|
|
7552
|
+
}
|
|
7553
|
+
.dtm-active-track-name {
|
|
7554
|
+
font-family: var(--dtm-font);
|
|
7555
|
+
font-size: 12px;
|
|
7556
|
+
font-weight: bold;
|
|
7557
|
+
color: var(--c-white);
|
|
7558
|
+
flex: 1 1 auto;
|
|
7559
|
+
}
|
|
7560
|
+
.dtm-active-track-pill {
|
|
7561
|
+
font-family: var(--dtm-font);
|
|
7562
|
+
font-size: 9px;
|
|
7563
|
+
font-weight: bold;
|
|
7564
|
+
letter-spacing: .08em;
|
|
7565
|
+
padding: 2px 5px;
|
|
7566
|
+
color: var(--dtm-gold);
|
|
7567
|
+
border: 1px solid var(--dtm-gold);
|
|
7568
|
+
background: rgba(0, 0, 0, 0.45);
|
|
7569
|
+
border-radius: 2px;
|
|
7570
|
+
}
|
|
7571
|
+
|
|
7331
7572
|
/* \u2500\u2500\u2500 MML\u51FA\u529B\uFF08CRT\u30BF\u30FC\u30DF\u30CA\u30EB\uFF09 \u2500\u2500\u2500 */
|
|
7332
7573
|
.dtm-output {
|
|
7333
7574
|
background: var(--c-black);
|
|
@@ -8744,7 +8985,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8744
8985
|
0
|
|
8745
8986
|
);
|
|
8746
8987
|
const timedNotes = placements.map((p) => ({
|
|
8747
|
-
pitch: p.
|
|
8988
|
+
pitch: unitsToPitchV1(p.pitchUnits),
|
|
8748
8989
|
when: p.startStep * secondsPerStep,
|
|
8749
8990
|
duration: p.durationSteps * secondsPerStep
|
|
8750
8991
|
}));
|
|
@@ -8777,7 +9018,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8777
9018
|
id: id++,
|
|
8778
9019
|
startStep: p.startStep,
|
|
8779
9020
|
durationSteps: p.durationSteps,
|
|
8780
|
-
|
|
9021
|
+
pitchUnits: p.pitchUnits,
|
|
8781
9022
|
velocity: DEFAULT_VELOCITY
|
|
8782
9023
|
}));
|
|
8783
9024
|
return {
|
|
@@ -9602,7 +9843,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9602
9843
|
(a, b) => a.startStep - b.startStep
|
|
9603
9844
|
);
|
|
9604
9845
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
9605
|
-
const semis = (lt.octave ?? 0) *
|
|
9846
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
9606
9847
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
9607
9848
|
const notes = [];
|
|
9608
9849
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -9610,7 +9851,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9610
9851
|
if (n.startStep < fromStep) continue;
|
|
9611
9852
|
notes.push({
|
|
9612
9853
|
syllable: lt.syllables[i2],
|
|
9613
|
-
pitch: n.
|
|
9854
|
+
pitch: n.pitchUnits + semis,
|
|
9614
9855
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
9615
9856
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
9616
9857
|
});
|
|
@@ -9885,6 +10126,7 @@ var SoundFont = class _SoundFont {
|
|
|
9885
10126
|
pitch = 60,
|
|
9886
10127
|
volume = 1,
|
|
9887
10128
|
velocity,
|
|
10129
|
+
detuneCents = 0,
|
|
9888
10130
|
when = 0,
|
|
9889
10131
|
duration = 1
|
|
9890
10132
|
} = {}) {
|
|
@@ -9904,7 +10146,7 @@ var SoundFont = class _SoundFont {
|
|
|
9904
10146
|
Object.assign(src, _param.src);
|
|
9905
10147
|
const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
|
|
9906
10148
|
const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
|
|
9907
|
-
src.detune.setValueAtTime(humanizeCents, 0);
|
|
10149
|
+
src.detune.setValueAtTime(humanizeCents + detuneCents, 0);
|
|
9908
10150
|
const effectiveVolume = volume * humanizeGainMul;
|
|
9909
10151
|
const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
|
|
9910
10152
|
const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
|
|
@@ -11043,7 +11285,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11043
11285
|
scheduleMetronomeBeats(ctx, cutGain);
|
|
11044
11286
|
};
|
|
11045
11287
|
const wafPlayDynamic = ({
|
|
11046
|
-
|
|
11288
|
+
pitchUnits,
|
|
11047
11289
|
velocity,
|
|
11048
11290
|
volume,
|
|
11049
11291
|
when,
|
|
@@ -11053,10 +11295,12 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11053
11295
|
}) => {
|
|
11054
11296
|
const waf = activeGmName ? _wafCache.get(activeGmName) : null;
|
|
11055
11297
|
if (waf) {
|
|
11298
|
+
const { midi, detuneCents } = unitsToMidiDetune(pitchUnits);
|
|
11056
11299
|
waf.play({
|
|
11057
11300
|
ctx,
|
|
11058
11301
|
destination,
|
|
11059
|
-
pitch,
|
|
11302
|
+
pitch: midi,
|
|
11303
|
+
detuneCents,
|
|
11060
11304
|
volume: volume * 0.85,
|
|
11061
11305
|
velocity,
|
|
11062
11306
|
when,
|
|
@@ -11066,7 +11310,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11066
11310
|
const synth = createSynth(ctx, destination);
|
|
11067
11311
|
synth.playNote({
|
|
11068
11312
|
trackId: "chord",
|
|
11069
|
-
|
|
11313
|
+
pitchUnits,
|
|
11070
11314
|
velocity: 100,
|
|
11071
11315
|
volume,
|
|
11072
11316
|
when,
|
|
@@ -11095,7 +11339,8 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11095
11339
|
trackIndex: 3,
|
|
11096
11340
|
startStep: whenStep,
|
|
11097
11341
|
durationSteps,
|
|
11098
|
-
|
|
11342
|
+
// chord-parser の構成音は12平均律の半音。内部表現の units へ写す。
|
|
11343
|
+
pitchUnits: pitchV1ToUnits(C32 + noteOffset),
|
|
11099
11344
|
velocity: 100
|
|
11100
11345
|
});
|
|
11101
11346
|
}
|
|
@@ -11108,7 +11353,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11108
11353
|
synth: false,
|
|
11109
11354
|
onPlayNote: (e) => {
|
|
11110
11355
|
wafPlayDynamic({
|
|
11111
|
-
|
|
11356
|
+
pitchUnits: e.pitchUnits,
|
|
11112
11357
|
velocity: e.velocity,
|
|
11113
11358
|
volume: e.volume,
|
|
11114
11359
|
when: e.when,
|
|
@@ -11550,6 +11795,14 @@ var buildUI = (target, options) => {
|
|
|
11550
11795
|
<summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
|
|
11551
11796
|
<div class="dtm-panel-body">
|
|
11552
11797
|
<div data-dtm="preset-select-slot"></div>
|
|
11798
|
+
<div class="dtm-row">
|
|
11799
|
+
<span class="dtm-label">\u97F3\u5F8B</span>
|
|
11800
|
+
<select class="dtm-select dtm-grow" data-dtm="edo-select">
|
|
11801
|
+
<option value="12">12\u5E73\u5747\u5F8B\uFF08\u901A\u5E38\uFF09</option>
|
|
11802
|
+
<option value="31">31\u5E73\u5747\u5F8B\uFF08\u5FAE\u5206\u97F3\uFF09</option>
|
|
11803
|
+
</select>
|
|
11804
|
+
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
11805
|
+
</div>
|
|
11553
11806
|
<div class="dtm-row">
|
|
11554
11807
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
11555
11808
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -11882,6 +12135,8 @@ var buildUI = (target, options) => {
|
|
|
11882
12135
|
copyMiniBtn: sel("copy-mini"),
|
|
11883
12136
|
overlay: sel("overlay"),
|
|
11884
12137
|
mmlInfoBtn: sel("mml-info"),
|
|
12138
|
+
edoSelect: sel("edo-select"),
|
|
12139
|
+
edoInfoBtn: sel("edo-info"),
|
|
11885
12140
|
modalOverlay: sel("modal-overlay"),
|
|
11886
12141
|
modalTitle: sel("modal-title"),
|
|
11887
12142
|
modalBody: sel("modal-body"),
|
|
@@ -12002,17 +12257,21 @@ var SCALES = [
|
|
|
12002
12257
|
[0, 2, 4, 7, 9]
|
|
12003
12258
|
// Pentatonic Major
|
|
12004
12259
|
];
|
|
12260
|
+
var MEANTONE_STEP_BY_SEMITONE2 = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
12005
12261
|
var generateRandomPattern = (core, options) => {
|
|
12006
|
-
const { stepsPerBar, startStep, pitchRangeStart } = options;
|
|
12262
|
+
const { stepsPerBar, startStep, pitchRangeStart, edo = 12 } = options;
|
|
12007
12263
|
const numBars = 8;
|
|
12008
12264
|
const noteLength = 24;
|
|
12009
|
-
const
|
|
12265
|
+
const semitoneToStep = (semi) => edo === 31 ? MEANTONE_STEP_BY_SEMITONE2[semi] : semi;
|
|
12266
|
+
const upr = UNITS_PER_OCTAVE / edo;
|
|
12267
|
+
const basePitch = pitchRangeStart + 60 * UNITS_PER_SEMITONE;
|
|
12010
12268
|
const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
|
|
12011
12269
|
const rootOffset = Math.floor(Math.random() * 12);
|
|
12012
12270
|
const availablePitches = [];
|
|
12013
12271
|
for (let i2 = 0; i2 < 12; i2++) {
|
|
12014
12272
|
const noteInOctave = (i2 - rootOffset + 12) % 12;
|
|
12015
|
-
if (scale.includes(noteInOctave))
|
|
12273
|
+
if (scale.includes(noteInOctave))
|
|
12274
|
+
availablePitches.push(basePitch + semitoneToStep(i2) * upr);
|
|
12016
12275
|
}
|
|
12017
12276
|
core.beginBatch();
|
|
12018
12277
|
for (let bar = 0; bar < numBars; bar++) {
|
|
@@ -12047,14 +12306,17 @@ var applyHarmonicFilter = (targetCore, chordCore, options) => {
|
|
|
12047
12306
|
const isNewBar = halfBar % 2 === 0;
|
|
12048
12307
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12049
12308
|
if (chordHere.length > 0) {
|
|
12050
|
-
currentClasses = new Set(
|
|
12309
|
+
currentClasses = new Set(
|
|
12310
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12311
|
+
);
|
|
12051
12312
|
} else if (isNewBar) {
|
|
12052
12313
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12053
12314
|
}
|
|
12054
12315
|
if (currentClasses.size === 0) continue;
|
|
12055
12316
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12056
12317
|
for (const n of activeHere) {
|
|
12057
|
-
if (!currentClasses.has(n.
|
|
12318
|
+
if (!currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE))
|
|
12319
|
+
targetCore.deleteNoteById(n.id);
|
|
12058
12320
|
}
|
|
12059
12321
|
}
|
|
12060
12322
|
targetCore.endBatch();
|
|
@@ -12076,13 +12338,17 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12076
12338
|
const isNewBar = halfBar % 2 === 0;
|
|
12077
12339
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12078
12340
|
if (chordHere.length > 0) {
|
|
12079
|
-
currentClasses = new Set(
|
|
12341
|
+
currentClasses = new Set(
|
|
12342
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12343
|
+
);
|
|
12080
12344
|
} else if (isNewBar) {
|
|
12081
12345
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12082
12346
|
}
|
|
12083
12347
|
if (currentClasses.size === 0) continue;
|
|
12084
12348
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12085
|
-
const filtered = activeHere.filter(
|
|
12349
|
+
const filtered = activeHere.filter(
|
|
12350
|
+
(n) => currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12351
|
+
);
|
|
12086
12352
|
const filteredIds = new Set(filtered.map((n) => n.id));
|
|
12087
12353
|
for (const n of activeHere) {
|
|
12088
12354
|
if (!filteredIds.has(n.id)) targetCore.deleteNoteById(n.id);
|
|
@@ -12094,7 +12360,7 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12094
12360
|
}
|
|
12095
12361
|
for (const notesAtTime of timeMap.values()) {
|
|
12096
12362
|
if (notesAtTime.length > 1) {
|
|
12097
|
-
notesAtTime.sort((a, b) => b.
|
|
12363
|
+
notesAtTime.sort((a, b) => b.pitchUnits - a.pitchUnits);
|
|
12098
12364
|
const [, ...others] = notesAtTime;
|
|
12099
12365
|
for (const on of others) targetCore.deleteNoteById(on.id);
|
|
12100
12366
|
}
|
|
@@ -12110,18 +12376,21 @@ var shiftNotes = (cores, shiftSteps) => {
|
|
|
12110
12376
|
for (const note of notes) {
|
|
12111
12377
|
const newStart = note.startStep + shiftSteps;
|
|
12112
12378
|
if (newStart < 0) core.deleteNoteById(note.id);
|
|
12113
|
-
else core.moveNote(note.id, newStart, note.
|
|
12379
|
+
else core.moveNote(note.id, newStart, note.pitchUnits);
|
|
12114
12380
|
}
|
|
12115
12381
|
core.saveHistory();
|
|
12116
12382
|
}
|
|
12117
12383
|
};
|
|
12118
|
-
var transposeNotes = (cores,
|
|
12119
|
-
if (
|
|
12384
|
+
var transposeNotes = (cores, steps) => {
|
|
12385
|
+
if (steps === 0) return;
|
|
12120
12386
|
for (const core of cores) {
|
|
12121
12387
|
const notes = [...core.getNotes()];
|
|
12122
12388
|
for (const note of notes) {
|
|
12123
|
-
const newPitch = Math.max(
|
|
12124
|
-
|
|
12389
|
+
const newPitch = Math.max(
|
|
12390
|
+
PITCH_RANGE_START,
|
|
12391
|
+
Math.min(PITCH_RANGE_END, note.pitchUnits + steps)
|
|
12392
|
+
);
|
|
12393
|
+
if (newPitch !== note.pitchUnits) {
|
|
12125
12394
|
core.moveNote(note.id, note.startStep, newPitch);
|
|
12126
12395
|
}
|
|
12127
12396
|
}
|
|
@@ -12446,11 +12715,31 @@ var exportMIDI = (options) => {
|
|
|
12446
12715
|
const div = 480;
|
|
12447
12716
|
const tickPerStep = div / STEPS_PER_BEAT3;
|
|
12448
12717
|
const midiTracks = [];
|
|
12449
|
-
|
|
12718
|
+
const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
|
|
12719
|
+
const BEND_RANGE_SEMITONES = 2;
|
|
12720
|
+
const bendKey = (cents) => Math.round(cents * 100);
|
|
12721
|
+
const bendUse = /* @__PURE__ */ new Map();
|
|
12722
|
+
for (const track of tracks) {
|
|
12723
|
+
for (const n of track.notes) {
|
|
12724
|
+
const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12725
|
+
const k = bendKey(detuneCents);
|
|
12726
|
+
bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
|
|
12727
|
+
}
|
|
12728
|
+
}
|
|
12729
|
+
const bendChannel = /* @__PURE__ */ new Map();
|
|
12730
|
+
const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
|
|
12731
|
+
for (const [k] of ordered) {
|
|
12732
|
+
if (bendChannel.size >= NOTE_CHANNELS.length) break;
|
|
12733
|
+
bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
|
|
12734
|
+
}
|
|
12735
|
+
tracks.forEach((track) => {
|
|
12450
12736
|
if (track.notes.length === 0) return;
|
|
12451
|
-
const channel = ch < 9 ? ch : ch + 1 & 15;
|
|
12452
12737
|
const events = [];
|
|
12453
12738
|
for (const n of track.notes) {
|
|
12739
|
+
const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12740
|
+
const k = bendKey(detuneCents);
|
|
12741
|
+
const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
|
|
12742
|
+
const note = Math.max(0, Math.min(127, midi));
|
|
12454
12743
|
const startTick = Math.round(n.startStep * tickPerStep);
|
|
12455
12744
|
const endTick = Math.round(
|
|
12456
12745
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
@@ -12458,12 +12747,25 @@ var exportMIDI = (options) => {
|
|
|
12458
12747
|
const vel = Math.round(
|
|
12459
12748
|
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
12460
12749
|
);
|
|
12461
|
-
events.push({ t: startTick, m: [144 | channel,
|
|
12462
|
-
events.push({ t: endTick, m: [144 | channel,
|
|
12750
|
+
events.push({ t: startTick, m: [144 | channel, note, vel] });
|
|
12751
|
+
events.push({ t: endTick, m: [144 | channel, note, 0] });
|
|
12463
12752
|
}
|
|
12464
12753
|
events.sort((a, b) => a.t - b.t);
|
|
12465
12754
|
midiTracks.push(events);
|
|
12466
12755
|
});
|
|
12756
|
+
const bendSetup = [];
|
|
12757
|
+
for (const [k, channel] of bendChannel) {
|
|
12758
|
+
const cents = k / 100;
|
|
12759
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
|
|
12760
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
|
|
12761
|
+
bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
|
|
12762
|
+
bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
|
|
12763
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
|
|
12764
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
|
|
12765
|
+
const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
|
|
12766
|
+
const v = Math.max(0, Math.min(16383, raw));
|
|
12767
|
+
bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
|
|
12768
|
+
}
|
|
12467
12769
|
const maxStep = Math.max(
|
|
12468
12770
|
...tracks.filter((t) => t.notes.length > 0).map(
|
|
12469
12771
|
(t) => Math.max(...t.notes.map((n) => n.startStep + n.durationSteps))
|
|
@@ -12500,6 +12802,7 @@ var exportMIDI = (options) => {
|
|
|
12500
12802
|
headerChunks(arr, midiTracks.length + 1, div);
|
|
12501
12803
|
trackChunks(arr, (a) => {
|
|
12502
12804
|
a.push(0, 255, 81, 3, ...to3byte(Math.round(6e7 / bpm)));
|
|
12805
|
+
for (const ev of bendSetup) a.push(0, ...ev.m);
|
|
12503
12806
|
});
|
|
12504
12807
|
for (const events of midiTracks) {
|
|
12505
12808
|
trackChunks(arr, (a) => {
|
|
@@ -12746,380 +13049,8 @@ var LinkedList = class {
|
|
|
12746
13049
|
}
|
|
12747
13050
|
};
|
|
12748
13051
|
|
|
12749
|
-
// src/renderer.ts
|
|
12750
|
-
var g_header_canvas;
|
|
12751
|
-
var g_key_canvas;
|
|
12752
|
-
var g_grid_canvas;
|
|
12753
|
-
var g_header_ctx;
|
|
12754
|
-
var g_key_ctx;
|
|
12755
|
-
var g_grid_ctx;
|
|
12756
|
-
var g_config;
|
|
12757
|
-
var KEYBOARD_WIDTH = 60;
|
|
12758
|
-
var HEADER_HEIGHT = 20;
|
|
12759
|
-
var getRenderConfig = () => g_config;
|
|
12760
|
-
var g_draw_offset_x = 0;
|
|
12761
|
-
var g_draw_offset_y = 0;
|
|
12762
|
-
var g_bg_active = false;
|
|
12763
|
-
var setBackgroundActive = (active) => {
|
|
12764
|
-
g_bg_active = active;
|
|
12765
|
-
};
|
|
12766
|
-
var getDrawOffset = () => ({
|
|
12767
|
-
x: g_draw_offset_x,
|
|
12768
|
-
y: g_draw_offset_y
|
|
12769
|
-
});
|
|
12770
|
-
var getGridCanvas = () => g_grid_canvas;
|
|
12771
|
-
var getGridContext = () => g_grid_ctx;
|
|
12772
|
-
var getHeaderCanvas = () => g_header_canvas;
|
|
12773
|
-
var init = (mountTarget, width = 800, height = 450, config) => {
|
|
12774
|
-
g_config = config;
|
|
12775
|
-
const headerCanvas = document.createElement("canvas");
|
|
12776
|
-
g_header_canvas = headerCanvas;
|
|
12777
|
-
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
12778
|
-
headerCanvas.height = HEADER_HEIGHT;
|
|
12779
|
-
headerCanvas.style.position = "absolute";
|
|
12780
|
-
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
12781
|
-
headerCanvas.style.top = "0px";
|
|
12782
|
-
const headerCtx = headerCanvas.getContext("2d");
|
|
12783
|
-
if (!headerCtx)
|
|
12784
|
-
throw new Error("Failed to get 2D rendering context for header.");
|
|
12785
|
-
g_header_ctx = headerCtx;
|
|
12786
|
-
const keyCanvas = document.createElement("canvas");
|
|
12787
|
-
g_key_canvas = keyCanvas;
|
|
12788
|
-
keyCanvas.width = KEYBOARD_WIDTH;
|
|
12789
|
-
keyCanvas.height = height - HEADER_HEIGHT;
|
|
12790
|
-
keyCanvas.style.position = "absolute";
|
|
12791
|
-
keyCanvas.style.left = "0px";
|
|
12792
|
-
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
12793
|
-
const keyCtx = keyCanvas.getContext("2d");
|
|
12794
|
-
if (!keyCtx)
|
|
12795
|
-
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
12796
|
-
g_key_ctx = keyCtx;
|
|
12797
|
-
const gridCanvas = document.createElement("canvas");
|
|
12798
|
-
g_grid_canvas = gridCanvas;
|
|
12799
|
-
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
12800
|
-
gridCanvas.height = height - HEADER_HEIGHT;
|
|
12801
|
-
gridCanvas.style.position = "absolute";
|
|
12802
|
-
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
12803
|
-
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
12804
|
-
gridCanvas.style.touchAction = "none";
|
|
12805
|
-
gridCanvas.style.userSelect = "none";
|
|
12806
|
-
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
12807
|
-
if (!gridCtx) throw new Error("Failed to get 2D rendering context for grid.");
|
|
12808
|
-
g_grid_ctx = gridCtx;
|
|
12809
|
-
mountTarget.innerHTML = "";
|
|
12810
|
-
mountTarget.style.position = "relative";
|
|
12811
|
-
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
12812
|
-
mountTarget.style.height = `${height}px`;
|
|
12813
|
-
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
12814
|
-
drawHeaderCorner();
|
|
12815
|
-
};
|
|
12816
|
-
var blackKeyPitches = /* @__PURE__ */ new Set([1, 3, 6, 8, 10]);
|
|
12817
|
-
var KEY_NAMES = [
|
|
12818
|
-
"C",
|
|
12819
|
-
"C#",
|
|
12820
|
-
"D",
|
|
12821
|
-
"D#",
|
|
12822
|
-
"E",
|
|
12823
|
-
"F",
|
|
12824
|
-
"F#",
|
|
12825
|
-
"G",
|
|
12826
|
-
"G#",
|
|
12827
|
-
"A",
|
|
12828
|
-
"A#",
|
|
12829
|
-
"B"
|
|
12830
|
-
];
|
|
12831
|
-
var drawHeaderCorner = () => {
|
|
12832
|
-
const mountTarget = g_key_canvas.parentElement;
|
|
12833
|
-
if (!mountTarget) return;
|
|
12834
|
-
let cornerDiv = mountTarget.querySelector("#header-corner");
|
|
12835
|
-
if (!cornerDiv) {
|
|
12836
|
-
cornerDiv = document.createElement("div");
|
|
12837
|
-
cornerDiv.id = "header-corner";
|
|
12838
|
-
cornerDiv.style.position = "absolute";
|
|
12839
|
-
cornerDiv.style.left = "0px";
|
|
12840
|
-
cornerDiv.style.top = "0px";
|
|
12841
|
-
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
12842
|
-
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
12843
|
-
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
12844
|
-
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
12845
|
-
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
12846
|
-
mountTarget.insertBefore(cornerDiv, g_header_canvas);
|
|
12847
|
-
}
|
|
12848
|
-
};
|
|
12849
|
-
var drawKeyboard = () => {
|
|
12850
|
-
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
12851
|
-
const { keyHeight, keyCount, pitchRangeStart } = g_config;
|
|
12852
|
-
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
12853
|
-
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
12854
|
-
const WHITE_KEY = "#ccc8b4";
|
|
12855
|
-
const BLACK_KEY = "#111111";
|
|
12856
|
-
const BK_EDGE = "#383838";
|
|
12857
|
-
const WW_SEP = "#807a6a";
|
|
12858
|
-
const BK_RATIO = 0.62;
|
|
12859
|
-
for (let y = startY; y < endY; y += keyHeight) {
|
|
12860
|
-
const pitchIndex = keyCount - 1 - y / keyHeight;
|
|
12861
|
-
const totalPitch = pitchIndex + pitchRangeStart;
|
|
12862
|
-
const pitchMod12 = totalPitch % 12;
|
|
12863
|
-
const isBlackKey = blackKeyPitches.has(pitchMod12);
|
|
12864
|
-
const octave = Math.floor(totalPitch / 12) - 1;
|
|
12865
|
-
const isC4Range = octave === 4;
|
|
12866
|
-
const screenY = y - g_draw_offset_y;
|
|
12867
|
-
const bkW = Math.floor(KEYBOARD_WIDTH * BK_RATIO);
|
|
12868
|
-
if (isBlackKey) {
|
|
12869
|
-
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
12870
|
-
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
12871
|
-
g_key_ctx.fillStyle = isC4Range ? "#1a1408" : BLACK_KEY;
|
|
12872
|
-
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
12873
|
-
g_key_ctx.strokeStyle = BK_EDGE;
|
|
12874
|
-
g_key_ctx.lineWidth = 1;
|
|
12875
|
-
g_key_ctx.beginPath();
|
|
12876
|
-
g_key_ctx.moveTo(bkW, screenY);
|
|
12877
|
-
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
12878
|
-
g_key_ctx.stroke();
|
|
12879
|
-
} else {
|
|
12880
|
-
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
12881
|
-
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
12882
|
-
if (pitchMod12 === 5 || pitchMod12 === 0) {
|
|
12883
|
-
g_key_ctx.strokeStyle = WW_SEP;
|
|
12884
|
-
g_key_ctx.lineWidth = 1;
|
|
12885
|
-
g_key_ctx.beginPath();
|
|
12886
|
-
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
12887
|
-
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
12888
|
-
g_key_ctx.stroke();
|
|
12889
|
-
}
|
|
12890
|
-
}
|
|
12891
|
-
if (pitchMod12 === 0) {
|
|
12892
|
-
const octave2 = Math.floor(totalPitch / 12) - 1;
|
|
12893
|
-
g_key_ctx.fillStyle = "#555040";
|
|
12894
|
-
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
12895
|
-
g_key_ctx.textAlign = "right";
|
|
12896
|
-
g_key_ctx.textBaseline = "bottom";
|
|
12897
|
-
g_key_ctx.fillText(
|
|
12898
|
-
`${KEY_NAMES[pitchMod12]}${octave2}`,
|
|
12899
|
-
KEYBOARD_WIDTH - 4,
|
|
12900
|
-
screenY + keyHeight - 2
|
|
12901
|
-
);
|
|
12902
|
-
}
|
|
12903
|
-
}
|
|
12904
|
-
g_key_ctx.beginPath();
|
|
12905
|
-
g_key_ctx.strokeStyle = "#29adff";
|
|
12906
|
-
g_key_ctx.lineWidth = 2;
|
|
12907
|
-
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
12908
|
-
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
12909
|
-
g_key_ctx.stroke();
|
|
12910
|
-
};
|
|
12911
|
-
var drawHeader = () => {
|
|
12912
|
-
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
12913
|
-
const { stepWidth, stepsPerBar } = g_config;
|
|
12914
|
-
g_header_ctx.save();
|
|
12915
|
-
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
12916
|
-
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
12917
|
-
g_header_ctx.fillRect(
|
|
12918
|
-
g_draw_offset_x,
|
|
12919
|
-
0,
|
|
12920
|
-
g_header_canvas.width,
|
|
12921
|
-
HEADER_HEIGHT
|
|
12922
|
-
);
|
|
12923
|
-
g_header_ctx.strokeStyle = "#3d405b";
|
|
12924
|
-
g_header_ctx.lineWidth = 1;
|
|
12925
|
-
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
12926
|
-
g_header_ctx.fillStyle = "#83769c";
|
|
12927
|
-
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
12928
|
-
const endBar = Math.ceil(
|
|
12929
|
-
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
12930
|
-
);
|
|
12931
|
-
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
12932
|
-
const x2 = bar * stepsPerBar * stepWidth;
|
|
12933
|
-
const screenX = x2;
|
|
12934
|
-
g_header_ctx.beginPath();
|
|
12935
|
-
g_header_ctx.moveTo(screenX, 0);
|
|
12936
|
-
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
12937
|
-
g_header_ctx.stroke();
|
|
12938
|
-
if (bar >= 0) {
|
|
12939
|
-
g_header_ctx.textAlign = "left";
|
|
12940
|
-
g_header_ctx.textBaseline = "middle";
|
|
12941
|
-
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
12942
|
-
}
|
|
12943
|
-
}
|
|
12944
|
-
g_header_ctx.restore();
|
|
12945
|
-
};
|
|
12946
|
-
var drawGrid = (noteLengthSteps = 1) => {
|
|
12947
|
-
drawKeyboard();
|
|
12948
|
-
drawHeader();
|
|
12949
|
-
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
12950
|
-
const { keyHeight, keyCount, stepWidth, stepsPerBar, pitchRangeStart } = g_config;
|
|
12951
|
-
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
12952
|
-
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
12953
|
-
for (let y = startY; y < endY; y += keyHeight) {
|
|
12954
|
-
const pitchIndex = keyCount - 1 - y / keyHeight;
|
|
12955
|
-
const totalPitch = pitchIndex + pitchRangeStart;
|
|
12956
|
-
const pitchMod12 = totalPitch % 12;
|
|
12957
|
-
const isBlackKey = blackKeyPitches.has(pitchMod12);
|
|
12958
|
-
const isC = pitchMod12 === 0;
|
|
12959
|
-
const octave = Math.floor(totalPitch / 12) - 1;
|
|
12960
|
-
const isC4Range = octave === 4;
|
|
12961
|
-
const screenY = y - g_draw_offset_y;
|
|
12962
|
-
g_grid_ctx.fillStyle = g_bg_active ? isBlackKey ? "rgba(8,11,22,0.55)" : "rgba(17,22,40,0.45)" : isBlackKey ? "#080b16" : "#111628";
|
|
12963
|
-
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
12964
|
-
if (isC4Range) {
|
|
12965
|
-
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
12966
|
-
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
12967
|
-
}
|
|
12968
|
-
g_grid_ctx.beginPath();
|
|
12969
|
-
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
12970
|
-
g_grid_ctx.lineWidth = 1;
|
|
12971
|
-
const lineY = screenY + keyHeight;
|
|
12972
|
-
g_grid_ctx.moveTo(0, lineY);
|
|
12973
|
-
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
12974
|
-
g_grid_ctx.stroke();
|
|
12975
|
-
}
|
|
12976
|
-
const gridStep = noteLengthSteps || 48;
|
|
12977
|
-
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
12978
|
-
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
12979
|
-
const lineStep = stepWidth * gridStep;
|
|
12980
|
-
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
12981
|
-
const step = x2 / stepWidth;
|
|
12982
|
-
const isBarLine = step % stepsPerBar === 0;
|
|
12983
|
-
const isNoteLine = step % gridStep === 0;
|
|
12984
|
-
const screenX = x2 - g_draw_offset_x;
|
|
12985
|
-
g_grid_ctx.beginPath();
|
|
12986
|
-
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
12987
|
-
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
12988
|
-
g_grid_ctx.moveTo(screenX, 0);
|
|
12989
|
-
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
12990
|
-
g_grid_ctx.stroke();
|
|
12991
|
-
}
|
|
12992
|
-
};
|
|
12993
|
-
var drawNotes = (notes, color = [59, 130, 246, 1]) => {
|
|
12994
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
12995
|
-
for (const note of notes) {
|
|
12996
|
-
const logicalX = note.startStep * stepWidth;
|
|
12997
|
-
const yIndex = keyCount - 1 - (note.pitch - pitchRangeStart);
|
|
12998
|
-
const logicalY = yIndex * keyHeight;
|
|
12999
|
-
const w = note.durationSteps * stepWidth;
|
|
13000
|
-
const h = keyHeight;
|
|
13001
|
-
const renderX = logicalX - g_draw_offset_x;
|
|
13002
|
-
const renderY = logicalY - g_draw_offset_y;
|
|
13003
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13004
|
-
const [r, g, b, a] = color;
|
|
13005
|
-
const finalOpacity = a * velocityOpacity;
|
|
13006
|
-
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13007
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13008
|
-
}
|
|
13009
|
-
};
|
|
13010
|
-
var drawNoteLyrics = (notes, syllables) => {
|
|
13011
|
-
if (syllables.length === 0) return;
|
|
13012
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13013
|
-
if (keyHeight < 10) return;
|
|
13014
|
-
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13015
|
-
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13016
|
-
const count = Math.min(sorted.length, syllables.length);
|
|
13017
|
-
g_grid_ctx.save();
|
|
13018
|
-
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13019
|
-
g_grid_ctx.textAlign = "left";
|
|
13020
|
-
g_grid_ctx.textBaseline = "middle";
|
|
13021
|
-
g_grid_ctx.lineWidth = 3;
|
|
13022
|
-
g_grid_ctx.lineJoin = "round";
|
|
13023
|
-
for (let i2 = 0; i2 < count; i2++) {
|
|
13024
|
-
const kana = syllables[i2];
|
|
13025
|
-
if (!kana) continue;
|
|
13026
|
-
const note = sorted[i2];
|
|
13027
|
-
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
13028
|
-
const renderY = (keyCount - 1 - (note.pitch - pitchRangeStart)) * keyHeight - g_draw_offset_y;
|
|
13029
|
-
const w = note.durationSteps * stepWidth;
|
|
13030
|
-
if (w < 6) continue;
|
|
13031
|
-
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
13032
|
-
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
13033
|
-
const textX = renderX + 2;
|
|
13034
|
-
const textY = renderY + keyHeight / 2;
|
|
13035
|
-
g_grid_ctx.save();
|
|
13036
|
-
g_grid_ctx.beginPath();
|
|
13037
|
-
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
13038
|
-
g_grid_ctx.clip();
|
|
13039
|
-
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
13040
|
-
g_grid_ctx.strokeText(kana, textX, textY);
|
|
13041
|
-
g_grid_ctx.fillStyle = "#fff1e8";
|
|
13042
|
-
g_grid_ctx.fillText(kana, textX, textY);
|
|
13043
|
-
g_grid_ctx.restore();
|
|
13044
|
-
}
|
|
13045
|
-
g_grid_ctx.restore();
|
|
13046
|
-
};
|
|
13047
|
-
var drawSelectionRect = (rect) => {
|
|
13048
|
-
if (!rect) return;
|
|
13049
|
-
g_grid_ctx.save();
|
|
13050
|
-
g_grid_ctx.strokeStyle = "#ffec27";
|
|
13051
|
-
g_grid_ctx.lineWidth = 2;
|
|
13052
|
-
g_grid_ctx.setLineDash([4, 4]);
|
|
13053
|
-
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
13054
|
-
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
13055
|
-
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
13056
|
-
g_grid_ctx.restore();
|
|
13057
|
-
};
|
|
13058
|
-
var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
13059
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13060
|
-
for (const note of notes) {
|
|
13061
|
-
if (!selectedIds.has(note.id)) continue;
|
|
13062
|
-
const logicalX = note.startStep * stepWidth;
|
|
13063
|
-
const yIndex = keyCount - 1 - (note.pitch - pitchRangeStart);
|
|
13064
|
-
const logicalY = yIndex * keyHeight;
|
|
13065
|
-
const w = note.durationSteps * stepWidth;
|
|
13066
|
-
const h = keyHeight;
|
|
13067
|
-
const renderX = logicalX - g_draw_offset_x;
|
|
13068
|
-
const renderY = logicalY - g_draw_offset_y;
|
|
13069
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13070
|
-
const [r, g, b, a] = baseColor;
|
|
13071
|
-
const darkenFactor = 1.3;
|
|
13072
|
-
const darkerR = Math.min(255, r * darkenFactor);
|
|
13073
|
-
const darkerG = Math.min(255, g * darkenFactor);
|
|
13074
|
-
const darkerB = Math.min(255, b * darkenFactor);
|
|
13075
|
-
const finalOpacity = a * velocityOpacity;
|
|
13076
|
-
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
13077
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13078
|
-
}
|
|
13079
|
-
};
|
|
13080
|
-
var getXY = (e) => {
|
|
13081
|
-
const { clientX, clientY } = e;
|
|
13082
|
-
const rect = g_grid_canvas.getBoundingClientRect();
|
|
13083
|
-
const x2 = Math.floor(clientX - rect.left);
|
|
13084
|
-
const y = Math.floor(clientY - rect.top);
|
|
13085
|
-
return [x2, y, e.buttons];
|
|
13086
|
-
};
|
|
13087
|
-
var getGridPosition = (e) => {
|
|
13088
|
-
const [x2, y] = getXY(e);
|
|
13089
|
-
const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
|
|
13090
|
-
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13091
|
-
const absoluteY = y + g_draw_offset_y;
|
|
13092
|
-
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13093
|
-
const pitch = keyCount - 1 - yIndex + pitchRangeStart;
|
|
13094
|
-
return { step, pitch, x: x2, y };
|
|
13095
|
-
};
|
|
13096
|
-
var onClick = (callback) => {
|
|
13097
|
-
g_grid_canvas.addEventListener(
|
|
13098
|
-
"click",
|
|
13099
|
-
(e) => {
|
|
13100
|
-
const [x2, y] = getXY(e);
|
|
13101
|
-
const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
|
|
13102
|
-
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13103
|
-
const absoluteY = y + g_draw_offset_y;
|
|
13104
|
-
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13105
|
-
const pitch = keyCount - 1 - yIndex + pitchRangeStart;
|
|
13106
|
-
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
13107
|
-
requestAnimationFrame(() => callback(step, pitch));
|
|
13108
|
-
}
|
|
13109
|
-
},
|
|
13110
|
-
{ passive: true }
|
|
13111
|
-
);
|
|
13112
|
-
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
13113
|
-
};
|
|
13114
|
-
var setDrawOffset = (x2, y) => {
|
|
13115
|
-
g_draw_offset_x = x2;
|
|
13116
|
-
g_draw_offset_y = y;
|
|
13117
|
-
drawKeyboard();
|
|
13118
|
-
drawHeader();
|
|
13119
|
-
};
|
|
13120
|
-
|
|
13121
13052
|
// src/mml-core.ts
|
|
13122
|
-
var
|
|
13053
|
+
var PITCH_MAP = [
|
|
13123
13054
|
"c",
|
|
13124
13055
|
"c+",
|
|
13125
13056
|
"d",
|
|
@@ -13133,6 +13064,39 @@ var PITCH_MAP2 = [
|
|
|
13133
13064
|
"a+",
|
|
13134
13065
|
"b"
|
|
13135
13066
|
];
|
|
13067
|
+
var EDO31_NAMES = [
|
|
13068
|
+
"c",
|
|
13069
|
+
"c+",
|
|
13070
|
+
"c#",
|
|
13071
|
+
"d-",
|
|
13072
|
+
"d_",
|
|
13073
|
+
"d",
|
|
13074
|
+
"d+",
|
|
13075
|
+
"d#",
|
|
13076
|
+
"e-",
|
|
13077
|
+
"e_",
|
|
13078
|
+
"e",
|
|
13079
|
+
"f-",
|
|
13080
|
+
"e#",
|
|
13081
|
+
"f",
|
|
13082
|
+
"f+",
|
|
13083
|
+
"f#",
|
|
13084
|
+
"g-",
|
|
13085
|
+
"g_",
|
|
13086
|
+
"g",
|
|
13087
|
+
"g+",
|
|
13088
|
+
"g#",
|
|
13089
|
+
"a-",
|
|
13090
|
+
"a_",
|
|
13091
|
+
"a",
|
|
13092
|
+
"a+",
|
|
13093
|
+
"a#",
|
|
13094
|
+
"b-",
|
|
13095
|
+
"b_",
|
|
13096
|
+
"b",
|
|
13097
|
+
"b+",
|
|
13098
|
+
"b#"
|
|
13099
|
+
];
|
|
13136
13100
|
var MMLCore = class _MMLCore {
|
|
13137
13101
|
notes = [];
|
|
13138
13102
|
nextNoteId = 0;
|
|
@@ -13146,9 +13110,12 @@ var MMLCore = class _MMLCore {
|
|
|
13146
13110
|
lastUndoTime = 0;
|
|
13147
13111
|
static UNDO_DEBOUNCE_MS = 100;
|
|
13148
13112
|
toolMode = "pen";
|
|
13149
|
-
|
|
13113
|
+
/** グリッド寸法の供給元(描画器ではなく呼び出し側が持つ設定を読む) */
|
|
13114
|
+
getConfig;
|
|
13115
|
+
constructor(handlers, volume = 80, getConfig) {
|
|
13150
13116
|
this.handlers = handlers;
|
|
13151
13117
|
this.volume = volume;
|
|
13118
|
+
this.getConfig = getConfig;
|
|
13152
13119
|
this.lastHistorySnapshot = JSON.stringify(this.notes);
|
|
13153
13120
|
this.history.add([]);
|
|
13154
13121
|
this.generateAndNotify();
|
|
@@ -13235,14 +13202,14 @@ var MMLCore = class _MMLCore {
|
|
|
13235
13202
|
*/
|
|
13236
13203
|
addNote(step, pitch, options) {
|
|
13237
13204
|
const existingIndex = this.notes.findIndex(
|
|
13238
|
-
(n) => n.startStep === step && n.
|
|
13205
|
+
(n) => n.startStep === step && n.pitchUnits === pitch
|
|
13239
13206
|
);
|
|
13240
13207
|
if (existingIndex === -1) {
|
|
13241
13208
|
const newNote = {
|
|
13242
13209
|
id: this.nextNoteId++,
|
|
13243
13210
|
startStep: step,
|
|
13244
13211
|
durationSteps: options.noteLengthSteps,
|
|
13245
|
-
pitch,
|
|
13212
|
+
pitchUnits: pitch,
|
|
13246
13213
|
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
13247
13214
|
};
|
|
13248
13215
|
this.notes.push(newNote);
|
|
@@ -13270,9 +13237,11 @@ var MMLCore = class _MMLCore {
|
|
|
13270
13237
|
moveNote(noteId, startStep, pitch) {
|
|
13271
13238
|
const note = this.notes.find((target) => target.id === noteId);
|
|
13272
13239
|
if (!note) return;
|
|
13273
|
-
const totalSteps = this.getMaxStep() +
|
|
13274
|
-
const
|
|
13275
|
-
const
|
|
13240
|
+
const totalSteps = this.getMaxStep() + this.getConfig().stepsPerBar;
|
|
13241
|
+
const cfg = this.getConfig();
|
|
13242
|
+
const upr = cfg.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
13243
|
+
const pitchRangeStart = cfg.pitchRangeStart;
|
|
13244
|
+
const pitchRangeEnd = pitchRangeStart + (cfg.keyCount - 1) * upr;
|
|
13276
13245
|
const clampedPitch = Math.min(
|
|
13277
13246
|
Math.max(pitch, pitchRangeStart),
|
|
13278
13247
|
pitchRangeEnd
|
|
@@ -13282,7 +13251,7 @@ var MMLCore = class _MMLCore {
|
|
|
13282
13251
|
totalSteps - note.durationSteps
|
|
13283
13252
|
);
|
|
13284
13253
|
note.startStep = clampedStart;
|
|
13285
|
-
note.
|
|
13254
|
+
note.pitchUnits = clampedPitch;
|
|
13286
13255
|
this.notes.sort((a, b) => a.startStep - b.startStep);
|
|
13287
13256
|
this.generateAndNotify();
|
|
13288
13257
|
}
|
|
@@ -13327,7 +13296,7 @@ var MMLCore = class _MMLCore {
|
|
|
13327
13296
|
* ただし、残りステップ(limit)は絶対に超えない。
|
|
13328
13297
|
*/
|
|
13329
13298
|
stepsToMMLDuration(steps, limit) {
|
|
13330
|
-
const config =
|
|
13299
|
+
const config = this.getConfig();
|
|
13331
13300
|
const total = config.stepsPerBar;
|
|
13332
13301
|
const candidates = [
|
|
13333
13302
|
{ dur: "1.", s: total * 1.5 },
|
|
@@ -13363,7 +13332,7 @@ var MMLCore = class _MMLCore {
|
|
|
13363
13332
|
* ギャップに収まる最大の音符を探す(減算アルゴリズム用)
|
|
13364
13333
|
*/
|
|
13365
13334
|
findBestFitDuration(gap) {
|
|
13366
|
-
const config =
|
|
13335
|
+
const config = this.getConfig();
|
|
13367
13336
|
const durations = [1, 2, 4, 8, 12, 16, 24, 32, 48, 64];
|
|
13368
13337
|
for (const d of durations) {
|
|
13369
13338
|
const stepLen = config.stepsPerBar / d;
|
|
@@ -13373,12 +13342,20 @@ var MMLCore = class _MMLCore {
|
|
|
13373
13342
|
}
|
|
13374
13343
|
return { dur: 64, steps: config.stepsPerBar / 64 };
|
|
13375
13344
|
}
|
|
13345
|
+
/** ピッチ(units) → その音律での「オクターブ番号」と「音名」。 */
|
|
13346
|
+
spell(pitchUnits) {
|
|
13347
|
+
const edo = this.getConfig().edo ?? 12;
|
|
13348
|
+
const names = edo === 31 ? EDO31_NAMES : PITCH_MAP;
|
|
13349
|
+
const octave = Math.floor(pitchUnits / 372) - 1;
|
|
13350
|
+
const within = (pitchUnits % 372 + 372) % 372;
|
|
13351
|
+
const step = Math.round(within / (372 / edo));
|
|
13352
|
+
return { octave, name: names[step % edo] };
|
|
13353
|
+
}
|
|
13376
13354
|
/**
|
|
13377
13355
|
* ピッチからオクターブ最適化のある音名を取得
|
|
13378
13356
|
*/
|
|
13379
13357
|
getNoteWithOctave(pitch, lastOctave) {
|
|
13380
|
-
const octave =
|
|
13381
|
-
const name = PITCH_MAP2[pitch % 12];
|
|
13358
|
+
const { octave, name } = this.spell(pitch);
|
|
13382
13359
|
if (lastOctave === -1 || Math.abs(octave - lastOctave) >= 2) {
|
|
13383
13360
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
13384
13361
|
}
|
|
@@ -13401,7 +13378,7 @@ var MMLCore = class _MMLCore {
|
|
|
13401
13378
|
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
13402
13379
|
*/
|
|
13403
13380
|
generateMML = (volumeOverride) => {
|
|
13404
|
-
const config =
|
|
13381
|
+
const config = this.getConfig();
|
|
13405
13382
|
const vol = volumeOverride ?? this.volume;
|
|
13406
13383
|
const header = `t${this.tempo} v${vol}`;
|
|
13407
13384
|
const segments = [];
|
|
@@ -13442,14 +13419,13 @@ var MMLCore = class _MMLCore {
|
|
|
13442
13419
|
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
13443
13420
|
if (notes.length > 1) {
|
|
13444
13421
|
const noteStrs = notes.map((n) => {
|
|
13445
|
-
const oct =
|
|
13446
|
-
const name = PITCH_MAP2[n.pitch % 12];
|
|
13422
|
+
const { octave: oct, name } = this.spell(n.pitchUnits);
|
|
13447
13423
|
return `o${oct}${name}`;
|
|
13448
13424
|
});
|
|
13449
13425
|
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
13450
13426
|
} else {
|
|
13451
13427
|
const { text, currentOctave } = this.getNoteWithOctave(
|
|
13452
|
-
notes[0].
|
|
13428
|
+
notes[0].pitchUnits,
|
|
13453
13429
|
lastOctave
|
|
13454
13430
|
);
|
|
13455
13431
|
segments.push(`${text}${durStr}`);
|
|
@@ -13480,7 +13456,7 @@ var MMLCore = class _MMLCore {
|
|
|
13480
13456
|
* MMLの音長文字列("4", "4.", "12"など)をステップ数に変換する
|
|
13481
13457
|
*/
|
|
13482
13458
|
getStepFromDottedMML(durStr) {
|
|
13483
|
-
const config =
|
|
13459
|
+
const config = this.getConfig();
|
|
13484
13460
|
const total = config.stepsPerBar;
|
|
13485
13461
|
const isDotted = durStr.endsWith(".");
|
|
13486
13462
|
const baseDur = parseInt(isDotted ? durStr.slice(0, -1) : durStr, 10);
|
|
@@ -13490,7 +13466,7 @@ var MMLCore = class _MMLCore {
|
|
|
13490
13466
|
};
|
|
13491
13467
|
var decomposeToMonophonic = (notes) => {
|
|
13492
13468
|
const sorted = [...notes].sort(
|
|
13493
|
-
(a, b) => a.startStep - b.startStep || a.
|
|
13469
|
+
(a, b) => a.startStep - b.startStep || a.pitchUnits - b.pitchUnits
|
|
13494
13470
|
);
|
|
13495
13471
|
const tracks = [];
|
|
13496
13472
|
const trackEnds = [];
|
|
@@ -13525,6 +13501,493 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
|
|
|
13525
13501
|
return chordNotes / notes.length >= threshold;
|
|
13526
13502
|
};
|
|
13527
13503
|
|
|
13504
|
+
// src/renderer.ts
|
|
13505
|
+
var KEYBOARD_WIDTH = 60;
|
|
13506
|
+
var HEADER_HEIGHT = 20;
|
|
13507
|
+
var NATURAL_STEPS3 = {
|
|
13508
|
+
12: [0, 2, 4, 5, 7, 9, 11],
|
|
13509
|
+
31: [0, 5, 10, 13, 18, 23, 28]
|
|
13510
|
+
};
|
|
13511
|
+
var keyTier = (step, edo) => {
|
|
13512
|
+
const naturals = NATURAL_STEPS3[edo] ?? NATURAL_STEPS3[12];
|
|
13513
|
+
let best = edo;
|
|
13514
|
+
for (const n of naturals) {
|
|
13515
|
+
const d = Math.abs(step - n);
|
|
13516
|
+
best = Math.min(best, d, edo - d);
|
|
13517
|
+
}
|
|
13518
|
+
if (best === 0) return 0;
|
|
13519
|
+
return best >= (edo === 31 ? 2 : 1) ? 2 : 1;
|
|
13520
|
+
};
|
|
13521
|
+
var KEY_NAMES = [
|
|
13522
|
+
"C",
|
|
13523
|
+
"C#",
|
|
13524
|
+
"D",
|
|
13525
|
+
"D#",
|
|
13526
|
+
"E",
|
|
13527
|
+
"F",
|
|
13528
|
+
"F#",
|
|
13529
|
+
"G",
|
|
13530
|
+
"G#",
|
|
13531
|
+
"A",
|
|
13532
|
+
"A#",
|
|
13533
|
+
"B"
|
|
13534
|
+
];
|
|
13535
|
+
var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
13536
|
+
let g_header_canvas;
|
|
13537
|
+
let g_key_canvas;
|
|
13538
|
+
let g_grid_canvas;
|
|
13539
|
+
let g_header_ctx;
|
|
13540
|
+
let g_key_ctx;
|
|
13541
|
+
let g_grid_ctx;
|
|
13542
|
+
let g_config = config;
|
|
13543
|
+
const getRenderConfig = () => g_config;
|
|
13544
|
+
let g_draw_offset_x = 0;
|
|
13545
|
+
let g_draw_offset_y = 0;
|
|
13546
|
+
let g_bg_active = false;
|
|
13547
|
+
const setBackgroundActive = (active) => {
|
|
13548
|
+
g_bg_active = active;
|
|
13549
|
+
};
|
|
13550
|
+
const getDrawOffset = () => ({
|
|
13551
|
+
x: g_draw_offset_x,
|
|
13552
|
+
y: g_draw_offset_y
|
|
13553
|
+
});
|
|
13554
|
+
const getGridCanvas = () => g_grid_canvas;
|
|
13555
|
+
const getGridContext = () => g_grid_ctx;
|
|
13556
|
+
const getHeaderCanvas = () => g_header_canvas;
|
|
13557
|
+
const setup = () => {
|
|
13558
|
+
g_config = config;
|
|
13559
|
+
const headerCanvas = document.createElement("canvas");
|
|
13560
|
+
g_header_canvas = headerCanvas;
|
|
13561
|
+
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
13562
|
+
headerCanvas.height = HEADER_HEIGHT;
|
|
13563
|
+
headerCanvas.style.position = "absolute";
|
|
13564
|
+
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13565
|
+
headerCanvas.style.top = "0px";
|
|
13566
|
+
const headerCtx = headerCanvas.getContext("2d");
|
|
13567
|
+
if (!headerCtx)
|
|
13568
|
+
throw new Error("Failed to get 2D rendering context for header.");
|
|
13569
|
+
g_header_ctx = headerCtx;
|
|
13570
|
+
const keyCanvas = document.createElement("canvas");
|
|
13571
|
+
g_key_canvas = keyCanvas;
|
|
13572
|
+
keyCanvas.width = KEYBOARD_WIDTH;
|
|
13573
|
+
keyCanvas.height = height - HEADER_HEIGHT;
|
|
13574
|
+
keyCanvas.style.position = "absolute";
|
|
13575
|
+
keyCanvas.style.left = "0px";
|
|
13576
|
+
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13577
|
+
const keyCtx = keyCanvas.getContext("2d");
|
|
13578
|
+
if (!keyCtx)
|
|
13579
|
+
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
13580
|
+
g_key_ctx = keyCtx;
|
|
13581
|
+
const gridCanvas = document.createElement("canvas");
|
|
13582
|
+
g_grid_canvas = gridCanvas;
|
|
13583
|
+
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
13584
|
+
gridCanvas.height = height - HEADER_HEIGHT;
|
|
13585
|
+
gridCanvas.style.position = "absolute";
|
|
13586
|
+
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13587
|
+
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13588
|
+
gridCanvas.style.touchAction = "none";
|
|
13589
|
+
gridCanvas.style.userSelect = "none";
|
|
13590
|
+
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
13591
|
+
if (!gridCtx)
|
|
13592
|
+
throw new Error("Failed to get 2D rendering context for grid.");
|
|
13593
|
+
g_grid_ctx = gridCtx;
|
|
13594
|
+
mountTarget.innerHTML = "";
|
|
13595
|
+
mountTarget.style.position = "relative";
|
|
13596
|
+
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
13597
|
+
mountTarget.style.height = `${height}px`;
|
|
13598
|
+
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
13599
|
+
drawHeaderCorner();
|
|
13600
|
+
};
|
|
13601
|
+
const drawHeaderCorner = () => {
|
|
13602
|
+
const mountTarget2 = g_key_canvas.parentElement;
|
|
13603
|
+
if (!mountTarget2) return;
|
|
13604
|
+
let cornerDiv = mountTarget2.querySelector(
|
|
13605
|
+
"#header-corner"
|
|
13606
|
+
);
|
|
13607
|
+
if (!cornerDiv) {
|
|
13608
|
+
cornerDiv = document.createElement("div");
|
|
13609
|
+
cornerDiv.id = "header-corner";
|
|
13610
|
+
cornerDiv.style.position = "absolute";
|
|
13611
|
+
cornerDiv.style.left = "0px";
|
|
13612
|
+
cornerDiv.style.top = "0px";
|
|
13613
|
+
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
13614
|
+
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
13615
|
+
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
13616
|
+
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
13617
|
+
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
13618
|
+
mountTarget2.insertBefore(cornerDiv, g_header_canvas);
|
|
13619
|
+
}
|
|
13620
|
+
};
|
|
13621
|
+
const drawKeyboard = () => {
|
|
13622
|
+
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
13623
|
+
const {
|
|
13624
|
+
keyHeight,
|
|
13625
|
+
keyCount,
|
|
13626
|
+
pitchRangeStart,
|
|
13627
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13628
|
+
edo = 12
|
|
13629
|
+
} = g_config;
|
|
13630
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13631
|
+
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
13632
|
+
const WHITE_KEY = "#ccc8b4";
|
|
13633
|
+
const BLACK_KEY = "#111111";
|
|
13634
|
+
const MICRO_KEY = "#4a4a4a";
|
|
13635
|
+
const BK_EDGE = "#383838";
|
|
13636
|
+
const WW_SEP = "#807a6a";
|
|
13637
|
+
const BK_RATIO = 0.62;
|
|
13638
|
+
const MICRO_RATIO = 0.45;
|
|
13639
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13640
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13641
|
+
const units = pitchRangeStart + rowIndex * upr;
|
|
13642
|
+
const step = Math.round(
|
|
13643
|
+
(units % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13644
|
+
);
|
|
13645
|
+
const tier = keyTier(step, edo);
|
|
13646
|
+
const octave = Math.floor(units / UNITS_PER_OCTAVE) - 1;
|
|
13647
|
+
const isC4Range = octave === 4;
|
|
13648
|
+
const screenY = y - g_draw_offset_y;
|
|
13649
|
+
const bkW = Math.floor(
|
|
13650
|
+
KEYBOARD_WIDTH * (tier === 1 ? MICRO_RATIO : BK_RATIO)
|
|
13651
|
+
);
|
|
13652
|
+
if (tier !== 0) {
|
|
13653
|
+
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
13654
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13655
|
+
g_key_ctx.fillStyle = tier === 1 ? MICRO_KEY : isC4Range ? "#1a1408" : BLACK_KEY;
|
|
13656
|
+
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
13657
|
+
g_key_ctx.strokeStyle = BK_EDGE;
|
|
13658
|
+
g_key_ctx.lineWidth = 1;
|
|
13659
|
+
g_key_ctx.beginPath();
|
|
13660
|
+
g_key_ctx.moveTo(bkW, screenY);
|
|
13661
|
+
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
13662
|
+
g_key_ctx.stroke();
|
|
13663
|
+
} else {
|
|
13664
|
+
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
13665
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13666
|
+
if (edo === 12 && (step === 5 || step === 0)) {
|
|
13667
|
+
g_key_ctx.strokeStyle = WW_SEP;
|
|
13668
|
+
g_key_ctx.lineWidth = 1;
|
|
13669
|
+
g_key_ctx.beginPath();
|
|
13670
|
+
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
13671
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
13672
|
+
g_key_ctx.stroke();
|
|
13673
|
+
}
|
|
13674
|
+
}
|
|
13675
|
+
if (step === 0) {
|
|
13676
|
+
g_key_ctx.fillStyle = "#555040";
|
|
13677
|
+
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
13678
|
+
g_key_ctx.textAlign = "right";
|
|
13679
|
+
g_key_ctx.textBaseline = "bottom";
|
|
13680
|
+
g_key_ctx.fillText(
|
|
13681
|
+
`${KEY_NAMES[0]}${octave}`,
|
|
13682
|
+
KEYBOARD_WIDTH - 4,
|
|
13683
|
+
screenY + keyHeight - 2
|
|
13684
|
+
);
|
|
13685
|
+
}
|
|
13686
|
+
}
|
|
13687
|
+
g_key_ctx.beginPath();
|
|
13688
|
+
g_key_ctx.strokeStyle = "#29adff";
|
|
13689
|
+
g_key_ctx.lineWidth = 2;
|
|
13690
|
+
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
13691
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
13692
|
+
g_key_ctx.stroke();
|
|
13693
|
+
};
|
|
13694
|
+
const drawHeader = () => {
|
|
13695
|
+
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
13696
|
+
const { stepWidth, stepsPerBar } = g_config;
|
|
13697
|
+
g_header_ctx.save();
|
|
13698
|
+
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
13699
|
+
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
13700
|
+
g_header_ctx.fillRect(
|
|
13701
|
+
g_draw_offset_x,
|
|
13702
|
+
0,
|
|
13703
|
+
g_header_canvas.width,
|
|
13704
|
+
HEADER_HEIGHT
|
|
13705
|
+
);
|
|
13706
|
+
g_header_ctx.strokeStyle = "#3d405b";
|
|
13707
|
+
g_header_ctx.lineWidth = 1;
|
|
13708
|
+
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
13709
|
+
g_header_ctx.fillStyle = "#83769c";
|
|
13710
|
+
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
13711
|
+
const endBar = Math.ceil(
|
|
13712
|
+
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13713
|
+
);
|
|
13714
|
+
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13715
|
+
const x2 = bar * stepsPerBar * stepWidth;
|
|
13716
|
+
const screenX = x2;
|
|
13717
|
+
g_header_ctx.beginPath();
|
|
13718
|
+
g_header_ctx.moveTo(screenX, 0);
|
|
13719
|
+
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13720
|
+
g_header_ctx.stroke();
|
|
13721
|
+
if (bar >= 0) {
|
|
13722
|
+
g_header_ctx.textAlign = "left";
|
|
13723
|
+
g_header_ctx.textBaseline = "middle";
|
|
13724
|
+
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13725
|
+
}
|
|
13726
|
+
}
|
|
13727
|
+
g_header_ctx.restore();
|
|
13728
|
+
};
|
|
13729
|
+
const drawGrid = (noteLengthSteps = 1) => {
|
|
13730
|
+
drawKeyboard();
|
|
13731
|
+
drawHeader();
|
|
13732
|
+
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13733
|
+
const {
|
|
13734
|
+
keyHeight,
|
|
13735
|
+
keyCount,
|
|
13736
|
+
stepWidth,
|
|
13737
|
+
stepsPerBar,
|
|
13738
|
+
pitchRangeStart,
|
|
13739
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13740
|
+
edo = 12
|
|
13741
|
+
} = g_config;
|
|
13742
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13743
|
+
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13744
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13745
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13746
|
+
const units = pitchRangeStart + rowIndex * upr;
|
|
13747
|
+
const step = Math.round(
|
|
13748
|
+
(units % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13749
|
+
);
|
|
13750
|
+
const tier = keyTier(step, edo);
|
|
13751
|
+
const isC = step === 0;
|
|
13752
|
+
const octave = Math.floor(units / UNITS_PER_OCTAVE) - 1;
|
|
13753
|
+
const isC4Range = octave === 4;
|
|
13754
|
+
const screenY = y - g_draw_offset_y;
|
|
13755
|
+
g_grid_ctx.fillStyle = g_bg_active ? tier === 2 ? "rgba(8,11,22,0.55)" : tier === 1 ? "rgba(12,16,30,0.5)" : "rgba(17,22,40,0.45)" : tier === 2 ? "#080b16" : tier === 1 ? "#0c101d" : "#111628";
|
|
13756
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13757
|
+
if (isC4Range) {
|
|
13758
|
+
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13759
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13760
|
+
}
|
|
13761
|
+
g_grid_ctx.beginPath();
|
|
13762
|
+
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13763
|
+
g_grid_ctx.lineWidth = 1;
|
|
13764
|
+
const lineY = screenY + keyHeight;
|
|
13765
|
+
g_grid_ctx.moveTo(0, lineY);
|
|
13766
|
+
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13767
|
+
g_grid_ctx.stroke();
|
|
13768
|
+
}
|
|
13769
|
+
const gridStep = noteLengthSteps || 48;
|
|
13770
|
+
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13771
|
+
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13772
|
+
const lineStep = stepWidth * gridStep;
|
|
13773
|
+
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13774
|
+
const step = x2 / stepWidth;
|
|
13775
|
+
const isBarLine = step % stepsPerBar === 0;
|
|
13776
|
+
const isNoteLine = step % gridStep === 0;
|
|
13777
|
+
const screenX = x2 - g_draw_offset_x;
|
|
13778
|
+
g_grid_ctx.beginPath();
|
|
13779
|
+
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13780
|
+
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13781
|
+
g_grid_ctx.moveTo(screenX, 0);
|
|
13782
|
+
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13783
|
+
g_grid_ctx.stroke();
|
|
13784
|
+
}
|
|
13785
|
+
};
|
|
13786
|
+
const drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13787
|
+
const {
|
|
13788
|
+
keyHeight,
|
|
13789
|
+
stepWidth,
|
|
13790
|
+
keyCount,
|
|
13791
|
+
pitchRangeStart,
|
|
13792
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13793
|
+
} = g_config;
|
|
13794
|
+
const canvasWidth = g_grid_canvas.width;
|
|
13795
|
+
const canvasHeight = g_grid_canvas.height;
|
|
13796
|
+
const [r, g, b, a] = color;
|
|
13797
|
+
for (const note of notes) {
|
|
13798
|
+
const logicalX = note.startStep * stepWidth;
|
|
13799
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13800
|
+
const logicalY = yIndex * keyHeight;
|
|
13801
|
+
const w = note.durationSteps * stepWidth;
|
|
13802
|
+
const h = keyHeight;
|
|
13803
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13804
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13805
|
+
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13806
|
+
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13807
|
+
if (isActive) {
|
|
13808
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13809
|
+
const finalOpacity = a * velocityOpacity;
|
|
13810
|
+
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13811
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13812
|
+
if (w >= 4 && h >= 4) {
|
|
13813
|
+
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13814
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13815
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13816
|
+
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13817
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13818
|
+
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13819
|
+
}
|
|
13820
|
+
} else {
|
|
13821
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13822
|
+
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13823
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13824
|
+
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13825
|
+
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13826
|
+
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13827
|
+
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13828
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
};
|
|
13832
|
+
const drawNoteLyrics = (notes, syllables) => {
|
|
13833
|
+
if (syllables.length === 0) return;
|
|
13834
|
+
const {
|
|
13835
|
+
keyHeight,
|
|
13836
|
+
stepWidth,
|
|
13837
|
+
keyCount,
|
|
13838
|
+
pitchRangeStart,
|
|
13839
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13840
|
+
} = g_config;
|
|
13841
|
+
if (keyHeight < 7) return;
|
|
13842
|
+
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13843
|
+
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13844
|
+
const count = Math.min(sorted.length, syllables.length);
|
|
13845
|
+
g_grid_ctx.save();
|
|
13846
|
+
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13847
|
+
g_grid_ctx.textAlign = "left";
|
|
13848
|
+
g_grid_ctx.textBaseline = "middle";
|
|
13849
|
+
g_grid_ctx.lineWidth = 3;
|
|
13850
|
+
g_grid_ctx.lineJoin = "round";
|
|
13851
|
+
for (let i2 = 0; i2 < count; i2++) {
|
|
13852
|
+
const kana = syllables[i2];
|
|
13853
|
+
if (!kana) continue;
|
|
13854
|
+
const note = sorted[i2];
|
|
13855
|
+
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
13856
|
+
const renderY = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
|
|
13857
|
+
const w = note.durationSteps * stepWidth;
|
|
13858
|
+
if (w < 6) continue;
|
|
13859
|
+
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
13860
|
+
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
13861
|
+
const textX = renderX + 2;
|
|
13862
|
+
const textY = renderY + keyHeight / 2;
|
|
13863
|
+
g_grid_ctx.save();
|
|
13864
|
+
g_grid_ctx.beginPath();
|
|
13865
|
+
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
13866
|
+
g_grid_ctx.clip();
|
|
13867
|
+
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
13868
|
+
g_grid_ctx.strokeText(kana, textX, textY);
|
|
13869
|
+
g_grid_ctx.fillStyle = "#fff1e8";
|
|
13870
|
+
g_grid_ctx.fillText(kana, textX, textY);
|
|
13871
|
+
g_grid_ctx.restore();
|
|
13872
|
+
}
|
|
13873
|
+
g_grid_ctx.restore();
|
|
13874
|
+
};
|
|
13875
|
+
const drawSelectionRect = (rect) => {
|
|
13876
|
+
if (!rect) return;
|
|
13877
|
+
g_grid_ctx.save();
|
|
13878
|
+
g_grid_ctx.strokeStyle = "#ffec27";
|
|
13879
|
+
g_grid_ctx.lineWidth = 2;
|
|
13880
|
+
g_grid_ctx.setLineDash([4, 4]);
|
|
13881
|
+
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
13882
|
+
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
13883
|
+
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
13884
|
+
g_grid_ctx.restore();
|
|
13885
|
+
};
|
|
13886
|
+
const drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
13887
|
+
const {
|
|
13888
|
+
keyHeight,
|
|
13889
|
+
stepWidth,
|
|
13890
|
+
keyCount,
|
|
13891
|
+
pitchRangeStart,
|
|
13892
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13893
|
+
} = g_config;
|
|
13894
|
+
for (const note of notes) {
|
|
13895
|
+
if (!selectedIds.has(note.id)) continue;
|
|
13896
|
+
const logicalX = note.startStep * stepWidth;
|
|
13897
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13898
|
+
const logicalY = yIndex * keyHeight;
|
|
13899
|
+
const w = note.durationSteps * stepWidth;
|
|
13900
|
+
const h = keyHeight;
|
|
13901
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13902
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13903
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13904
|
+
const [r, g, b, a] = baseColor;
|
|
13905
|
+
const darkenFactor = 1.3;
|
|
13906
|
+
const darkerR = Math.min(255, r * darkenFactor);
|
|
13907
|
+
const darkerG = Math.min(255, g * darkenFactor);
|
|
13908
|
+
const darkerB = Math.min(255, b * darkenFactor);
|
|
13909
|
+
const finalOpacity = a * velocityOpacity;
|
|
13910
|
+
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
13911
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13912
|
+
}
|
|
13913
|
+
};
|
|
13914
|
+
const getXY = (e) => {
|
|
13915
|
+
const { clientX, clientY } = e;
|
|
13916
|
+
const rect = g_grid_canvas.getBoundingClientRect();
|
|
13917
|
+
const x2 = Math.floor(clientX - rect.left);
|
|
13918
|
+
const y = Math.floor(clientY - rect.top);
|
|
13919
|
+
return [x2, y, e.buttons];
|
|
13920
|
+
};
|
|
13921
|
+
const getGridPosition = (e) => {
|
|
13922
|
+
const [x2, y] = getXY(e);
|
|
13923
|
+
const {
|
|
13924
|
+
keyCount,
|
|
13925
|
+
pitchRangeStart,
|
|
13926
|
+
keyHeight,
|
|
13927
|
+
stepWidth,
|
|
13928
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13929
|
+
} = g_config;
|
|
13930
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13931
|
+
const absoluteY = y + g_draw_offset_y;
|
|
13932
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13933
|
+
const pitch = pitchRangeStart + (keyCount - 1 - yIndex) * upr;
|
|
13934
|
+
return { step, pitch, x: x2, y };
|
|
13935
|
+
};
|
|
13936
|
+
const onClick = (callback) => {
|
|
13937
|
+
g_grid_canvas.addEventListener(
|
|
13938
|
+
"click",
|
|
13939
|
+
(e) => {
|
|
13940
|
+
const [x2, y] = getXY(e);
|
|
13941
|
+
const {
|
|
13942
|
+
keyCount,
|
|
13943
|
+
pitchRangeStart,
|
|
13944
|
+
keyHeight,
|
|
13945
|
+
stepWidth,
|
|
13946
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13947
|
+
} = g_config;
|
|
13948
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13949
|
+
const absoluteY = y + g_draw_offset_y;
|
|
13950
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13951
|
+
const pitch = pitchRangeStart + (keyCount - 1 - yIndex) * upr;
|
|
13952
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
13953
|
+
requestAnimationFrame(() => callback(step, pitch));
|
|
13954
|
+
}
|
|
13955
|
+
},
|
|
13956
|
+
{ passive: true }
|
|
13957
|
+
);
|
|
13958
|
+
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
13959
|
+
};
|
|
13960
|
+
const setDrawOffset = (x2, y) => {
|
|
13961
|
+
g_draw_offset_x = x2;
|
|
13962
|
+
g_draw_offset_y = y;
|
|
13963
|
+
drawKeyboard();
|
|
13964
|
+
drawHeader();
|
|
13965
|
+
};
|
|
13966
|
+
setup();
|
|
13967
|
+
return {
|
|
13968
|
+
getRenderConfig,
|
|
13969
|
+
setBackgroundActive,
|
|
13970
|
+
getDrawOffset,
|
|
13971
|
+
getGridCanvas,
|
|
13972
|
+
getGridContext,
|
|
13973
|
+
getHeaderCanvas,
|
|
13974
|
+
drawKeyboard,
|
|
13975
|
+
drawHeader,
|
|
13976
|
+
drawGrid,
|
|
13977
|
+
drawNotes,
|
|
13978
|
+
drawNoteLyrics,
|
|
13979
|
+
drawSelectionRect,
|
|
13980
|
+
drawSelectedNotes,
|
|
13981
|
+
getXY,
|
|
13982
|
+
getGridPosition,
|
|
13983
|
+
onClick,
|
|
13984
|
+
setDrawOffset,
|
|
13985
|
+
destroy: () => {
|
|
13986
|
+
mountTarget.innerHTML = "";
|
|
13987
|
+
}
|
|
13988
|
+
};
|
|
13989
|
+
};
|
|
13990
|
+
|
|
13528
13991
|
// src/daw.ts
|
|
13529
13992
|
var CHORD_INFO_HTML2 = `
|
|
13530
13993
|
<div class="dtm-modal-body-content">
|
|
@@ -13811,6 +14274,27 @@ var MIDI_INFO_HTML = `
|
|
|
13811
14274
|
<p style="margin-top:4px;"><small>\u6B4C\u8A5E\u306E\u62BD\u51FA: <a href="https://rpgen3.github.io/ust2txt/" target="_blank" rel="noopener">ust2txt</a></small></p>
|
|
13812
14275
|
</div>
|
|
13813
14276
|
`;
|
|
14277
|
+
var EDO_INFO_HTML = `
|
|
14278
|
+
<div class="dtm-modal-section">
|
|
14279
|
+
<p><strong>\u97F3\u5F8B</strong>\u306F1\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u4F55\u7B49\u5206\u3059\u308B\u304B\u306E\u8A2D\u5B9A\u3067\u3059\u3002\u66F2\u5168\u4F53\u306B\u52B9\u304D\u307E\u3059\uFF08\u30C8\u30E9\u30C3\u30AF\u3054\u3068\u30FB\u5C0F\u7BC0\u3054\u3068\u306B\u306F\u5909\u3048\u3089\u308C\u307E\u305B\u3093\uFF09\u3002</p>
|
|
14280
|
+
<p><strong>12\u5E73\u5747\u5F8B</strong>\u304C\u901A\u5E38\u306E\u30D4\u30A2\u30CE\u3068\u540C\u3058\u8ABF\u5F8B\u3067\u3059\u3002<strong>31\u5E73\u5747\u5F8B</strong>\u306F1\u30AA\u30AF\u30BF\u30FC\u30D6\u309231\u7B49\u5206\u3057\u3001\u30D4\u30A2\u30CE\u306B\u306F\u7121\u3044\u97F3\uFF08\u5FAE\u5206\u97F3\uFF09\u304C\u4F7F\u3048\u307E\u3059\u3002</p>
|
|
14281
|
+
<p style="margin-top:8px;"><strong>31\u5E73\u5747\u5F8B\u306E\u7279\u5FB4</strong></p>
|
|
14282
|
+
<ul>
|
|
14283
|
+
<li>\u95773\u5EA6\u304C\u7D14\u6B63\u306B\u3054\u304F\u8FD1\u304F\uFF08\u8AA4\u5DEE0.8\u30BB\u30F3\u30C8\uFF09\u3001<strong>\u548C\u97F3\u304C12\u5E73\u5747\u5F8B\u3088\u308A\u7DBA\u9E97\u306B\u97FF\u304D\u307E\u3059</strong>\u3002</li>
|
|
14284
|
+
<li>\u30D4\u30A2\u30CE\u306B\u7121\u3044\u97F3\u304C\u4F7F\u3048\u307E\u3059\u3002\u4E2D\u7ACB3\u5EA6\uFF08\u9577\u77ED\u3069\u3061\u3089\u3067\u3082\u306A\u30443\u5EA6\uFF09\u3001\u81EA\u71367\u5EA6\uFF08\u30D0\u30FC\u30D0\u30FC\u30B7\u30E7\u30C3\u30D7\u306E\u97FF\u304D\uFF09\u306A\u3069\u3002</li>
|
|
14285
|
+
<li>C\u266F \u3068 D\u266D \u304C<strong>\u5225\u306E\u97F3</strong>\u306B\u306A\u308A\u307E\u3059\uFF0812\u5E73\u5747\u5F8B\u3067\u306F\u540C\u3058\u97F3\uFF09\u3002</li>
|
|
14286
|
+
</ul>
|
|
14287
|
+
<p style="margin-top:8px;"><strong>MML\u3067\u306E\u66F8\u304D\u65B9</strong></p>
|
|
14288
|
+
<p><code>#edo=31</code> \u3092\u5BA3\u8A00\u3057\u307E\u3059\u3002\u81E8\u6642\u8A18\u53F7\u306F4\u3064\u4F7F\u3044\u5206\u3051\u307E\u3059\u3002</p>
|
|
14289
|
+
<ul>
|
|
14290
|
+
<li><code>#</code> \u2026 \u534A\u97F3\u4E0A\u3052\uFF082\u5EA6\uFF09\u3000<code>-</code> \u2026 \u534A\u97F3\u4E0B\u3052\uFF082\u5EA6\uFF09</li>
|
|
14291
|
+
<li><code>+</code> \u2026 \u5FAE\u5206\u97F31\u3064\u4E0A\u3052\uFF081\u5EA6\uFF09\u3000<code>_</code> \u2026 \u5FAE\u5206\u97F31\u3064\u4E0B\u3052\uFF081\u5EA6\uFF09</li>
|
|
14292
|
+
</ul>
|
|
14293
|
+
<p>\u4F8B: <code>#edo=31 @0 o4 c c+ c# d- d_ d;</code></p>
|
|
14294
|
+
<p style="margin-top:8px;"><small>12\u5E73\u5747\u5F8B\u3067\u306F4\u8A18\u53F7\u3068\u3082\u5F93\u6765\u901A\u308A\u306E\u610F\u5473\uFF08\u30B7\u30E3\u30FC\u30D72\u3064\u30FB\u30D5\u30E9\u30C3\u30C82\u3064\uFF09\u306B\u306A\u308B\u306E\u3067\u3001\u65E2\u5B58\u306E\u66F2\u306E\u89E3\u91C8\u306F\u5909\u308F\u308A\u307E\u305B\u3093\u3002</small></p>
|
|
14295
|
+
<p style="margin-top:8px;"><small>\u5207\u308A\u66FF\u3048\u308B\u3068\u3001\u65E2\u5B58\u306E\u97F3\u7B26\u306F\u65B0\u3057\u3044\u97F3\u5F8B\u306E\u683C\u5B50\u3078\u4E38\u3081\u3089\u308C\u307E\u3059\u300212\u219231 \u306F\u6700\u592719\u30BB\u30F3\u30C8\u306E\u79FB\u52D5\u3067\u5143\u306B\u623B\u305B\u307E\u3059\u304C\u3001<strong>31\u219212 \u306F\u6700\u592748\u30BB\u30F3\u30C8\u52D5\u304D\u300112\u5E73\u5747\u5F8B\u306B\u7121\u3044\u97F3\u306F\u5931\u308F\u308C\u307E\u3059</strong>\u3002</small></p>
|
|
14296
|
+
</div>
|
|
14297
|
+
`;
|
|
13814
14298
|
var KOE_INFO_HTML = `
|
|
13815
14299
|
<div class="dtm-modal-body-content">
|
|
13816
14300
|
<h4>1. UTAU\u97F3\u6E90\u3092 .koe \u306B\u5909\u63DB\u3059\u308B</h4>
|
|
@@ -14028,7 +14512,7 @@ var AUTO_ROLE_BASE_VOLUME = {
|
|
|
14028
14512
|
chord: 72
|
|
14029
14513
|
};
|
|
14030
14514
|
var computeAutoRoleStats = (notes) => {
|
|
14031
|
-
const avgPitch = notes.reduce((s, n) => s + n.
|
|
14515
|
+
const avgPitch = notes.reduce((s, n) => s + n.pitchUnits, 0) / notes.length;
|
|
14032
14516
|
const avgDur = notes.reduce((s, n) => s + n.durationSteps, 0) / notes.length;
|
|
14033
14517
|
const byStart = /* @__PURE__ */ new Map();
|
|
14034
14518
|
for (const n of notes)
|
|
@@ -14042,8 +14526,9 @@ var computeAutoRoleStats = (notes) => {
|
|
|
14042
14526
|
const notesPerBeat = notes.length / spanBeats;
|
|
14043
14527
|
return { avgPitch, avgDur, avgPoly, maxPoly, notesPerBeat };
|
|
14044
14528
|
};
|
|
14529
|
+
var AUTO_ROLE_BASS_UNITS = 48 * UNITS_PER_SEMITONE;
|
|
14045
14530
|
var classifyTrackRole = (stats) => {
|
|
14046
|
-
if (stats.avgPitch <
|
|
14531
|
+
if (stats.avgPitch < AUTO_ROLE_BASS_UNITS) return "bass";
|
|
14047
14532
|
if (stats.maxPoly >= 2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT)
|
|
14048
14533
|
return "chord";
|
|
14049
14534
|
if (stats.notesPerBeat < 1.2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT * 1.5)
|
|
@@ -14055,7 +14540,10 @@ var computeAutoRoleVolume = (role, stats) => {
|
|
|
14055
14540
|
if (role === "chord" && stats.avgPoly > 1) {
|
|
14056
14541
|
vol /= Math.sqrt(stats.avgPoly);
|
|
14057
14542
|
} else if (role === "bass") {
|
|
14058
|
-
const depthSemitones = Math.max(
|
|
14543
|
+
const depthSemitones = Math.max(
|
|
14544
|
+
0,
|
|
14545
|
+
(AUTO_ROLE_BASS_UNITS - stats.avgPitch) / UNITS_PER_SEMITONE
|
|
14546
|
+
);
|
|
14059
14547
|
vol += Math.min(12, depthSemitones * 0.5);
|
|
14060
14548
|
}
|
|
14061
14549
|
return clamp5(Math.round(vol), 1, 127);
|
|
@@ -14147,17 +14635,48 @@ var mountDAW = (target, options = {}) => {
|
|
|
14147
14635
|
refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
|
|
14148
14636
|
const renderConfig = {
|
|
14149
14637
|
stepsPerBar: 192,
|
|
14150
|
-
keyCount:
|
|
14151
|
-
pitchRangeStart:
|
|
14638
|
+
keyCount: KEY_COUNT,
|
|
14639
|
+
pitchRangeStart: PITCH_RANGE_START,
|
|
14640
|
+
unitsPerRow: unitsPerRow(12),
|
|
14641
|
+
edo: 12,
|
|
14152
14642
|
keyHeight: BASE_KEY_HEIGHT,
|
|
14153
14643
|
stepWidth: BASE_STEP_WIDTH * 2
|
|
14154
14644
|
// zoom100% 相当
|
|
14155
14645
|
};
|
|
14646
|
+
let renderer;
|
|
14156
14647
|
let zoomX = 100;
|
|
14157
14648
|
let zoomY = 100;
|
|
14158
14649
|
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
14159
14650
|
let masterVolume = options.masterVolume ?? 50;
|
|
14160
|
-
|
|
14651
|
+
const applyMasterVolume = (volume) => {
|
|
14652
|
+
masterVolume = clamp5(Math.round(volume), 0, 100);
|
|
14653
|
+
refs.masterVolume.value = String(masterVolume);
|
|
14654
|
+
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
14655
|
+
options.singingVoices?.setVolume(masterVolume / 100);
|
|
14656
|
+
};
|
|
14657
|
+
applyMasterVolume(masterVolume);
|
|
14658
|
+
const snapToEdoGrid = (units) => {
|
|
14659
|
+
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
14660
|
+
return Math.round(units / upr) * upr;
|
|
14661
|
+
};
|
|
14662
|
+
const applyEdo = (edo, opts) => {
|
|
14663
|
+
const next = edo === 31 ? 31 : 12;
|
|
14664
|
+
if (renderConfig.edo === next) return;
|
|
14665
|
+
const upr = unitsPerRow(next);
|
|
14666
|
+
renderConfig.edo = next;
|
|
14667
|
+
renderConfig.unitsPerRow = upr;
|
|
14668
|
+
renderConfig.keyCount = keyCountFor(next);
|
|
14669
|
+
if (!opts?.snap) return;
|
|
14670
|
+
for (const t of trackStates) {
|
|
14671
|
+
t.core.beginBatch();
|
|
14672
|
+
for (const note of [...t.core.getNotes()]) {
|
|
14673
|
+
const snapped = Math.round(note.pitchUnits / upr) * upr;
|
|
14674
|
+
if (snapped !== note.pitchUnits)
|
|
14675
|
+
t.core.moveNote(note.id, note.startStep, snapped);
|
|
14676
|
+
}
|
|
14677
|
+
t.core.endBatch();
|
|
14678
|
+
}
|
|
14679
|
+
};
|
|
14161
14680
|
let reverbAmount = options.reverbAmount ?? 0;
|
|
14162
14681
|
let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
|
|
14163
14682
|
let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
|
|
@@ -14215,8 +14734,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14215
14734
|
let snapGridSteps = 12;
|
|
14216
14735
|
const gridLineSteps = 48;
|
|
14217
14736
|
let currentOffsetX = 0;
|
|
14218
|
-
const _initPitch = options.initialScrollPitch ?? 48;
|
|
14219
|
-
let currentOffsetY = (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215;
|
|
14737
|
+
const _initPitch = pitchV1ToUnits(options.initialScrollPitch ?? 48);
|
|
14738
|
+
let currentOffsetY = (renderConfig.keyCount - 1 - (_initPitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - 215;
|
|
14220
14739
|
let playStartStep = 0;
|
|
14221
14740
|
let isSolo = false;
|
|
14222
14741
|
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
@@ -14311,21 +14830,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
14311
14830
|
if (!ready) return;
|
|
14312
14831
|
if (!suppressPatch && options.onNotesPatch) {
|
|
14313
14832
|
const prevByKey = new Map(
|
|
14314
|
-
prevNotes.map((n) => [`${n.startStep}_${n.
|
|
14833
|
+
prevNotes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14315
14834
|
);
|
|
14316
14835
|
const currByKey = new Map(
|
|
14317
|
-
notes.map((n) => [`${n.startStep}_${n.
|
|
14836
|
+
notes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14318
14837
|
);
|
|
14319
14838
|
const added = notes.filter((n) => {
|
|
14320
|
-
const prev = prevByKey.get(
|
|
14839
|
+
const prev = prevByKey.get(
|
|
14840
|
+
`${n.startStep}_${n.pitchUnits}`
|
|
14841
|
+
);
|
|
14321
14842
|
return !prev || prev.durationSteps !== n.durationSteps || prev.velocity !== n.velocity;
|
|
14322
14843
|
}).map((n) => ({
|
|
14323
14844
|
startStep: n.startStep,
|
|
14324
|
-
|
|
14845
|
+
pitchUnits: n.pitchUnits,
|
|
14325
14846
|
durationSteps: n.durationSteps,
|
|
14326
14847
|
velocity: n.velocity
|
|
14327
14848
|
}));
|
|
14328
|
-
const removed = prevNotes.filter(
|
|
14849
|
+
const removed = prevNotes.filter(
|
|
14850
|
+
(n) => !currByKey.has(`${n.startStep}_${n.pitchUnits}`)
|
|
14851
|
+
).map((n) => ({
|
|
14852
|
+
startStep: n.startStep,
|
|
14853
|
+
pitchUnits: n.pitchUnits
|
|
14854
|
+
}));
|
|
14329
14855
|
if (added.length > 0 || removed.length > 0) {
|
|
14330
14856
|
options.onNotesPatch(config.id, added, removed);
|
|
14331
14857
|
}
|
|
@@ -14335,7 +14861,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14335
14861
|
updateUndoRedo();
|
|
14336
14862
|
}
|
|
14337
14863
|
},
|
|
14338
|
-
config.volume
|
|
14864
|
+
config.volume,
|
|
14865
|
+
() => renderConfig
|
|
14339
14866
|
),
|
|
14340
14867
|
volume: config.volume,
|
|
14341
14868
|
savedChordInput: "",
|
|
@@ -14430,18 +14957,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
14430
14957
|
return (lastNoteMeasure + 2) * renderConfig.stepsPerBar;
|
|
14431
14958
|
};
|
|
14432
14959
|
const getMaxOffsetX = () => {
|
|
14433
|
-
const canvas = getGridCanvas();
|
|
14960
|
+
const canvas = renderer.getGridCanvas();
|
|
14434
14961
|
const maxNoteStep = getMaxNoteStep();
|
|
14435
14962
|
const totalContentWidth = maxNoteStep * renderConfig.stepWidth;
|
|
14436
14963
|
return Math.max(0, totalContentWidth - canvas.width);
|
|
14437
14964
|
};
|
|
14438
14965
|
const getMaxOffsetY = () => {
|
|
14439
14966
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
14440
|
-
return Math.max(0, totalHeight - getGridCanvas().height);
|
|
14967
|
+
return Math.max(0, totalHeight - renderer.getGridCanvas().height);
|
|
14441
14968
|
};
|
|
14442
14969
|
const drawStartLine = () => {
|
|
14443
|
-
const ctx = getGridContext();
|
|
14444
|
-
const canvas = getGridCanvas();
|
|
14970
|
+
const ctx = renderer.getGridContext();
|
|
14971
|
+
const canvas = renderer.getGridCanvas();
|
|
14445
14972
|
if (!ctx) return;
|
|
14446
14973
|
const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
|
|
14447
14974
|
if (x2 < -10 || x2 > canvas.width + 10) return;
|
|
@@ -14456,8 +14983,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14456
14983
|
ctx.restore();
|
|
14457
14984
|
};
|
|
14458
14985
|
const drawPlayhead = () => {
|
|
14459
|
-
const ctx = getGridContext();
|
|
14460
|
-
const canvas = getGridCanvas();
|
|
14986
|
+
const ctx = renderer.getGridContext();
|
|
14987
|
+
const canvas = renderer.getGridCanvas();
|
|
14461
14988
|
if (!ctx) return;
|
|
14462
14989
|
const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
|
|
14463
14990
|
if (x2 < 0 || x2 > canvas.width) return;
|
|
@@ -14471,20 +14998,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
14471
14998
|
ctx.restore();
|
|
14472
14999
|
};
|
|
14473
15000
|
const redrawAll = () => {
|
|
14474
|
-
drawGrid(gridLineSteps);
|
|
15001
|
+
renderer.drawGrid(gridLineSteps);
|
|
14475
15002
|
let lyricTargetNotes = null;
|
|
15003
|
+
let activeTrackState = null;
|
|
14476
15004
|
for (const t of trackStates) {
|
|
14477
15005
|
if (hiddenTracks.has(t.config.id)) continue;
|
|
14478
15006
|
if (isSolo && t.config.id !== activeTrackId) continue;
|
|
15007
|
+
if (t.config.id === activeTrackId) {
|
|
15008
|
+
activeTrackState = t;
|
|
15009
|
+
continue;
|
|
15010
|
+
}
|
|
14479
15011
|
const [r, g, b] = t.config.color;
|
|
14480
|
-
const isActive = t.config.id === activeTrackId;
|
|
14481
|
-
const a = isActive ? 1 : 0.3;
|
|
14482
15012
|
const notes = t.core.getNotes();
|
|
14483
|
-
drawNotes(notes, [r, g, b,
|
|
14484
|
-
|
|
15013
|
+
renderer.drawNotes(notes, [r, g, b, 1], false);
|
|
15014
|
+
}
|
|
15015
|
+
if (activeTrackState) {
|
|
15016
|
+
const [r, g, b] = activeTrackState.config.color;
|
|
15017
|
+
const notes = activeTrackState.core.getNotes();
|
|
15018
|
+
renderer.drawNotes(notes, [r, g, b, 1], true);
|
|
15019
|
+
lyricTargetNotes = notes;
|
|
14485
15020
|
}
|
|
14486
15021
|
if (activeToolMode === "select" && selectionRect) {
|
|
14487
|
-
const ctx = getGridContext();
|
|
15022
|
+
const ctx = renderer.getGridContext();
|
|
14488
15023
|
ctx.save();
|
|
14489
15024
|
ctx.strokeStyle = "#ffec27";
|
|
14490
15025
|
ctx.lineWidth = 2;
|
|
@@ -14507,19 +15042,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
14507
15042
|
if (activeToolMode === "select" && selectedNotes.length > 0) {
|
|
14508
15043
|
const ids = new Set(selectedNotes.map((n) => n.id));
|
|
14509
15044
|
const active = getActive();
|
|
14510
|
-
drawSelectedNotes(active.core.getNotes(), ids, [
|
|
15045
|
+
renderer.drawSelectedNotes(active.core.getNotes(), ids, [
|
|
14511
15046
|
...active.config.color,
|
|
14512
15047
|
1
|
|
14513
15048
|
]);
|
|
14514
15049
|
}
|
|
14515
15050
|
if (lyricTargetNotes)
|
|
14516
|
-
drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
15051
|
+
renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
14517
15052
|
drawStartLine();
|
|
14518
15053
|
if (playbackState === "playing") drawPlayhead();
|
|
14519
15054
|
updateScrollbars();
|
|
14520
15055
|
};
|
|
14521
15056
|
const updateScrollbars = () => {
|
|
14522
|
-
const canvas = getGridCanvas();
|
|
15057
|
+
const canvas = renderer.getGridCanvas();
|
|
14523
15058
|
const maxOffsetX = getMaxOffsetX();
|
|
14524
15059
|
const sbW = refs.hScroll.clientWidth;
|
|
14525
15060
|
if (maxOffsetX <= 0) {
|
|
@@ -14603,7 +15138,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14603
15138
|
const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
14604
15139
|
const ratio = x2 / (rect.width - thumbW);
|
|
14605
15140
|
currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
|
|
14606
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15141
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14607
15142
|
redrawAll();
|
|
14608
15143
|
};
|
|
14609
15144
|
const moveV = (clientY) => {
|
|
@@ -14614,7 +15149,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14614
15149
|
const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
14615
15150
|
const ratio = y / (rect.height - thumbH);
|
|
14616
15151
|
currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
|
|
14617
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15152
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14618
15153
|
redrawAll();
|
|
14619
15154
|
};
|
|
14620
15155
|
};
|
|
@@ -14653,8 +15188,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14653
15188
|
const autoScrollTick = () => {
|
|
14654
15189
|
autoScrollRAF = null;
|
|
14655
15190
|
if (!isSelecting || !lastMoveEvent) return;
|
|
14656
|
-
const canvas = getGridCanvas();
|
|
14657
|
-
const { x: x2, y } = getGridPosition(lastMoveEvent);
|
|
15191
|
+
const canvas = renderer.getGridCanvas();
|
|
15192
|
+
const { x: x2, y } = renderer.getGridPosition(lastMoveEvent);
|
|
14658
15193
|
const dx = computeEdgeSpeed(x2, canvas.width);
|
|
14659
15194
|
const dy = computeEdgeSpeed(y, canvas.height);
|
|
14660
15195
|
if (dx !== 0 || dy !== 0) {
|
|
@@ -14662,7 +15197,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14662
15197
|
const maxOffsetY = getMaxOffsetY();
|
|
14663
15198
|
currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
|
|
14664
15199
|
currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
|
|
14665
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15200
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14666
15201
|
onPointerMove(lastMoveEvent);
|
|
14667
15202
|
}
|
|
14668
15203
|
if (isSelecting) {
|
|
@@ -14683,11 +15218,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14683
15218
|
};
|
|
14684
15219
|
const findActiveNoteAt = (x2, y, margin = 0) => {
|
|
14685
15220
|
const active = getActive();
|
|
14686
|
-
const {
|
|
14687
|
-
|
|
15221
|
+
const {
|
|
15222
|
+
stepWidth,
|
|
15223
|
+
keyHeight,
|
|
15224
|
+
keyCount,
|
|
15225
|
+
pitchRangeStart,
|
|
15226
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15227
|
+
} = renderConfig;
|
|
15228
|
+
const offset = renderer.getDrawOffset();
|
|
14688
15229
|
for (const note of active.core.getNotes()) {
|
|
14689
15230
|
const logicalX = note.startStep * stepWidth;
|
|
14690
|
-
const yIndex = keyCount - 1 - (note.
|
|
15231
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14691
15232
|
const logicalY = yIndex * keyHeight;
|
|
14692
15233
|
const w = note.durationSteps * stepWidth;
|
|
14693
15234
|
const renderX = logicalX - offset.x;
|
|
@@ -14700,7 +15241,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14700
15241
|
const hasNoteAt = (step, pitch, excludeId) => {
|
|
14701
15242
|
const active = getActive();
|
|
14702
15243
|
return active.core.getNotes().some(
|
|
14703
|
-
(n) => n.id !== excludeId && n.
|
|
15244
|
+
(n) => n.id !== excludeId && n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
14704
15245
|
);
|
|
14705
15246
|
};
|
|
14706
15247
|
const snapToGrid = (duration) => Math.max(
|
|
@@ -14711,7 +15252,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14711
15252
|
const onGridPointerDown = (event) => {
|
|
14712
15253
|
event.preventDefault();
|
|
14713
15254
|
options.onResumeAudio?.();
|
|
14714
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15255
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14715
15256
|
const active = getActive();
|
|
14716
15257
|
if (activeToolMode === "eraser") {
|
|
14717
15258
|
if (isActiveLocked()) return;
|
|
@@ -14726,7 +15267,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14726
15267
|
selectedOriginal = selectedNotes.map((n) => ({
|
|
14727
15268
|
id: n.id,
|
|
14728
15269
|
startStep: n.startStep,
|
|
14729
|
-
pitch: n.
|
|
15270
|
+
pitch: n.pitchUnits
|
|
14730
15271
|
}));
|
|
14731
15272
|
isSelecting = true;
|
|
14732
15273
|
dragMode = "move";
|
|
@@ -14745,7 +15286,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14745
15286
|
{
|
|
14746
15287
|
id: clicked.id,
|
|
14747
15288
|
startStep: clicked.startStep,
|
|
14748
|
-
pitch: clicked.
|
|
15289
|
+
pitch: clicked.pitchUnits
|
|
14749
15290
|
}
|
|
14750
15291
|
];
|
|
14751
15292
|
isSelecting = true;
|
|
@@ -14763,9 +15304,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
14763
15304
|
hasDragged = false;
|
|
14764
15305
|
const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
|
|
14765
15306
|
if (existing) {
|
|
14766
|
-
playPreview(existing.
|
|
15307
|
+
playPreview(existing.pitchUnits);
|
|
14767
15308
|
const { stepWidth } = renderConfig;
|
|
14768
|
-
const offset = getDrawOffset();
|
|
15309
|
+
const offset = renderer.getDrawOffset();
|
|
14769
15310
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
14770
15311
|
const w = existing.durationSteps * stepWidth;
|
|
14771
15312
|
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
@@ -14776,17 +15317,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14776
15317
|
dragOffsetPitch: 0,
|
|
14777
15318
|
startStep: existing.startStep,
|
|
14778
15319
|
durationSteps: existing.durationSteps,
|
|
14779
|
-
lastPreviewPitch: existing.
|
|
15320
|
+
lastPreviewPitch: existing.pitchUnits
|
|
14780
15321
|
};
|
|
14781
15322
|
} else {
|
|
14782
15323
|
dragState = {
|
|
14783
15324
|
noteId: existing.id,
|
|
14784
15325
|
mode: "move",
|
|
14785
15326
|
dragOffsetStep: step - existing.startStep,
|
|
14786
|
-
dragOffsetPitch: pitch - existing.
|
|
15327
|
+
dragOffsetPitch: pitch - existing.pitchUnits,
|
|
14787
15328
|
startStep: existing.startStep,
|
|
14788
15329
|
durationSteps: existing.durationSteps,
|
|
14789
|
-
lastPreviewPitch: existing.
|
|
15330
|
+
lastPreviewPitch: existing.pitchUnits
|
|
14790
15331
|
};
|
|
14791
15332
|
}
|
|
14792
15333
|
suppressClick = true;
|
|
@@ -14797,14 +15338,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
14797
15338
|
const newStart = snappedStep;
|
|
14798
15339
|
const newEnd = newStart + currentInsertLength;
|
|
14799
15340
|
const overlapping = active.core.getNotes().some(
|
|
14800
|
-
(n) => n.
|
|
15341
|
+
(n) => n.pitchUnits === pitch && newStart < n.startStep + n.durationSteps && newEnd > n.startStep
|
|
14801
15342
|
);
|
|
14802
15343
|
if (!overlapping) {
|
|
14803
15344
|
active.core.addNote(snappedStep, pitch, {
|
|
14804
15345
|
noteLengthSteps: currentInsertLength
|
|
14805
15346
|
});
|
|
14806
15347
|
playPreview(pitch);
|
|
14807
|
-
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.
|
|
15348
|
+
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.pitchUnits === pitch);
|
|
14808
15349
|
if (newNote) {
|
|
14809
15350
|
dragState = {
|
|
14810
15351
|
noteId: newNote.id,
|
|
@@ -14813,7 +15354,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14813
15354
|
dragOffsetPitch: 0,
|
|
14814
15355
|
startStep: newNote.startStep,
|
|
14815
15356
|
durationSteps: newNote.durationSteps,
|
|
14816
|
-
lastPreviewPitch: newNote.
|
|
15357
|
+
lastPreviewPitch: newNote.pitchUnits
|
|
14817
15358
|
};
|
|
14818
15359
|
hasDragged = true;
|
|
14819
15360
|
}
|
|
@@ -14824,7 +15365,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14824
15365
|
const active = getActive();
|
|
14825
15366
|
if (activeToolMode === "pen") {
|
|
14826
15367
|
if (!dragState) return;
|
|
14827
|
-
const { step, pitch } = getGridPosition(event);
|
|
15368
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
14828
15369
|
hasDragged = true;
|
|
14829
15370
|
if (dragState.mode === "move") {
|
|
14830
15371
|
const nextStart = step - dragState.dragOffsetStep;
|
|
@@ -14848,7 +15389,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14848
15389
|
}
|
|
14849
15390
|
if (activeToolMode === "select" && isSelecting && selectionStart) {
|
|
14850
15391
|
ensureAutoScroll(event);
|
|
14851
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15392
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14852
15393
|
if (dragMode === "rect") {
|
|
14853
15394
|
const rect = {
|
|
14854
15395
|
x: Math.min(x2, selectionStart.x),
|
|
@@ -14857,11 +15398,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14857
15398
|
height: Math.abs(y - selectionStart.y)
|
|
14858
15399
|
};
|
|
14859
15400
|
selectionRect = rect;
|
|
14860
|
-
const {
|
|
14861
|
-
|
|
15401
|
+
const {
|
|
15402
|
+
stepWidth,
|
|
15403
|
+
keyHeight,
|
|
15404
|
+
keyCount,
|
|
15405
|
+
pitchRangeStart,
|
|
15406
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15407
|
+
} = renderConfig;
|
|
15408
|
+
const offset = renderer.getDrawOffset();
|
|
14862
15409
|
selectedNotes = active.core.getNotes().filter((note) => {
|
|
14863
15410
|
const logicalX = note.startStep * stepWidth;
|
|
14864
|
-
const yIndex = keyCount - 1 - (note.
|
|
15411
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14865
15412
|
const logicalY = yIndex * keyHeight;
|
|
14866
15413
|
const nx = logicalX - offset.x;
|
|
14867
15414
|
const ny = logicalY - offset.y;
|
|
@@ -14880,7 +15427,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14880
15427
|
const orig = selectedOriginal.find((o) => o.id === note.id);
|
|
14881
15428
|
if (!orig) continue;
|
|
14882
15429
|
const newPitch = orig.pitch + deltaPitch;
|
|
14883
|
-
if (newPitch >=
|
|
15430
|
+
if (newPitch >= PITCH_RANGE_START && newPitch <= PITCH_RANGE_END)
|
|
14884
15431
|
active.core.moveNote(
|
|
14885
15432
|
note.id,
|
|
14886
15433
|
orig.startStep + snappedDelta,
|
|
@@ -15028,22 +15575,23 @@ var mountDAW = (target, options = {}) => {
|
|
|
15028
15575
|
refs.bgOpacityRow.classList.add("dtm-hidden");
|
|
15029
15576
|
}
|
|
15030
15577
|
refs.bgRemoveBtn.classList.toggle("dtm-hidden", !blob2);
|
|
15031
|
-
setBackgroundActive(!!blob2);
|
|
15578
|
+
renderer.setBackgroundActive(!!blob2);
|
|
15032
15579
|
redrawAll();
|
|
15033
15580
|
};
|
|
15034
15581
|
const setupCanvas = () => {
|
|
15035
15582
|
const w = refs.rollContainer.clientWidth || 800;
|
|
15036
15583
|
const h = refs.rollContainer.clientHeight || 450;
|
|
15037
|
-
|
|
15038
|
-
|
|
15584
|
+
renderer?.destroy();
|
|
15585
|
+
renderer = createRenderer(refs.wrapper, w, h, renderConfig);
|
|
15586
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
15039
15587
|
gridCanvas.addEventListener("pointerdown", onGridPointerDown);
|
|
15040
15588
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
15041
15589
|
event.preventDefault();
|
|
15042
15590
|
if (isActiveLocked()) return;
|
|
15043
|
-
const { step, pitch } = getGridPosition(event);
|
|
15591
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15044
15592
|
const active = getActive();
|
|
15045
15593
|
const note = active.core.getNotes().find(
|
|
15046
|
-
(n) => n.
|
|
15594
|
+
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
15047
15595
|
);
|
|
15048
15596
|
if (note) active.core.deleteNoteById(note.id);
|
|
15049
15597
|
});
|
|
@@ -15061,7 +15609,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15061
15609
|
0,
|
|
15062
15610
|
getMaxOffsetX()
|
|
15063
15611
|
);
|
|
15064
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15612
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15065
15613
|
redrawAll();
|
|
15066
15614
|
},
|
|
15067
15615
|
{ passive: false }
|
|
@@ -15071,7 +15619,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15071
15619
|
suppressClick = false;
|
|
15072
15620
|
}
|
|
15073
15621
|
});
|
|
15074
|
-
const headerCanvas = getHeaderCanvas();
|
|
15622
|
+
const headerCanvas = renderer.getHeaderCanvas();
|
|
15075
15623
|
headerCanvas.addEventListener("click", (event) => {
|
|
15076
15624
|
if (playbackState === "playing") return;
|
|
15077
15625
|
const rect = headerCanvas.getBoundingClientRect();
|
|
@@ -15087,11 +15635,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15087
15635
|
}
|
|
15088
15636
|
redrawAll();
|
|
15089
15637
|
});
|
|
15090
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15638
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15091
15639
|
redrawAll();
|
|
15092
15640
|
};
|
|
15093
15641
|
const applyZoomX = () => {
|
|
15094
|
-
const canvas = getGridCanvas();
|
|
15642
|
+
const canvas = renderer.getGridCanvas();
|
|
15095
15643
|
const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
|
|
15096
15644
|
renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
|
|
15097
15645
|
refs.zoomXLabel.textContent = `${zoomX}%`;
|
|
@@ -15100,11 +15648,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15100
15648
|
0,
|
|
15101
15649
|
getMaxOffsetX()
|
|
15102
15650
|
);
|
|
15103
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15651
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15104
15652
|
redrawAll();
|
|
15105
15653
|
};
|
|
15106
15654
|
const applyZoomY = () => {
|
|
15107
|
-
const canvas = getGridCanvas();
|
|
15655
|
+
const canvas = renderer.getGridCanvas();
|
|
15108
15656
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
15109
15657
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
15110
15658
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
@@ -15113,7 +15661,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15113
15661
|
0,
|
|
15114
15662
|
getMaxOffsetY()
|
|
15115
15663
|
);
|
|
15116
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15664
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15117
15665
|
redrawAll();
|
|
15118
15666
|
};
|
|
15119
15667
|
const getViewState = () => ({
|
|
@@ -15125,7 +15673,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15125
15673
|
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
15126
15674
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
15127
15675
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
15128
|
-
options.onPlayNote?.({
|
|
15676
|
+
options.onPlayNote?.({
|
|
15677
|
+
trackId,
|
|
15678
|
+
pitchUnits: pitch,
|
|
15679
|
+
velocity,
|
|
15680
|
+
volume,
|
|
15681
|
+
when,
|
|
15682
|
+
duration
|
|
15683
|
+
});
|
|
15129
15684
|
};
|
|
15130
15685
|
const sequencer = createSequencer({
|
|
15131
15686
|
getTracks: () => trackStates.map((t) => ({
|
|
@@ -15153,7 +15708,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15153
15708
|
},
|
|
15154
15709
|
onTick: (step) => {
|
|
15155
15710
|
currentPlayStep = step;
|
|
15156
|
-
const canvas = getGridCanvas();
|
|
15711
|
+
const canvas = renderer.getGridCanvas();
|
|
15157
15712
|
const visibleSteps = canvas.width / renderConfig.stepWidth;
|
|
15158
15713
|
const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
|
|
15159
15714
|
if (currentPlayStep > threshold) {
|
|
@@ -15163,7 +15718,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15163
15718
|
0,
|
|
15164
15719
|
getMaxOffsetX()
|
|
15165
15720
|
);
|
|
15166
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15721
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15167
15722
|
}
|
|
15168
15723
|
redrawAll();
|
|
15169
15724
|
},
|
|
@@ -15194,7 +15749,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15194
15749
|
(a, b) => a.startStep - b.startStep
|
|
15195
15750
|
);
|
|
15196
15751
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
15197
|
-
const semis = (lt.octave ?? 0) *
|
|
15752
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
15198
15753
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
15199
15754
|
const notes = [];
|
|
15200
15755
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -15202,7 +15757,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15202
15757
|
if (n.startStep < fromStep) continue;
|
|
15203
15758
|
notes.push({
|
|
15204
15759
|
syllable: lt.syllables[i2],
|
|
15205
|
-
pitch: n.
|
|
15760
|
+
pitch: n.pitchUnits + semis,
|
|
15206
15761
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
15207
15762
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
15208
15763
|
});
|
|
@@ -15245,13 +15800,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
15245
15800
|
}
|
|
15246
15801
|
}
|
|
15247
15802
|
if (playbackState !== "paused") {
|
|
15248
|
-
const canvas = getGridCanvas();
|
|
15803
|
+
const canvas = renderer.getGridCanvas();
|
|
15249
15804
|
currentOffsetX = clamp5(
|
|
15250
15805
|
playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
|
|
15251
15806
|
0,
|
|
15252
15807
|
getMaxOffsetX()
|
|
15253
15808
|
);
|
|
15254
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15809
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15255
15810
|
}
|
|
15256
15811
|
playbackState = "playing";
|
|
15257
15812
|
sequencer.start(fromStep);
|
|
@@ -15337,17 +15892,38 @@ var mountDAW = (target, options = {}) => {
|
|
|
15337
15892
|
trackPillEls.clear();
|
|
15338
15893
|
for (const [i2, t] of trackStates.entries()) {
|
|
15339
15894
|
const [r, g, b] = t.config.color;
|
|
15895
|
+
const isActive = t.config.id === activeTrackId;
|
|
15340
15896
|
const btn = document.createElement("button");
|
|
15341
|
-
btn.className = `dtm-pill ${
|
|
15897
|
+
btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""}`;
|
|
15342
15898
|
btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
|
|
15343
|
-
btn.title = t.config.name
|
|
15899
|
+
btn.title = `Track ${i2 + 1}: ${t.config.name}`;
|
|
15900
|
+
btn.setAttribute(
|
|
15901
|
+
"aria-label",
|
|
15902
|
+
`Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}`
|
|
15903
|
+
);
|
|
15344
15904
|
btn.textContent = String(i2 + 1);
|
|
15345
15905
|
btn.addEventListener("click", () => switchTrack(t.config.id));
|
|
15346
15906
|
refs.trackTabs.appendChild(btn);
|
|
15347
15907
|
trackPillEls.set(t.config.id, btn);
|
|
15348
15908
|
}
|
|
15349
15909
|
const active = getActive();
|
|
15910
|
+
const activeIndex = trackStates.findIndex(
|
|
15911
|
+
(t) => t.config.id === activeTrackId
|
|
15912
|
+
);
|
|
15913
|
+
const [activeR, activeG, activeB] = active.config.color;
|
|
15914
|
+
const panelEl = refs.trackBody.closest(".dtm-panel");
|
|
15915
|
+
if (panelEl) {
|
|
15916
|
+
const summaryEl = panelEl.querySelector("summary");
|
|
15917
|
+
if (summaryEl) {
|
|
15918
|
+
summaryEl.textContent = `\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\uFF08Track ${activeIndex + 1}: ${active.config.name}\uFF09`;
|
|
15919
|
+
}
|
|
15920
|
+
}
|
|
15350
15921
|
refs.trackBody.innerHTML = `
|
|
15922
|
+
<div class="dtm-active-track-banner" style="--dtm-track-color: rgb(${activeR},${activeG},${activeB})">
|
|
15923
|
+
<span class="dtm-active-track-badge">TRACK ${activeIndex + 1}</span>
|
|
15924
|
+
<span class="dtm-active-track-name">${active.config.name}</span>
|
|
15925
|
+
<span class="dtm-active-track-pill">ACTIVE</span>
|
|
15926
|
+
</div>
|
|
15351
15927
|
<div class="dtm-row">
|
|
15352
15928
|
<span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
|
|
15353
15929
|
<input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
|
|
@@ -16217,6 +16793,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16217
16793
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16218
16794
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16219
16795
|
mode,
|
|
16796
|
+
edo: renderConfig.edo,
|
|
16220
16797
|
trackInstruments: trackInstMeta,
|
|
16221
16798
|
trackCompression: trackCompMeta,
|
|
16222
16799
|
trackWidth: trackWidthMeta,
|
|
@@ -16245,6 +16822,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16245
16822
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16246
16823
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16247
16824
|
mode,
|
|
16825
|
+
edo: renderConfig.edo,
|
|
16248
16826
|
trackInstruments: trackInstMeta,
|
|
16249
16827
|
trackCompression: trackCompMeta,
|
|
16250
16828
|
trackWidth: trackWidthMeta,
|
|
@@ -16384,19 +16962,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
16384
16962
|
}
|
|
16385
16963
|
}
|
|
16386
16964
|
if (candidateNotes.length === 0) return null;
|
|
16387
|
-
const sum = candidateNotes.reduce((acc, note) => acc + note.
|
|
16965
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitchUnits, 0);
|
|
16388
16966
|
return Math.round(sum / candidateNotes.length);
|
|
16389
16967
|
};
|
|
16390
16968
|
const centerPitch = (pitch) => {
|
|
16391
|
-
const canvas = getGridCanvas();
|
|
16392
|
-
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
16969
|
+
const canvas = renderer.getGridCanvas();
|
|
16970
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE);
|
|
16393
16971
|
const logicalY = yIndex * renderConfig.keyHeight;
|
|
16394
16972
|
currentOffsetY = clamp5(
|
|
16395
16973
|
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
16396
16974
|
0,
|
|
16397
16975
|
getMaxOffsetY()
|
|
16398
16976
|
);
|
|
16399
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16977
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16400
16978
|
};
|
|
16401
16979
|
const clearAll = () => {
|
|
16402
16980
|
for (const t of trackStates) {
|
|
@@ -16454,10 +17032,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
16454
17032
|
if (!meta.drumFont) applyDrumPatternFont(meta.drum);
|
|
16455
17033
|
}
|
|
16456
17034
|
if (meta.volume !== void 0) {
|
|
16457
|
-
|
|
16458
|
-
refs.masterVolume.value = String(meta.volume);
|
|
16459
|
-
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
17035
|
+
applyMasterVolume(meta.volume);
|
|
16460
17036
|
}
|
|
17037
|
+
applyEdo(meta.edo ?? 12);
|
|
17038
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16461
17039
|
if (meta.drumVolume !== void 0) {
|
|
16462
17040
|
drumVolume = meta.drumVolume;
|
|
16463
17041
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -16622,7 +17200,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16622
17200
|
if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
|
|
16623
17201
|
const t = trackStates[p.trackIndex];
|
|
16624
17202
|
if (!t) continue;
|
|
16625
|
-
t.core.addNote(p.startStep, p.
|
|
17203
|
+
t.core.addNote(p.startStep, p.pitchUnits, {
|
|
16626
17204
|
noteLengthSteps: p.durationSteps,
|
|
16627
17205
|
velocity: DEFAULT_VELOCITY
|
|
16628
17206
|
});
|
|
@@ -16639,7 +17217,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16639
17217
|
if (firstPitch !== null) {
|
|
16640
17218
|
centerPitch(firstPitch);
|
|
16641
17219
|
} else {
|
|
16642
|
-
centerPitch(48);
|
|
17220
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16643
17221
|
}
|
|
16644
17222
|
redrawAll();
|
|
16645
17223
|
updateTrackPanel();
|
|
@@ -16657,6 +17235,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16657
17235
|
const chordTrack = trackStates.find((t) => t.config.id === "chord");
|
|
16658
17236
|
if (!chordTrack) return;
|
|
16659
17237
|
const placements = buildChordPlacements({
|
|
17238
|
+
// 和音は五度連鎖経由で音律の格子へ写す(31平均律では長3度が10度になる)
|
|
17239
|
+
edo: renderConfig.edo,
|
|
16660
17240
|
chordStr: active.savedChordInput,
|
|
16661
17241
|
patternType: active.savedChordPattern,
|
|
16662
17242
|
rootShift: active.savedChordRoot,
|
|
@@ -16666,7 +17246,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16666
17246
|
chordTrack.core.clearNotesWithoutHistory();
|
|
16667
17247
|
chordTrack.core.beginBatch();
|
|
16668
17248
|
for (const p of placements) {
|
|
16669
|
-
chordTrack.core.addNote(p.startStep, p.
|
|
17249
|
+
chordTrack.core.addNote(p.startStep, p.pitchUnits, {
|
|
16670
17250
|
noteLengthSteps: Math.max(1, p.durationSteps),
|
|
16671
17251
|
velocity: p.velocity
|
|
16672
17252
|
});
|
|
@@ -16704,6 +17284,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16704
17284
|
active.vocalOctaveUnison = "none";
|
|
16705
17285
|
} else {
|
|
16706
17286
|
clearAll();
|
|
17287
|
+
applyEdo(12);
|
|
17288
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16707
17289
|
for (const t of trackStates) t.core.setLoadMode(true);
|
|
16708
17290
|
for (const t of trackStates) {
|
|
16709
17291
|
t.lyrics = "";
|
|
@@ -16730,7 +17312,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16730
17312
|
if (applyActiveOnly && p.trackId !== activeTrackId) continue;
|
|
16731
17313
|
const t = trackStates.find((ts) => ts.config.id === p.trackId);
|
|
16732
17314
|
if (!t) continue;
|
|
16733
|
-
t.core.addNote(p.startStep, p.pitch, {
|
|
17315
|
+
t.core.addNote(p.startStep, snapToEdoGrid(pitchV1ToUnits(p.pitch)), {
|
|
16734
17316
|
noteLengthSteps: p.durationSteps,
|
|
16735
17317
|
velocity: p.velocity
|
|
16736
17318
|
});
|
|
@@ -16747,7 +17329,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16747
17329
|
if (firstPitch !== null) {
|
|
16748
17330
|
centerPitch(firstPitch);
|
|
16749
17331
|
} else {
|
|
16750
|
-
centerPitch(48);
|
|
17332
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16751
17333
|
}
|
|
16752
17334
|
redrawAll();
|
|
16753
17335
|
updateTrackPanel();
|
|
@@ -16811,18 +17393,21 @@ var mountDAW = (target, options = {}) => {
|
|
|
16811
17393
|
}
|
|
16812
17394
|
}, 30);
|
|
16813
17395
|
};
|
|
16814
|
-
const showConfirmModal = (message) => new Promise((resolve) => {
|
|
17396
|
+
const showConfirmModal = (message, opts) => new Promise((resolve) => {
|
|
17397
|
+
const title = opts?.title ?? "\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D";
|
|
17398
|
+
const yes = opts?.yes ?? "\u306F\u3044\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u308B\uFF09";
|
|
17399
|
+
const no = opts?.no ?? "\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09";
|
|
16815
17400
|
const overlay = document.createElement("div");
|
|
16816
17401
|
overlay.className = "dtm-modal-overlay";
|
|
16817
17402
|
overlay.innerHTML = `
|
|
16818
17403
|
<div class="dtm-modal">
|
|
16819
17404
|
<div class="dtm-modal-header">
|
|
16820
|
-
<span class="dtm-modal-title"
|
|
17405
|
+
<span class="dtm-modal-title">${title}</span>
|
|
16821
17406
|
</div>
|
|
16822
17407
|
<div class="dtm-modal-body"><p>${message}</p></div>
|
|
16823
17408
|
<div class="dtm-confirm-footer">
|
|
16824
|
-
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no"
|
|
16825
|
-
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes"
|
|
17409
|
+
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no">${no}</button>
|
|
17410
|
+
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes">${yes}</button>
|
|
16826
17411
|
</div>
|
|
16827
17412
|
</div>`;
|
|
16828
17413
|
const close = (result) => {
|
|
@@ -16930,10 +17515,36 @@ var mountDAW = (target, options = {}) => {
|
|
|
16930
17515
|
});
|
|
16931
17516
|
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
16932
17517
|
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
17518
|
+
refs.edoSelect.addEventListener("change", async () => {
|
|
17519
|
+
const next = Number.parseInt(refs.edoSelect.value, 10) === 31 ? 31 : 12;
|
|
17520
|
+
const prev = renderConfig.edo ?? 12;
|
|
17521
|
+
if (next === prev) return;
|
|
17522
|
+
const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
|
|
17523
|
+
if (next === 12 && prev === 31 && hasNotes) {
|
|
17524
|
+
const okToDrop = await showConfirmModal(
|
|
17525
|
+
"31\u5E73\u5747\u5F8B\u304B\u308912\u5E73\u5747\u5F8B\u3078\u623B\u3059\u3068\u3001\u97F3\u7B26\u304C\u6700\u592748\u30BB\u30F3\u30C8\uFF08\u534A\u97F3\u306E\u7D04\u534A\u5206\uFF09\u52D5\u304D\u307E\u3059\u3002<br>12\u5E73\u5747\u5F8B\u306B\u7121\u3044\u97F3\uFF08\u4E2D\u7ACB3\u5EA6\u306A\u3069\uFF09\u306F\u8FD1\u3044\u97F3\u306B\u6F70\u308C\u3001<strong>\u5143\u306B\u306F\u623B\u305B\u307E\u305B\u3093</strong>\u3002<br>\u5207\u308A\u66FF\u3048\u307E\u3059\u304B\uFF1F",
|
|
17526
|
+
{
|
|
17527
|
+
title: "\u97F3\u5F8B\u306E\u78BA\u8A8D",
|
|
17528
|
+
yes: "\u5207\u308A\u66FF\u3048\u308B",
|
|
17529
|
+
no: "\u3084\u3081\u308B"
|
|
17530
|
+
}
|
|
17531
|
+
);
|
|
17532
|
+
if (!okToDrop) {
|
|
17533
|
+
refs.edoSelect.value = String(prev);
|
|
17534
|
+
return;
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
applyEdo(next, { snap: true });
|
|
17538
|
+
const first = getFirstDetectedPitch();
|
|
17539
|
+
centerPitch(first ?? pitchV1ToUnits(48));
|
|
17540
|
+
redrawAll();
|
|
17541
|
+
updateUndoRedo();
|
|
17542
|
+
});
|
|
17543
|
+
refs.edoInfoBtn.addEventListener("click", () => {
|
|
17544
|
+
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
17545
|
+
});
|
|
16933
17546
|
refs.masterVolume.addEventListener("input", () => {
|
|
16934
|
-
|
|
16935
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
16936
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17547
|
+
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
16937
17548
|
});
|
|
16938
17549
|
refs.masterComp.addEventListener("input", () => {
|
|
16939
17550
|
masterCompression = Number.parseInt(refs.masterComp.value, 10) || 0;
|
|
@@ -16997,10 +17608,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16997
17608
|
const scale = targetPeak / observedPeakMax;
|
|
16998
17609
|
const suggested = clamp5(Math.round(masterVolume * scale), 10, 100);
|
|
16999
17610
|
if (suggested !== masterVolume) {
|
|
17000
|
-
|
|
17001
|
-
refs.masterVolume.value = String(masterVolume);
|
|
17002
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17003
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17611
|
+
applyMasterVolume(suggested);
|
|
17004
17612
|
}
|
|
17005
17613
|
observedPeakMax = 0;
|
|
17006
17614
|
observedPlayMs = 0;
|
|
@@ -17160,7 +17768,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
17160
17768
|
generateRandomPattern(getActive().core, {
|
|
17161
17769
|
stepsPerBar: renderConfig.stepsPerBar,
|
|
17162
17770
|
startStep: playStartStep,
|
|
17163
|
-
pitchRangeStart: renderConfig.pitchRangeStart
|
|
17771
|
+
pitchRangeStart: renderConfig.pitchRangeStart,
|
|
17772
|
+
edo: renderConfig.edo
|
|
17164
17773
|
});
|
|
17165
17774
|
redrawAll();
|
|
17166
17775
|
});
|
|
@@ -17497,7 +18106,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17497
18106
|
id: 0,
|
|
17498
18107
|
startStep: p.startStep,
|
|
17499
18108
|
durationSteps: p.durationSteps,
|
|
17500
|
-
|
|
18109
|
+
pitchUnits: snapToEdoGrid(pitchV1ToUnits(p.pitch)),
|
|
17501
18110
|
velocity: p.velocity
|
|
17502
18111
|
}));
|
|
17503
18112
|
const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
|
|
@@ -17952,10 +18561,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
17952
18561
|
const newStart = playStartStep + (note.startStep - minStart);
|
|
17953
18562
|
const newEnd = newStart + note.durationSteps;
|
|
17954
18563
|
const overlap = notes.some(
|
|
17955
|
-
(ex) => ex.
|
|
18564
|
+
(ex) => ex.pitchUnits === note.pitchUnits && newStart < ex.startStep + ex.durationSteps && newEnd > ex.startStep
|
|
17956
18565
|
);
|
|
17957
18566
|
if (!overlap)
|
|
17958
|
-
core.addNote(newStart, note.
|
|
18567
|
+
core.addNote(newStart, note.pitchUnits, {
|
|
17959
18568
|
noteLengthSteps: note.durationSteps,
|
|
17960
18569
|
velocity: note.velocity
|
|
17961
18570
|
});
|
|
@@ -18028,13 +18637,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
18028
18637
|
pausedPlayStep = step;
|
|
18029
18638
|
currentPlayStep = step;
|
|
18030
18639
|
playbackState = "paused";
|
|
18031
|
-
const canvas = getGridCanvas();
|
|
18640
|
+
const canvas = renderer.getGridCanvas();
|
|
18032
18641
|
currentOffsetX = clamp5(
|
|
18033
18642
|
step * renderConfig.stepWidth - canvas.width * 0.5,
|
|
18034
18643
|
0,
|
|
18035
18644
|
getMaxOffsetX()
|
|
18036
18645
|
);
|
|
18037
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18646
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18038
18647
|
updateTransport();
|
|
18039
18648
|
redrawAll();
|
|
18040
18649
|
};
|
|
@@ -18102,16 +18711,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18102
18711
|
forcePauseAt,
|
|
18103
18712
|
setLoading,
|
|
18104
18713
|
setMasterVolume: (volume) => {
|
|
18105
|
-
|
|
18106
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18107
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18108
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18714
|
+
applyMasterVolume(volume);
|
|
18109
18715
|
},
|
|
18110
18716
|
setVolume: (volume) => {
|
|
18111
|
-
|
|
18112
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18113
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18114
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18717
|
+
applyMasterVolume(volume);
|
|
18115
18718
|
},
|
|
18116
18719
|
setDrumVolume: (volume) => {
|
|
18117
18720
|
drumVolume = clamp5(volume, 0, 100);
|
|
@@ -18154,15 +18757,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
18154
18757
|
suppressPatch = true;
|
|
18155
18758
|
track.core.beginBatch();
|
|
18156
18759
|
for (const n of added) {
|
|
18157
|
-
const existing = track.core.getNotes().find(
|
|
18760
|
+
const existing = track.core.getNotes().find(
|
|
18761
|
+
(e) => e.startStep === n.startStep && e.pitchUnits === n.pitchUnits
|
|
18762
|
+
);
|
|
18158
18763
|
if (existing) track.core.deleteNoteById(existing.id);
|
|
18159
|
-
track.core.addNote(n.startStep, n.
|
|
18764
|
+
track.core.addNote(n.startStep, n.pitchUnits, {
|
|
18160
18765
|
noteLengthSteps: n.durationSteps,
|
|
18161
18766
|
velocity: n.velocity
|
|
18162
18767
|
});
|
|
18163
18768
|
}
|
|
18164
18769
|
for (const r of removed) {
|
|
18165
|
-
const note = track.core.getNotes().find(
|
|
18770
|
+
const note = track.core.getNotes().find(
|
|
18771
|
+
(n) => n.startStep === r.startStep && n.pitchUnits === r.pitchUnits
|
|
18772
|
+
);
|
|
18166
18773
|
if (note) track.core.deleteNoteById(note.id);
|
|
18167
18774
|
}
|
|
18168
18775
|
track.core.endBatch();
|
|
@@ -18204,9 +18811,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
18204
18811
|
if (t.config.id === activeTrackId) updateTrackPanel();
|
|
18205
18812
|
},
|
|
18206
18813
|
noteToCanvas: (step, pitch) => {
|
|
18207
|
-
const canvas = getGridCanvas();
|
|
18814
|
+
const canvas = renderer.getGridCanvas();
|
|
18208
18815
|
const x2 = step * renderConfig.stepWidth - currentOffsetX;
|
|
18209
|
-
const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
|
|
18816
|
+
const y = (renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - currentOffsetY;
|
|
18210
18817
|
const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
|
|
18211
18818
|
let side = null;
|
|
18212
18819
|
if (!onScreen) {
|
|
@@ -18270,7 +18877,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18270
18877
|
id: id++,
|
|
18271
18878
|
startStep: p.startStep,
|
|
18272
18879
|
durationSteps: p.durationSteps,
|
|
18273
|
-
|
|
18880
|
+
pitchUnits: p.pitchUnits,
|
|
18274
18881
|
velocity: p.velocity
|
|
18275
18882
|
}));
|
|
18276
18883
|
return {
|
|
@@ -18296,7 +18903,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18296
18903
|
(a, b) => a.startStep - b.startStep
|
|
18297
18904
|
);
|
|
18298
18905
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
18299
|
-
const semis = (lt.octave ?? 0) *
|
|
18906
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
18300
18907
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
18301
18908
|
const notes = [];
|
|
18302
18909
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -18304,7 +18911,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18304
18911
|
if (n.startStep < fromStep) continue;
|
|
18305
18912
|
notes.push({
|
|
18306
18913
|
syllable: lt.syllables[i2],
|
|
18307
|
-
pitch: n.
|
|
18914
|
+
pitch: n.pitchUnits + semis,
|
|
18308
18915
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
18309
18916
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
18310
18917
|
});
|
|
@@ -18492,24 +19099,28 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18492
19099
|
config,
|
|
18493
19100
|
noteLengthSteps = 1
|
|
18494
19101
|
} = options;
|
|
18495
|
-
|
|
19102
|
+
const renderer = createRenderer(mountTarget, width, height, config);
|
|
18496
19103
|
let currentNoteLengthSteps = noteLengthSteps;
|
|
18497
19104
|
let selectionRect = null;
|
|
18498
19105
|
let isSelecting = false;
|
|
18499
19106
|
let selectionStart = null;
|
|
18500
19107
|
let selectedNotes = [];
|
|
18501
19108
|
let copiedNotes = [];
|
|
18502
|
-
const core = new MMLCore(
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
|
|
19109
|
+
const core = new MMLCore(
|
|
19110
|
+
{
|
|
19111
|
+
onMMLGenerated: handlers.onMMLGenerated,
|
|
19112
|
+
onNotesChanged: (notes) => {
|
|
19113
|
+
handlers.onNotesChanged(notes);
|
|
19114
|
+
}
|
|
19115
|
+
},
|
|
19116
|
+
80,
|
|
19117
|
+
() => config
|
|
19118
|
+
);
|
|
18508
19119
|
const getAddNoteOptions = () => ({
|
|
18509
19120
|
noteLengthSteps: currentNoteLengthSteps
|
|
18510
19121
|
});
|
|
18511
19122
|
let suppressClick = false;
|
|
18512
|
-
onClick((step, pitch) => {
|
|
19123
|
+
renderer.onClick((step, pitch) => {
|
|
18513
19124
|
if (suppressClick) {
|
|
18514
19125
|
suppressClick = false;
|
|
18515
19126
|
return;
|
|
@@ -18521,7 +19132,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18521
19132
|
} else if (mode === "eraser") {
|
|
18522
19133
|
const notes = core.getNotes();
|
|
18523
19134
|
const note = notes.find(
|
|
18524
|
-
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.
|
|
19135
|
+
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.pitchUnits === pitch
|
|
18525
19136
|
);
|
|
18526
19137
|
if (note) {
|
|
18527
19138
|
core.deleteNoteById(note.id);
|
|
@@ -18529,17 +19140,23 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18529
19140
|
}
|
|
18530
19141
|
}
|
|
18531
19142
|
});
|
|
18532
|
-
const gridCanvas = getGridCanvas();
|
|
19143
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
18533
19144
|
const resizeHandleWidth = 6;
|
|
18534
19145
|
let dragState = null;
|
|
18535
19146
|
let hasDragged = false;
|
|
18536
19147
|
let lastPreviewPitch = null;
|
|
18537
19148
|
const findNoteAtPosition = (x2, y) => {
|
|
18538
|
-
const {
|
|
18539
|
-
|
|
19149
|
+
const {
|
|
19150
|
+
stepWidth,
|
|
19151
|
+
keyHeight,
|
|
19152
|
+
keyCount,
|
|
19153
|
+
pitchRangeStart,
|
|
19154
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19155
|
+
} = renderer.getRenderConfig();
|
|
19156
|
+
const offset = renderer.getDrawOffset();
|
|
18540
19157
|
for (const note of core.getNotes()) {
|
|
18541
19158
|
const logicalX = note.startStep * stepWidth;
|
|
18542
|
-
const yIndex = keyCount - 1 - (note.
|
|
19159
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18543
19160
|
const logicalY = yIndex * keyHeight;
|
|
18544
19161
|
const w = note.durationSteps * stepWidth;
|
|
18545
19162
|
const h = keyHeight;
|
|
@@ -18553,7 +19170,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18553
19170
|
};
|
|
18554
19171
|
const handlePointerMove = (e) => {
|
|
18555
19172
|
if (core.getToolMode() === "select" && isSelecting && selectionStart) {
|
|
18556
|
-
const { x: x2, y } = getGridPosition(e);
|
|
19173
|
+
const { x: x2, y } = renderer.getGridPosition(e);
|
|
18557
19174
|
const minX = Math.min(x2, selectionStart.x);
|
|
18558
19175
|
const minY = Math.min(y, selectionStart.y);
|
|
18559
19176
|
const width2 = Math.abs(x2 - selectionStart.x);
|
|
@@ -18565,7 +19182,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18565
19182
|
}
|
|
18566
19183
|
if (!dragState) return;
|
|
18567
19184
|
hasDragged = true;
|
|
18568
|
-
const { step, pitch } = getGridPosition(e);
|
|
19185
|
+
const { step, pitch } = renderer.getGridPosition(e);
|
|
18569
19186
|
if (dragState.mode === "move") {
|
|
18570
19187
|
if (dragState.selectedNotes && dragState.selectedNotes.length > 0) {
|
|
18571
19188
|
const noteId = dragState.noteId;
|
|
@@ -18574,10 +19191,10 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18574
19191
|
const nextStart2 = step - dragState.dragOffsetStep;
|
|
18575
19192
|
const nextPitch2 = pitch - dragState.dragOffsetPitch;
|
|
18576
19193
|
const stepDelta = nextStart2 - baseNote.startStep;
|
|
18577
|
-
const pitchDelta = nextPitch2 - baseNote.
|
|
19194
|
+
const pitchDelta = nextPitch2 - baseNote.pitchUnits;
|
|
18578
19195
|
for (const note of dragState.selectedNotes) {
|
|
18579
19196
|
const newStart = note.startStep + stepDelta;
|
|
18580
|
-
const newPitch = note.
|
|
19197
|
+
const newPitch = note.pitchUnits + pitchDelta;
|
|
18581
19198
|
core.moveNote(note.id, newStart, newPitch);
|
|
18582
19199
|
}
|
|
18583
19200
|
if (options.onPreviewSound && pitch !== lastPreviewPitch) {
|
|
@@ -18618,7 +19235,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18618
19235
|
}
|
|
18619
19236
|
};
|
|
18620
19237
|
gridCanvas.addEventListener("pointerdown", (e) => {
|
|
18621
|
-
const { x: x2, y, step, pitch } = getGridPosition(e);
|
|
19238
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(e);
|
|
18622
19239
|
const currentMode = core.getToolMode();
|
|
18623
19240
|
if (currentMode === "select") {
|
|
18624
19241
|
const clickedNote = findNoteAtPosition(x2, y);
|
|
@@ -18629,7 +19246,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18629
19246
|
noteId: clickedNote.id,
|
|
18630
19247
|
mode: "move",
|
|
18631
19248
|
dragOffsetStep: step - clickedNote.startStep,
|
|
18632
|
-
dragOffsetPitch: pitch - clickedNote.
|
|
19249
|
+
dragOffsetPitch: pitch - clickedNote.pitchUnits,
|
|
18633
19250
|
startStep: clickedNote.startStep,
|
|
18634
19251
|
selectedNotes: notesInRect
|
|
18635
19252
|
// 複数選択ノートを保存
|
|
@@ -18647,10 +19264,16 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18647
19264
|
}
|
|
18648
19265
|
const note = findNoteAtPosition(x2, y);
|
|
18649
19266
|
if (!note) return;
|
|
18650
|
-
const {
|
|
18651
|
-
|
|
19267
|
+
const {
|
|
19268
|
+
stepWidth,
|
|
19269
|
+
keyHeight,
|
|
19270
|
+
keyCount,
|
|
19271
|
+
pitchRangeStart,
|
|
19272
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19273
|
+
} = renderer.getRenderConfig();
|
|
19274
|
+
const offset = renderer.getDrawOffset();
|
|
18652
19275
|
const logicalX = note.startStep * stepWidth;
|
|
18653
|
-
const yIndex = keyCount - 1 - (note.
|
|
19276
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18654
19277
|
const logicalY = yIndex * keyHeight;
|
|
18655
19278
|
const renderX = logicalX - offset.x;
|
|
18656
19279
|
const renderY = logicalY - offset.y;
|
|
@@ -18669,7 +19292,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18669
19292
|
noteId: note.id,
|
|
18670
19293
|
mode: "move",
|
|
18671
19294
|
dragOffsetStep: step - note.startStep,
|
|
18672
|
-
dragOffsetPitch: pitch - note.
|
|
19295
|
+
dragOffsetPitch: pitch - note.pitchUnits,
|
|
18673
19296
|
startStep: note.startStep
|
|
18674
19297
|
};
|
|
18675
19298
|
});
|
|
@@ -18680,41 +19303,47 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18680
19303
|
"wheel",
|
|
18681
19304
|
(e) => {
|
|
18682
19305
|
e.preventDefault();
|
|
18683
|
-
const configValues = getRenderConfig();
|
|
19306
|
+
const configValues = renderer.getRenderConfig();
|
|
18684
19307
|
const gridHeight = gridCanvas.height;
|
|
18685
19308
|
const maxOffsetY = Math.max(
|
|
18686
19309
|
0,
|
|
18687
19310
|
configValues.keyCount * configValues.keyHeight - gridHeight
|
|
18688
19311
|
);
|
|
18689
|
-
const currentOffset = getDrawOffset();
|
|
19312
|
+
const currentOffset = renderer.getDrawOffset();
|
|
18690
19313
|
const nextOffsetY = Math.min(
|
|
18691
19314
|
Math.max(currentOffset.y + e.deltaY, 0),
|
|
18692
19315
|
maxOffsetY
|
|
18693
19316
|
);
|
|
18694
|
-
setDrawOffset(currentOffset.x, nextOffsetY);
|
|
18695
|
-
drawGrid();
|
|
18696
|
-
drawNotes(core.getNotes());
|
|
19317
|
+
renderer.setDrawOffset(currentOffset.x, nextOffsetY);
|
|
19318
|
+
renderer.drawGrid();
|
|
19319
|
+
renderer.drawNotes(core.getNotes());
|
|
18697
19320
|
},
|
|
18698
19321
|
{ passive: false }
|
|
18699
19322
|
);
|
|
18700
19323
|
const redraw = () => {
|
|
18701
|
-
drawGrid();
|
|
18702
|
-
drawNotes(core.getNotes());
|
|
19324
|
+
renderer.drawGrid();
|
|
19325
|
+
renderer.drawNotes(core.getNotes());
|
|
18703
19326
|
if (core.getToolMode() === "select") {
|
|
18704
|
-
drawSelectionRect(selectionRect);
|
|
19327
|
+
renderer.drawSelectionRect(selectionRect);
|
|
18705
19328
|
if (selectedNotes.length > 0) {
|
|
18706
19329
|
const selectedIds = new Set(selectedNotes.map((n) => n.id));
|
|
18707
|
-
drawSelectedNotes(core.getNotes(), selectedIds);
|
|
19330
|
+
renderer.drawSelectedNotes(core.getNotes(), selectedIds);
|
|
18708
19331
|
}
|
|
18709
19332
|
}
|
|
18710
19333
|
};
|
|
18711
19334
|
const getNotesInRect = (rect) => {
|
|
18712
|
-
const {
|
|
18713
|
-
|
|
19335
|
+
const {
|
|
19336
|
+
stepWidth,
|
|
19337
|
+
keyHeight,
|
|
19338
|
+
keyCount,
|
|
19339
|
+
pitchRangeStart,
|
|
19340
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19341
|
+
} = renderer.getRenderConfig();
|
|
19342
|
+
const offset = renderer.getDrawOffset();
|
|
18714
19343
|
const notes = [];
|
|
18715
19344
|
for (const note of core.getNotes()) {
|
|
18716
19345
|
const logicalX = note.startStep * stepWidth;
|
|
18717
|
-
const yIndex = keyCount - 1 - (note.
|
|
19346
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18718
19347
|
const logicalY = yIndex * keyHeight;
|
|
18719
19348
|
const noteRect = {
|
|
18720
19349
|
x: logicalX - offset.x,
|
|
@@ -18761,7 +19390,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18761
19390
|
const minStart = Math.min(...copiedNotes.map((n) => n.startStep));
|
|
18762
19391
|
copiedNotes.forEach((note) => {
|
|
18763
19392
|
const newStep = startStep + (note.startStep - minStart);
|
|
18764
|
-
core.addNote(newStep, note.
|
|
19393
|
+
core.addNote(newStep, note.pitchUnits, {
|
|
18765
19394
|
noteLengthSteps: note.durationSteps,
|
|
18766
19395
|
velocity: note.velocity
|
|
18767
19396
|
});
|
|
@@ -19602,10 +20231,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
19602
20231
|
);
|
|
19603
20232
|
}
|
|
19604
20233
|
if (!sfInst) return;
|
|
20234
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
19605
20235
|
sfInst.play({
|
|
19606
20236
|
ctx: audioCtx,
|
|
19607
20237
|
destination: getChannelStrip(e.trackId).input,
|
|
19608
|
-
pitch:
|
|
20238
|
+
pitch: midi,
|
|
20239
|
+
detuneCents,
|
|
19609
20240
|
volume: e.volume,
|
|
19610
20241
|
velocity: e.velocity,
|
|
19611
20242
|
when: e.when,
|
|
@@ -19921,10 +20552,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
19921
20552
|
);
|
|
19922
20553
|
}
|
|
19923
20554
|
if (!sfInst) return;
|
|
20555
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
19924
20556
|
sfInst.play({
|
|
19925
20557
|
ctx: audioCtx,
|
|
19926
20558
|
destination: getChannelStrip(e.trackId).input,
|
|
19927
|
-
pitch:
|
|
20559
|
+
pitch: midi,
|
|
20560
|
+
detuneCents,
|
|
19928
20561
|
volume: e.volume,
|
|
19929
20562
|
velocity: e.velocity,
|
|
19930
20563
|
when: e.when,
|
|
@@ -20010,10 +20643,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20010
20643
|
);
|
|
20011
20644
|
}
|
|
20012
20645
|
if (!sfInst) return;
|
|
20646
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20013
20647
|
sfInst.play({
|
|
20014
20648
|
ctx: audioCtx,
|
|
20015
20649
|
destination: getChannelStrip(e.trackId).input,
|
|
20016
|
-
pitch:
|
|
20650
|
+
pitch: midi,
|
|
20651
|
+
detuneCents,
|
|
20017
20652
|
volume: e.volume,
|
|
20018
20653
|
velocity: e.velocity,
|
|
20019
20654
|
when: e.when,
|
|
@@ -20092,10 +20727,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20092
20727
|
);
|
|
20093
20728
|
}
|
|
20094
20729
|
if (!sfInst) return;
|
|
20730
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20095
20731
|
sfInst.play({
|
|
20096
20732
|
ctx: audioCtx,
|
|
20097
20733
|
destination: getChannelStrip(e.trackId).input,
|
|
20098
|
-
pitch:
|
|
20734
|
+
pitch: midi,
|
|
20735
|
+
detuneCents,
|
|
20099
20736
|
volume: e.volume,
|
|
20100
20737
|
velocity: e.velocity,
|
|
20101
20738
|
when: e.when,
|
|
@@ -20132,10 +20769,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20132
20769
|
sfInst = resolveSoundFont(defaultPreset, role, "simple");
|
|
20133
20770
|
}
|
|
20134
20771
|
if (!sfInst) return;
|
|
20772
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20135
20773
|
sfInst.play({
|
|
20136
20774
|
ctx: audioCtx,
|
|
20137
20775
|
destination: getChannelStrip(e.trackId).input,
|
|
20138
|
-
pitch:
|
|
20776
|
+
pitch: midi,
|
|
20777
|
+
detuneCents,
|
|
20139
20778
|
volume: e.volume,
|
|
20140
20779
|
velocity: e.velocity,
|
|
20141
20780
|
when: e.when,
|
|
@@ -20158,10 +20797,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20158
20797
|
if (audioCtx.state === "suspended") {
|
|
20159
20798
|
await audioCtx.resume();
|
|
20160
20799
|
}
|
|
20800
|
+
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
20161
20801
|
sfInst.play({
|
|
20162
20802
|
ctx: audioCtx,
|
|
20163
20803
|
destination: masterGain,
|
|
20164
|
-
pitch:
|
|
20804
|
+
pitch: midi,
|
|
20805
|
+
detuneCents,
|
|
20165
20806
|
volume: vol / 100,
|
|
20166
20807
|
when: 0,
|
|
20167
20808
|
duration: dur
|
|
@@ -20181,7 +20822,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20181
20822
|
// 伴奏トラック
|
|
20182
20823
|
startStep: p.startStep,
|
|
20183
20824
|
durationSteps: p.durationSteps,
|
|
20184
|
-
|
|
20825
|
+
pitchUnits: p.pitchUnits,
|
|
20185
20826
|
velocity: p.velocity
|
|
20186
20827
|
}));
|
|
20187
20828
|
const playerPreset = defaultPreset;
|
|
@@ -20189,10 +20830,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20189
20830
|
const playPlayerNote = (e) => {
|
|
20190
20831
|
const sfInst = resolveSoundFont(playerPreset, "chord");
|
|
20191
20832
|
if (!sfInst) return;
|
|
20833
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20192
20834
|
sfInst.play({
|
|
20193
20835
|
ctx: audioCtx,
|
|
20194
20836
|
destination: getChannelStrip("chord").input,
|
|
20195
|
-
pitch:
|
|
20837
|
+
pitch: midi,
|
|
20838
|
+
detuneCents,
|
|
20196
20839
|
volume: e.volume,
|
|
20197
20840
|
velocity: e.velocity,
|
|
20198
20841
|
when: e.when,
|
|
@@ -20330,8 +20973,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20330
20973
|
};
|
|
20331
20974
|
};
|
|
20332
20975
|
export {
|
|
20976
|
+
A4_HZ,
|
|
20977
|
+
A4_UNITS,
|
|
20978
|
+
CENTS_PER_UNIT,
|
|
20333
20979
|
DAW_CSS,
|
|
20334
20980
|
DEFAULT_BPM,
|
|
20981
|
+
DEFAULT_EDO,
|
|
20335
20982
|
DEFAULT_GATE,
|
|
20336
20983
|
DEFAULT_PAN,
|
|
20337
20984
|
DEFAULT_PLAYBACK_VELOCITY,
|
|
@@ -20341,21 +20988,30 @@ export {
|
|
|
20341
20988
|
DRUM_FONT,
|
|
20342
20989
|
DRUM_KEYS,
|
|
20343
20990
|
DRUM_PATTERNS,
|
|
20991
|
+
EDO31_NAMES,
|
|
20344
20992
|
GM_INSTRUMENT_NAMES,
|
|
20345
20993
|
INSTRUMENT_PRESETS,
|
|
20994
|
+
KEY_COUNT,
|
|
20346
20995
|
KOE_BASE_URL,
|
|
20347
20996
|
KOE_VOICEBANKS,
|
|
20348
20997
|
KOE_VOICEBANK_LABELS,
|
|
20349
20998
|
KOE_VOICEBANK_TERMS,
|
|
20350
20999
|
LinkedList,
|
|
20351
21000
|
MAX_VOCAL_VOLUME,
|
|
21001
|
+
MICRO_STEP,
|
|
20352
21002
|
MMLCore,
|
|
20353
21003
|
MML_END_MARKER,
|
|
20354
21004
|
MidiSearchClient,
|
|
20355
|
-
|
|
21005
|
+
PITCH_ENCODING_VERSION,
|
|
21006
|
+
PITCH_MAP,
|
|
21007
|
+
PITCH_RANGE_END,
|
|
21008
|
+
PITCH_RANGE_START,
|
|
20356
21009
|
PREWARM_NOTES,
|
|
20357
21010
|
TRACKS_ADVANCED,
|
|
20358
21011
|
TRACKS_SIMPLE,
|
|
21012
|
+
UNITS_PER_EDO31_DEGREE,
|
|
21013
|
+
UNITS_PER_OCTAVE,
|
|
21014
|
+
UNITS_PER_SEMITONE,
|
|
20359
21015
|
VIBRATO_MIN_SEC,
|
|
20360
21016
|
VOICE_IMAGES,
|
|
20361
21017
|
VOICE_IMAGE_KEY,
|
|
@@ -20365,6 +21021,7 @@ export {
|
|
|
20365
21021
|
buildChordPlacements,
|
|
20366
21022
|
buildDrumPatternJson,
|
|
20367
21023
|
buildNameToKeyMapping,
|
|
21024
|
+
chromaticStep,
|
|
20368
21025
|
collectPitchTokens,
|
|
20369
21026
|
concatFloat32,
|
|
20370
21027
|
createAudioContext,
|
|
@@ -20373,19 +21030,13 @@ export {
|
|
|
20373
21030
|
createKoeVoice,
|
|
20374
21031
|
createLyricsConductor,
|
|
20375
21032
|
createPianoRoll,
|
|
21033
|
+
createRenderer,
|
|
20376
21034
|
createSequencer,
|
|
20377
21035
|
createSingingVoices,
|
|
20378
21036
|
createSynth,
|
|
20379
21037
|
createVoiceRegistry,
|
|
20380
21038
|
decodeMml,
|
|
20381
21039
|
decomposeToMonophonic,
|
|
20382
|
-
drawGrid,
|
|
20383
|
-
drawHeader,
|
|
20384
|
-
drawKeyboard,
|
|
20385
|
-
drawNoteLyrics,
|
|
20386
|
-
drawNotes,
|
|
20387
|
-
drawSelectedNotes,
|
|
20388
|
-
drawSelectionRect,
|
|
20389
21040
|
encodeMml,
|
|
20390
21041
|
encodeWavPCM16,
|
|
20391
21042
|
exportMIDI,
|
|
@@ -20393,36 +21044,34 @@ export {
|
|
|
20393
21044
|
extractMidiDrumPattern,
|
|
20394
21045
|
extractMidiPlacements,
|
|
20395
21046
|
extractMidiPlacementsByTrack,
|
|
21047
|
+
fifthToStep,
|
|
21048
|
+
fifthToUnits,
|
|
20396
21049
|
formatMmlMeta,
|
|
20397
21050
|
freqFromPitch,
|
|
20398
21051
|
generateRandomPattern,
|
|
20399
|
-
getDrawOffset,
|
|
20400
21052
|
getDrumPatternKeys,
|
|
20401
|
-
getGridCanvas,
|
|
20402
|
-
getGridContext,
|
|
20403
|
-
getGridPosition,
|
|
20404
|
-
getHeaderCanvas,
|
|
20405
21053
|
getMidiBPM,
|
|
20406
|
-
getRenderConfig,
|
|
20407
|
-
getXY,
|
|
20408
21054
|
icon,
|
|
20409
|
-
init,
|
|
20410
21055
|
injectStyles,
|
|
20411
21056
|
isChordHeavyTrack,
|
|
21057
|
+
isNaturalLetter,
|
|
20412
21058
|
isPlausibleMidiTranscription,
|
|
20413
21059
|
isValidHttpUrl,
|
|
21060
|
+
keyCountFor,
|
|
20414
21061
|
koeUrl,
|
|
21062
|
+
midiToUnits,
|
|
20415
21063
|
mountChordPlayer,
|
|
20416
21064
|
mountDAW,
|
|
20417
21065
|
mountMmlPlayer,
|
|
21066
|
+
naturalStep,
|
|
20418
21067
|
normalizeDrumPatterns,
|
|
20419
21068
|
normalizeLyrics,
|
|
20420
|
-
onClick,
|
|
20421
21069
|
panToStereo,
|
|
20422
21070
|
parseCustomVocals,
|
|
20423
21071
|
parseLyrics,
|
|
20424
21072
|
parseMML,
|
|
20425
21073
|
parseMmlMeta,
|
|
21074
|
+
pitchV1ToUnits,
|
|
20426
21075
|
playChords,
|
|
20427
21076
|
playMML,
|
|
20428
21077
|
playNote,
|
|
@@ -20430,13 +21079,18 @@ export {
|
|
|
20430
21079
|
playSingingMML,
|
|
20431
21080
|
resolveDrumPattern,
|
|
20432
21081
|
resolveLoopPoint,
|
|
20433
|
-
setBackgroundActive,
|
|
20434
|
-
setDrawOffset,
|
|
20435
21082
|
shiftNotes,
|
|
20436
21083
|
showLoadingOverlay,
|
|
21084
|
+
spellingToUnits,
|
|
20437
21085
|
stripCustomVocals,
|
|
20438
21086
|
stripLyrics,
|
|
20439
21087
|
stripMmlMeta,
|
|
20440
21088
|
transposeNotes,
|
|
21089
|
+
unitsPerRow,
|
|
21090
|
+
unitsPerStep,
|
|
21091
|
+
unitsToHz,
|
|
21092
|
+
unitsToMidi,
|
|
21093
|
+
unitsToMidiDetune,
|
|
21094
|
+
unitsToPitchV1,
|
|
20441
21095
|
vocalVolumeToGain
|
|
20442
21096
|
};
|