@onjmin/dtm 1.0.8 → 2.0.3
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 +152 -0
- package/dist/index.d.mts +303 -107
- package/dist/index.d.ts +303 -107
- package/dist/index.js +1301 -738
- package/dist/index.mjs +1269 -720
- 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);
|
|
434
|
+
};
|
|
435
|
+
var add = (chord, n, half, out) => {
|
|
436
|
+
put(chord, out, n, half);
|
|
389
437
|
};
|
|
390
|
-
var aug = (chord) => {
|
|
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,77 @@ var createChannelStrip = (ctx, destination, options = {}) => {
|
|
|
1499
1568
|
};
|
|
1500
1569
|
};
|
|
1501
1570
|
|
|
1571
|
+
// src/tuning.ts
|
|
1572
|
+
var units = (n) => n;
|
|
1573
|
+
var midiNote = (n) => n;
|
|
1574
|
+
var addUnits = (a, delta) => a + delta;
|
|
1575
|
+
var UNITS_PER_OCTAVE = 372;
|
|
1576
|
+
var UNITS_PER_SEMITONE = 372 / 12;
|
|
1577
|
+
var UNITS_PER_EDO31_DEGREE = 372 / 31;
|
|
1578
|
+
var CENTS_PER_UNIT = 1200 / UNITS_PER_OCTAVE;
|
|
1579
|
+
var A4_UNITS = 69 * UNITS_PER_SEMITONE;
|
|
1580
|
+
var A4_HZ = 440;
|
|
1581
|
+
var DEFAULT_EDO = 12;
|
|
1582
|
+
var unitsPerStep = (edo) => UNITS_PER_OCTAVE / edo;
|
|
1583
|
+
var midiToUnits = (midi) => midi * UNITS_PER_SEMITONE;
|
|
1584
|
+
var unitsToMidi = (u) => u / UNITS_PER_SEMITONE;
|
|
1585
|
+
var unitsToHz = (u) => A4_HZ * 2 ** ((u - A4_UNITS) / UNITS_PER_OCTAVE);
|
|
1586
|
+
var unitsToMidiDetune = (u) => {
|
|
1587
|
+
const midi = Math.round(u / UNITS_PER_SEMITONE);
|
|
1588
|
+
return {
|
|
1589
|
+
midi,
|
|
1590
|
+
detuneCents: (u - midi * UNITS_PER_SEMITONE) * CENTS_PER_UNIT
|
|
1591
|
+
};
|
|
1592
|
+
};
|
|
1593
|
+
var NATURAL_STEPS = {
|
|
1594
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
1595
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
1596
|
+
};
|
|
1597
|
+
var chromaticStep = (edo) => edo === 31 ? 2 : 1;
|
|
1598
|
+
var MICRO_STEP = 1;
|
|
1599
|
+
var isNaturalLetter = (ch) => Object.hasOwn(NATURAL_STEPS[12], ch);
|
|
1600
|
+
var naturalStep = (ch, edo) => NATURAL_STEPS[edo][ch] ?? null;
|
|
1601
|
+
var spellingToUnits = (letter, octave, chromatic, micro, edo) => {
|
|
1602
|
+
const step = naturalStep(letter, edo);
|
|
1603
|
+
if (step === null) return null;
|
|
1604
|
+
const perStep = unitsPerStep(edo);
|
|
1605
|
+
const stepsFromC = step + chromatic * chromaticStep(edo) + micro * MICRO_STEP;
|
|
1606
|
+
return (octave + 1) * UNITS_PER_OCTAVE + stepsFromC * perStep;
|
|
1607
|
+
};
|
|
1608
|
+
var FIFTH_STEPS = { 12: 7, 31: 18 };
|
|
1609
|
+
var fifthToStep = (fifthIndex, edo) => {
|
|
1610
|
+
const n = fifthIndex * FIFTH_STEPS[edo];
|
|
1611
|
+
return (n % edo + edo) % edo;
|
|
1612
|
+
};
|
|
1613
|
+
var fifthToUnits = (fifthIndex, edo) => fifthToStep(fifthIndex, edo) * unitsPerStep(edo);
|
|
1614
|
+
var PITCH_ENCODING_VERSION = 2;
|
|
1615
|
+
var pitchV1ToUnits = (pitch) => pitch * UNITS_PER_SEMITONE;
|
|
1616
|
+
var unitsToPitchV1 = (u) => Math.round(u / UNITS_PER_SEMITONE);
|
|
1617
|
+
|
|
1502
1618
|
// src/chords.ts
|
|
1503
1619
|
var C3 = 48;
|
|
1620
|
+
var MEANTONE_STEP_BY_SEMITONE = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
1621
|
+
var chordToneToUnits = (relSemitone, relFifth, rootSemitone, rootFifth, edo) => {
|
|
1622
|
+
const perStep = UNITS_PER_OCTAVE / edo;
|
|
1623
|
+
const rootOct = Math.floor(rootSemitone / 12);
|
|
1624
|
+
const rootWithin = (rootSemitone % 12 + 12) % 12;
|
|
1625
|
+
const rootStep = edo === 31 ? (fifthToStep(rootFifth, 31) + MEANTONE_STEP_BY_SEMITONE[rootWithin] - fifthToStep(rootFifth, 31) + 31) % 31 : rootWithin;
|
|
1626
|
+
const relWithin12 = (relSemitone % 12 + 12) % 12;
|
|
1627
|
+
const relOct = Math.round((relSemitone - relWithin12) / 12);
|
|
1628
|
+
const relStep = (fifthToStep(relFifth, edo) - fifthToStep(rootFifth, edo) + edo) % edo;
|
|
1629
|
+
return units(
|
|
1630
|
+
(rootOct + relOct) * UNITS_PER_OCTAVE + (rootStep + relStep) * perStep
|
|
1631
|
+
);
|
|
1632
|
+
};
|
|
1504
1633
|
var buildChordPlacements = (options) => {
|
|
1505
|
-
const {
|
|
1634
|
+
const {
|
|
1635
|
+
chordStr,
|
|
1636
|
+
patternType,
|
|
1637
|
+
rootShift,
|
|
1638
|
+
bpm,
|
|
1639
|
+
stepsPerBar,
|
|
1640
|
+
edo = 12
|
|
1641
|
+
} = options;
|
|
1506
1642
|
const placements = [];
|
|
1507
1643
|
if (!chordStr.trim()) return placements;
|
|
1508
1644
|
const offset = rootShift;
|
|
@@ -1531,8 +1667,20 @@ var buildChordPlacements = (options) => {
|
|
|
1531
1667
|
for (const group of Object.values(chordGroups)) {
|
|
1532
1668
|
for (const chord of group) {
|
|
1533
1669
|
let notes;
|
|
1670
|
+
let toUnits;
|
|
1534
1671
|
try {
|
|
1535
|
-
|
|
1672
|
+
const parsed = parseChord(`${chord.key}${chord.chord}`);
|
|
1673
|
+
notes = [...parsed.notes];
|
|
1674
|
+
const fifthOf = new Map(
|
|
1675
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1676
|
+
);
|
|
1677
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1678
|
+
rel,
|
|
1679
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1680
|
+
C3 + offset,
|
|
1681
|
+
parsed.rootFifth,
|
|
1682
|
+
edo === 31 ? 31 : 12
|
|
1683
|
+
);
|
|
1536
1684
|
} catch {
|
|
1537
1685
|
continue;
|
|
1538
1686
|
}
|
|
@@ -1541,7 +1689,7 @@ var buildChordPlacements = (options) => {
|
|
|
1541
1689
|
for (const noteOffset of notes) {
|
|
1542
1690
|
placements.push({
|
|
1543
1691
|
startStep: chord.whenStep,
|
|
1544
|
-
|
|
1692
|
+
pitchUnits: toUnits(noteOffset),
|
|
1545
1693
|
durationSteps: noteLength,
|
|
1546
1694
|
velocity: 100
|
|
1547
1695
|
});
|
|
@@ -1551,7 +1699,7 @@ var buildChordPlacements = (options) => {
|
|
|
1551
1699
|
notes.forEach((noteOffset, i2) => {
|
|
1552
1700
|
placements.push({
|
|
1553
1701
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1554
|
-
|
|
1702
|
+
pitchUnits: toUnits(noteOffset),
|
|
1555
1703
|
durationSteps: noteLength - i2 * arpInterval,
|
|
1556
1704
|
velocity: 100
|
|
1557
1705
|
});
|
|
@@ -1561,7 +1709,7 @@ var buildChordPlacements = (options) => {
|
|
|
1561
1709
|
notes.forEach((noteOffset, i2) => {
|
|
1562
1710
|
placements.push({
|
|
1563
1711
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1564
|
-
|
|
1712
|
+
pitchUnits: toUnits(noteOffset),
|
|
1565
1713
|
durationSteps: Math.max(12, noteLength - i2 * arpInterval),
|
|
1566
1714
|
velocity: 100
|
|
1567
1715
|
});
|
|
@@ -1575,7 +1723,7 @@ var buildChordPlacements = (options) => {
|
|
|
1575
1723
|
for (const noteOffset of notes) {
|
|
1576
1724
|
placements.push({
|
|
1577
1725
|
startStep: syncopatedStep,
|
|
1578
|
-
|
|
1726
|
+
pitchUnits: toUnits(noteOffset),
|
|
1579
1727
|
durationSteps: Math.min(halfBeat, 12),
|
|
1580
1728
|
velocity: 100
|
|
1581
1729
|
});
|
|
@@ -1594,7 +1742,7 @@ var buildChordPlacements = (options) => {
|
|
|
1594
1742
|
for (const noteOffset of notes) {
|
|
1595
1743
|
placements.push({
|
|
1596
1744
|
startStep: noteStart,
|
|
1597
|
-
|
|
1745
|
+
pitchUnits: toUnits(noteOffset),
|
|
1598
1746
|
durationSteps: yatsumeLengthSteps,
|
|
1599
1747
|
velocity: 100
|
|
1600
1748
|
});
|
|
@@ -1606,7 +1754,7 @@ var buildChordPlacements = (options) => {
|
|
|
1606
1754
|
const stepOffset = i2 * Math.floor(stepsPerBar / 4);
|
|
1607
1755
|
placements.push({
|
|
1608
1756
|
startStep: chord.whenStep + stepOffset,
|
|
1609
|
-
|
|
1757
|
+
pitchUnits: toUnits(noteOffset),
|
|
1610
1758
|
durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
|
|
1611
1759
|
velocity: 100
|
|
1612
1760
|
});
|
|
@@ -1618,8 +1766,20 @@ var buildChordPlacements = (options) => {
|
|
|
1618
1766
|
const chordNames = chordStr.split(/[\s,]+/).filter((c) => c);
|
|
1619
1767
|
chordNames.forEach((chordName, barIndex) => {
|
|
1620
1768
|
let notes;
|
|
1769
|
+
let toUnits;
|
|
1621
1770
|
try {
|
|
1622
|
-
|
|
1771
|
+
const parsed = parseChord(chordName);
|
|
1772
|
+
notes = [...parsed.notes];
|
|
1773
|
+
const fifthOf = new Map(
|
|
1774
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1775
|
+
);
|
|
1776
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1777
|
+
rel,
|
|
1778
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1779
|
+
C3 + offset,
|
|
1780
|
+
parsed.rootFifth,
|
|
1781
|
+
edo === 31 ? 31 : 12
|
|
1782
|
+
);
|
|
1623
1783
|
} catch {
|
|
1624
1784
|
return;
|
|
1625
1785
|
}
|
|
@@ -1629,7 +1789,7 @@ var buildChordPlacements = (options) => {
|
|
|
1629
1789
|
const stepOffset = i2 * 3;
|
|
1630
1790
|
placements.push({
|
|
1631
1791
|
startStep: startStep + stepOffset,
|
|
1632
|
-
|
|
1792
|
+
pitchUnits: toUnits(noteOffset),
|
|
1633
1793
|
durationSteps: chordLength - stepOffset,
|
|
1634
1794
|
velocity: 100
|
|
1635
1795
|
});
|
|
@@ -2044,7 +2204,7 @@ var Worldline = class _Worldline {
|
|
|
2044
2204
|
const WL = this.wasm;
|
|
2045
2205
|
const FS = WORLDLINE_SAMPLE_RATE;
|
|
2046
2206
|
const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
|
|
2047
|
-
const
|
|
2207
|
+
const midiNote2 = Math.round(69 + 12 * Math.log2(basePitch / 440));
|
|
2048
2208
|
const posMs = 0;
|
|
2049
2209
|
const reqLen = preMs + durationMs;
|
|
2050
2210
|
const cutMs = WL_FRAME_MS * 2;
|
|
@@ -2068,7 +2228,7 @@ var Worldline = class _Worldline {
|
|
|
2068
2228
|
sv(8, samplePtr, "*");
|
|
2069
2229
|
sv(12, 0, "i32");
|
|
2070
2230
|
sv(16, 0, "*");
|
|
2071
|
-
sv(20,
|
|
2231
|
+
sv(20, midiNote2, "i32");
|
|
2072
2232
|
sv(24, 100, "double");
|
|
2073
2233
|
sv(32, 0, "double");
|
|
2074
2234
|
sv(40, reqLen, "double");
|
|
@@ -2161,6 +2321,11 @@ var DEFAULT_VELOCITY = 100;
|
|
|
2161
2321
|
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
2162
2322
|
var DEFAULT_STEPS_PER_BAR = 192;
|
|
2163
2323
|
var MML_END_MARKER = "#end;";
|
|
2324
|
+
var PITCH_RANGE_START = 0;
|
|
2325
|
+
var PITCH_RANGE_END = 3937;
|
|
2326
|
+
var unitsPerRow = (edo) => 372 / edo;
|
|
2327
|
+
var keyCountFor = (edo) => Math.floor((PITCH_RANGE_END - PITCH_RANGE_START) / unitsPerRow(edo)) + 1;
|
|
2328
|
+
var KEY_COUNT = keyCountFor(12);
|
|
2164
2329
|
|
|
2165
2330
|
// src/vibrato.ts
|
|
2166
2331
|
var VIBRATO_MIN_SEC = 0.35;
|
|
@@ -2560,7 +2725,8 @@ var FORMANTS = {
|
|
|
2560
2725
|
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
2561
2726
|
N: [250, 1e3]
|
|
2562
2727
|
};
|
|
2563
|
-
var
|
|
2728
|
+
var unitsToFreq = (units2) => 440 * 2 ** ((units2 - 2139) / 372);
|
|
2729
|
+
var unitsToMidiFloat = (units2) => units2 / 31;
|
|
2564
2730
|
var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
2565
2731
|
const active = /* @__PURE__ */ new Set();
|
|
2566
2732
|
const voice = (syllable, e) => {
|
|
@@ -2594,7 +2760,7 @@ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
|
2594
2760
|
}
|
|
2595
2761
|
const osc = ctx.createOscillator();
|
|
2596
2762
|
osc.type = "sawtooth";
|
|
2597
|
-
osc.frequency.value =
|
|
2763
|
+
osc.frequency.value = unitsToFreq(e.pitchUnits);
|
|
2598
2764
|
const makeFormant = (freq, q2, gainScale) => {
|
|
2599
2765
|
const filter = ctx.createBiquadFilter();
|
|
2600
2766
|
filter.type = "bandpass";
|
|
@@ -2846,7 +3012,7 @@ var createLocalBackend = async (options) => {
|
|
|
2846
3012
|
if (!consonantPcm || !vowelPcm) return null;
|
|
2847
3013
|
const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
|
|
2848
3014
|
if (!spliced) return null;
|
|
2849
|
-
const targetHz =
|
|
3015
|
+
const targetHz = unitsToFreq(pitch);
|
|
2850
3016
|
const audio = worldline.renderNote({
|
|
2851
3017
|
pcm: spliced.pcm,
|
|
2852
3018
|
pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
|
|
@@ -2875,7 +3041,7 @@ var createLocalBackend = async (options) => {
|
|
|
2875
3041
|
if (!pcm || pcm.length === 0) return null;
|
|
2876
3042
|
const entry = bank.manifest.phonemes[alias];
|
|
2877
3043
|
const lead = leadInFromEntry(entry);
|
|
2878
|
-
const targetHz =
|
|
3044
|
+
const targetHz = unitsToFreq(pitch);
|
|
2879
3045
|
if (worldline) {
|
|
2880
3046
|
const audio = worldline.renderNote({
|
|
2881
3047
|
pcm,
|
|
@@ -3082,7 +3248,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3082
3248
|
backend.pitchTokens,
|
|
3083
3249
|
syllable,
|
|
3084
3250
|
prevVowel,
|
|
3085
|
-
e.
|
|
3251
|
+
unitsToMidiFloat(e.pitchUnits)
|
|
3086
3252
|
);
|
|
3087
3253
|
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
3088
3254
|
if (!alias) return;
|
|
@@ -3090,7 +3256,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3090
3256
|
const peak = Math.max(1e-4, e.volume);
|
|
3091
3257
|
const pan = e.pan ?? 0;
|
|
3092
3258
|
const durationMs = Math.max(60, e.duration * 1e3);
|
|
3093
|
-
void renderInto(alias, e.
|
|
3259
|
+
void renderInto(alias, e.pitchUnits, durationMs).then((r) => {
|
|
3094
3260
|
if (r)
|
|
3095
3261
|
schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
|
|
3096
3262
|
});
|
|
@@ -3102,7 +3268,10 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3102
3268
|
backend.pitchTokens,
|
|
3103
3269
|
syllable,
|
|
3104
3270
|
prevVowelArg,
|
|
3105
|
-
|
|
3271
|
+
// 多音階バンクのピッチトークン("_G4" 等)は録音の音名なので、
|
|
3272
|
+
// 最寄り選択はMIDIノート番号の尺度で行う。units のまま渡すと常に
|
|
3273
|
+
// 最高音のトークンが選ばれてしまう。
|
|
3274
|
+
unitsToMidiFloat(pitch)
|
|
3106
3275
|
);
|
|
3107
3276
|
if (!alias) return null;
|
|
3108
3277
|
const dMs = Math.max(60, durationMs);
|
|
@@ -3134,13 +3303,14 @@ var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
|
3134
3303
|
var STREAM_POLL_MS = 100;
|
|
3135
3304
|
var OCTAVE_UNISON_PEAK_SCALE = 0.6;
|
|
3136
3305
|
var octaveUnisonOffsets = (mode) => {
|
|
3306
|
+
const OCT = 372;
|
|
3137
3307
|
switch (mode) {
|
|
3138
3308
|
case "down":
|
|
3139
|
-
return [-
|
|
3309
|
+
return [-OCT];
|
|
3140
3310
|
case "up":
|
|
3141
|
-
return [
|
|
3311
|
+
return [OCT];
|
|
3142
3312
|
case "both":
|
|
3143
|
-
return [-
|
|
3313
|
+
return [-OCT, OCT];
|
|
3144
3314
|
default:
|
|
3145
3315
|
return [];
|
|
3146
3316
|
}
|
|
@@ -3350,7 +3520,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3350
3520
|
const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
|
|
3351
3521
|
model(note.syllable, {
|
|
3352
3522
|
trackId: track.id ?? "",
|
|
3353
|
-
pitch,
|
|
3523
|
+
pitchUnits: pitch,
|
|
3354
3524
|
velocity: 100,
|
|
3355
3525
|
volume: effPeak,
|
|
3356
3526
|
when,
|
|
@@ -3365,7 +3535,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3365
3535
|
};
|
|
3366
3536
|
dispatchNote(note.pitch, 1);
|
|
3367
3537
|
for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
|
|
3368
|
-
dispatchNote(note.pitch + offset, OCTAVE_UNISON_PEAK_SCALE);
|
|
3538
|
+
dispatchNote(units(note.pitch + offset), OCTAVE_UNISON_PEAK_SCALE);
|
|
3369
3539
|
}
|
|
3370
3540
|
if (!(model.renderToCache && model.scheduleCached)) {
|
|
3371
3541
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -3416,17 +3586,13 @@ var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
|
3416
3586
|
};
|
|
3417
3587
|
|
|
3418
3588
|
// 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
|
|
3589
|
+
var NATURAL_STEPS2 = {
|
|
3590
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
3591
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
3427
3592
|
};
|
|
3593
|
+
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
3428
3594
|
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;
|
|
3595
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
|
|
3430
3596
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
3431
3597
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
3432
3598
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -3476,6 +3642,9 @@ var parseMmlMeta = (mml) => {
|
|
|
3476
3642
|
if (m[2] === "simple" || m[2] === "advanced") {
|
|
3477
3643
|
meta.mode = m[2];
|
|
3478
3644
|
}
|
|
3645
|
+
} else if (key === "edo") {
|
|
3646
|
+
const e = Number.parseInt(m[2], 10);
|
|
3647
|
+
if (e === 12 || e === 31) meta.edo = e;
|
|
3479
3648
|
}
|
|
3480
3649
|
}
|
|
3481
3650
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -3578,6 +3747,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
3578
3747
|
if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
|
|
3579
3748
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
3580
3749
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
3750
|
+
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
3581
3751
|
if (meta.trackInstruments) {
|
|
3582
3752
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
3583
3753
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -3652,6 +3822,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
3652
3822
|
const endMarkerBase = MML_END_MARKER.replace(/;+$/, "");
|
|
3653
3823
|
const endRegex = new RegExp(`(?<![cdafgCDAFG])${endMarkerBase}\\b;?`, "gi");
|
|
3654
3824
|
const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
|
|
3825
|
+
const edo = meta.edo === 31 ? 31 : 12;
|
|
3826
|
+
const naturals = NATURAL_STEPS2[edo];
|
|
3827
|
+
const unitsPerStep2 = 372 / edo;
|
|
3828
|
+
const chromaticStep2 = edo === 31 ? 2 : 1;
|
|
3655
3829
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
3656
3830
|
let trackIndex = 0;
|
|
3657
3831
|
let sourceTrackIndex = 0;
|
|
@@ -3699,6 +3873,19 @@ var parseMML = (mml, options = {}) => {
|
|
|
3699
3873
|
type
|
|
3700
3874
|
});
|
|
3701
3875
|
};
|
|
3876
|
+
const readAccidentals = () => {
|
|
3877
|
+
let delta = 0;
|
|
3878
|
+
while (j < body.length) {
|
|
3879
|
+
const a = body[j];
|
|
3880
|
+
if (a === "#") delta += chromaticStep2;
|
|
3881
|
+
else if (a === "-") delta -= chromaticStep2;
|
|
3882
|
+
else if (a === "+") delta += edo === 31 ? 1 : chromaticStep2;
|
|
3883
|
+
else if (a === "_") delta -= edo === 31 ? 1 : chromaticStep2;
|
|
3884
|
+
else break;
|
|
3885
|
+
j++;
|
|
3886
|
+
}
|
|
3887
|
+
return delta;
|
|
3888
|
+
};
|
|
3702
3889
|
const parseLength = () => {
|
|
3703
3890
|
let numStr = "";
|
|
3704
3891
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -3770,17 +3957,13 @@ var parseMML = (mml, options = {}) => {
|
|
|
3770
3957
|
const savedOctave = octave;
|
|
3771
3958
|
while (j < body.length && body[j] !== "]") {
|
|
3772
3959
|
const c = body[j];
|
|
3773
|
-
if (
|
|
3774
|
-
let
|
|
3960
|
+
if (isNatural(c)) {
|
|
3961
|
+
let pitchSteps = naturals[c];
|
|
3775
3962
|
j++;
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
pitch--;
|
|
3781
|
-
j++;
|
|
3782
|
-
}
|
|
3783
|
-
chordNotes.push((octave + 1) * 12 + pitch);
|
|
3963
|
+
pitchSteps += readAccidentals();
|
|
3964
|
+
chordNotes.push(
|
|
3965
|
+
units((octave + 1) * 372 + pitchSteps * unitsPerStep2)
|
|
3966
|
+
);
|
|
3784
3967
|
} else if (c === ">") {
|
|
3785
3968
|
octave = Math.min(8, octave + 1);
|
|
3786
3969
|
j++;
|
|
@@ -3806,7 +3989,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
3806
3989
|
placements.push({
|
|
3807
3990
|
trackIndex,
|
|
3808
3991
|
startStep: currentStep,
|
|
3809
|
-
|
|
3992
|
+
pitchUnits: p,
|
|
3810
3993
|
durationSteps: Math.max(1, steps),
|
|
3811
3994
|
velocity
|
|
3812
3995
|
});
|
|
@@ -3814,23 +3997,17 @@ var parseMML = (mml, options = {}) => {
|
|
|
3814
3997
|
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
3815
3998
|
currentStep += steps;
|
|
3816
3999
|
octave = savedOctave;
|
|
3817
|
-
} else if (
|
|
3818
|
-
let
|
|
4000
|
+
} else if (isNatural(ch)) {
|
|
4001
|
+
let pitchSteps = naturals[ch];
|
|
3819
4002
|
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;
|
|
4003
|
+
pitchSteps += readAccidentals();
|
|
4004
|
+
const midiPitch = units((octave + 1) * 372 + pitchSteps * unitsPerStep2);
|
|
3828
4005
|
const steps = parseLength();
|
|
3829
4006
|
recordContributor();
|
|
3830
4007
|
placements.push({
|
|
3831
4008
|
trackIndex,
|
|
3832
4009
|
startStep: currentStep,
|
|
3833
|
-
|
|
4010
|
+
pitchUnits: midiPitch,
|
|
3834
4011
|
durationSteps: Math.max(1, steps),
|
|
3835
4012
|
velocity
|
|
3836
4013
|
});
|
|
@@ -3962,7 +4139,7 @@ var createSequencer = (options) => {
|
|
|
3962
4139
|
maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
|
|
3963
4140
|
timeline.push({
|
|
3964
4141
|
trackId: track.id,
|
|
3965
|
-
pitch: note.
|
|
4142
|
+
pitch: note.pitchUnits,
|
|
3966
4143
|
volume: track.volume / 100,
|
|
3967
4144
|
velocity: note.velocity ?? DEFAULT_PLAYBACK_VELOCITY,
|
|
3968
4145
|
when,
|
|
@@ -4025,7 +4202,7 @@ var createSequencer = (options) => {
|
|
|
4025
4202
|
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
4026
4203
|
options.onPlayNote({
|
|
4027
4204
|
trackId: ev.trackId,
|
|
4028
|
-
|
|
4205
|
+
pitchUnits: ev.pitch,
|
|
4029
4206
|
velocity: ev.velocity,
|
|
4030
4207
|
volume: currentVolume * velocityVolume,
|
|
4031
4208
|
when: Math.max(0, _when),
|
|
@@ -6345,7 +6522,7 @@ var SONG_DRUM_PATTERNS = {
|
|
|
6345
6522
|
};
|
|
6346
6523
|
|
|
6347
6524
|
// src/synth.ts
|
|
6348
|
-
var freqFromPitch = (
|
|
6525
|
+
var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
|
|
6349
6526
|
var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
6350
6527
|
const wave = tone.wave ?? "square";
|
|
6351
6528
|
const attack = tone.attack ?? 0;
|
|
@@ -6361,7 +6538,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
|
6361
6538
|
const osc = ctx.createOscillator();
|
|
6362
6539
|
const gain = ctx.createGain();
|
|
6363
6540
|
osc.type = wave;
|
|
6364
|
-
osc.frequency.value = freqFromPitch(e.
|
|
6541
|
+
osc.frequency.value = freqFromPitch(e.pitchUnits);
|
|
6365
6542
|
const t0 = ctx.currentTime + e.when;
|
|
6366
6543
|
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
|
|
6367
6544
|
if (tone.decay) {
|
|
@@ -6466,7 +6643,7 @@ var playPlacements = (placements, options) => {
|
|
|
6466
6643
|
id: id++,
|
|
6467
6644
|
startStep: p.startStep,
|
|
6468
6645
|
durationSteps: p.durationSteps,
|
|
6469
|
-
|
|
6646
|
+
pitchUnits: p.pitchUnits,
|
|
6470
6647
|
velocity: p.velocity
|
|
6471
6648
|
}));
|
|
6472
6649
|
return {
|
|
@@ -6637,7 +6814,7 @@ var playNote = (options) => {
|
|
|
6637
6814
|
const dur = options.duration ?? 1;
|
|
6638
6815
|
synth.playNote({
|
|
6639
6816
|
trackId: "melody",
|
|
6640
|
-
|
|
6817
|
+
pitchUnits: options.pitchUnits,
|
|
6641
6818
|
velocity: 100,
|
|
6642
6819
|
volume: vol / 100,
|
|
6643
6820
|
when: 0,
|
|
@@ -6658,7 +6835,7 @@ var playChords = (chordStr, options = {}) => {
|
|
|
6658
6835
|
// 伴奏トラック
|
|
6659
6836
|
startStep: p.startStep,
|
|
6660
6837
|
durationSteps: p.durationSteps,
|
|
6661
|
-
|
|
6838
|
+
pitchUnits: p.pitchUnits,
|
|
6662
6839
|
velocity: p.velocity
|
|
6663
6840
|
}));
|
|
6664
6841
|
return playPlacements(placements, {
|
|
@@ -8815,7 +8992,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8815
8992
|
0
|
|
8816
8993
|
);
|
|
8817
8994
|
const timedNotes = placements.map((p) => ({
|
|
8818
|
-
pitch: p.
|
|
8995
|
+
pitch: unitsToPitchV1(p.pitchUnits),
|
|
8819
8996
|
when: p.startStep * secondsPerStep,
|
|
8820
8997
|
duration: p.durationSteps * secondsPerStep
|
|
8821
8998
|
}));
|
|
@@ -8848,7 +9025,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8848
9025
|
id: id++,
|
|
8849
9026
|
startStep: p.startStep,
|
|
8850
9027
|
durationSteps: p.durationSteps,
|
|
8851
|
-
|
|
9028
|
+
pitchUnits: p.pitchUnits,
|
|
8852
9029
|
velocity: DEFAULT_VELOCITY
|
|
8853
9030
|
}));
|
|
8854
9031
|
return {
|
|
@@ -9673,7 +9850,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9673
9850
|
(a, b) => a.startStep - b.startStep
|
|
9674
9851
|
);
|
|
9675
9852
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
9676
|
-
const semis = (lt.octave ?? 0) *
|
|
9853
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
9677
9854
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
9678
9855
|
const notes = [];
|
|
9679
9856
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -9681,7 +9858,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9681
9858
|
if (n.startStep < fromStep) continue;
|
|
9682
9859
|
notes.push({
|
|
9683
9860
|
syllable: lt.syllables[i2],
|
|
9684
|
-
pitch: n.
|
|
9861
|
+
pitch: units(n.pitchUnits + semis),
|
|
9685
9862
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
9686
9863
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
9687
9864
|
});
|
|
@@ -9956,6 +10133,7 @@ var SoundFont = class _SoundFont {
|
|
|
9956
10133
|
pitch = 60,
|
|
9957
10134
|
volume = 1,
|
|
9958
10135
|
velocity,
|
|
10136
|
+
detuneCents = 0,
|
|
9959
10137
|
when = 0,
|
|
9960
10138
|
duration = 1
|
|
9961
10139
|
} = {}) {
|
|
@@ -9975,7 +10153,7 @@ var SoundFont = class _SoundFont {
|
|
|
9975
10153
|
Object.assign(src, _param.src);
|
|
9976
10154
|
const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
|
|
9977
10155
|
const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
|
|
9978
|
-
src.detune.setValueAtTime(humanizeCents, 0);
|
|
10156
|
+
src.detune.setValueAtTime(humanizeCents + detuneCents, 0);
|
|
9979
10157
|
const effectiveVolume = volume * humanizeGainMul;
|
|
9980
10158
|
const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
|
|
9981
10159
|
const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
|
|
@@ -11114,7 +11292,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11114
11292
|
scheduleMetronomeBeats(ctx, cutGain);
|
|
11115
11293
|
};
|
|
11116
11294
|
const wafPlayDynamic = ({
|
|
11117
|
-
|
|
11295
|
+
pitchUnits,
|
|
11118
11296
|
velocity,
|
|
11119
11297
|
volume,
|
|
11120
11298
|
when,
|
|
@@ -11124,10 +11302,12 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11124
11302
|
}) => {
|
|
11125
11303
|
const waf = activeGmName ? _wafCache.get(activeGmName) : null;
|
|
11126
11304
|
if (waf) {
|
|
11305
|
+
const { midi, detuneCents } = unitsToMidiDetune(pitchUnits);
|
|
11127
11306
|
waf.play({
|
|
11128
11307
|
ctx,
|
|
11129
11308
|
destination,
|
|
11130
|
-
pitch,
|
|
11309
|
+
pitch: midi,
|
|
11310
|
+
detuneCents,
|
|
11131
11311
|
volume: volume * 0.85,
|
|
11132
11312
|
velocity,
|
|
11133
11313
|
when,
|
|
@@ -11137,7 +11317,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11137
11317
|
const synth = createSynth(ctx, destination);
|
|
11138
11318
|
synth.playNote({
|
|
11139
11319
|
trackId: "chord",
|
|
11140
|
-
|
|
11320
|
+
pitchUnits,
|
|
11141
11321
|
velocity: 100,
|
|
11142
11322
|
volume,
|
|
11143
11323
|
when,
|
|
@@ -11166,7 +11346,8 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11166
11346
|
trackIndex: 3,
|
|
11167
11347
|
startStep: whenStep,
|
|
11168
11348
|
durationSteps,
|
|
11169
|
-
|
|
11349
|
+
// chord-parser の構成音は12平均律の半音。内部表現の units へ写す。
|
|
11350
|
+
pitchUnits: pitchV1ToUnits(C32 + noteOffset),
|
|
11170
11351
|
velocity: 100
|
|
11171
11352
|
});
|
|
11172
11353
|
}
|
|
@@ -11179,7 +11360,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11179
11360
|
synth: false,
|
|
11180
11361
|
onPlayNote: (e) => {
|
|
11181
11362
|
wafPlayDynamic({
|
|
11182
|
-
|
|
11363
|
+
pitchUnits: e.pitchUnits,
|
|
11183
11364
|
velocity: e.velocity,
|
|
11184
11365
|
volume: e.volume,
|
|
11185
11366
|
when: e.when,
|
|
@@ -11621,6 +11802,14 @@ var buildUI = (target, options) => {
|
|
|
11621
11802
|
<summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
|
|
11622
11803
|
<div class="dtm-panel-body">
|
|
11623
11804
|
<div data-dtm="preset-select-slot"></div>
|
|
11805
|
+
<div class="dtm-row">
|
|
11806
|
+
<span class="dtm-label">\u97F3\u5F8B</span>
|
|
11807
|
+
<select class="dtm-select dtm-grow" data-dtm="edo-select">
|
|
11808
|
+
<option value="12">12\u5E73\u5747\u5F8B\uFF08\u901A\u5E38\uFF09</option>
|
|
11809
|
+
<option value="31">31\u5E73\u5747\u5F8B\uFF08\u5FAE\u5206\u97F3\uFF09</option>
|
|
11810
|
+
</select>
|
|
11811
|
+
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
11812
|
+
</div>
|
|
11624
11813
|
<div class="dtm-row">
|
|
11625
11814
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
11626
11815
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -11953,6 +12142,8 @@ var buildUI = (target, options) => {
|
|
|
11953
12142
|
copyMiniBtn: sel("copy-mini"),
|
|
11954
12143
|
overlay: sel("overlay"),
|
|
11955
12144
|
mmlInfoBtn: sel("mml-info"),
|
|
12145
|
+
edoSelect: sel("edo-select"),
|
|
12146
|
+
edoInfoBtn: sel("edo-info"),
|
|
11956
12147
|
modalOverlay: sel("modal-overlay"),
|
|
11957
12148
|
modalTitle: sel("modal-title"),
|
|
11958
12149
|
modalBody: sel("modal-body"),
|
|
@@ -12073,17 +12264,21 @@ var SCALES = [
|
|
|
12073
12264
|
[0, 2, 4, 7, 9]
|
|
12074
12265
|
// Pentatonic Major
|
|
12075
12266
|
];
|
|
12267
|
+
var MEANTONE_STEP_BY_SEMITONE2 = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
12076
12268
|
var generateRandomPattern = (core, options) => {
|
|
12077
|
-
const { stepsPerBar, startStep, pitchRangeStart } = options;
|
|
12269
|
+
const { stepsPerBar, startStep, pitchRangeStart, edo = 12 } = options;
|
|
12078
12270
|
const numBars = 8;
|
|
12079
12271
|
const noteLength = 24;
|
|
12080
|
-
const
|
|
12272
|
+
const semitoneToStep = (semi) => edo === 31 ? MEANTONE_STEP_BY_SEMITONE2[semi] : semi;
|
|
12273
|
+
const upr = UNITS_PER_OCTAVE / edo;
|
|
12274
|
+
const basePitch = pitchRangeStart + 60 * UNITS_PER_SEMITONE;
|
|
12081
12275
|
const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
|
|
12082
12276
|
const rootOffset = Math.floor(Math.random() * 12);
|
|
12083
12277
|
const availablePitches = [];
|
|
12084
12278
|
for (let i2 = 0; i2 < 12; i2++) {
|
|
12085
12279
|
const noteInOctave = (i2 - rootOffset + 12) % 12;
|
|
12086
|
-
if (scale.includes(noteInOctave))
|
|
12280
|
+
if (scale.includes(noteInOctave))
|
|
12281
|
+
availablePitches.push(units(basePitch + semitoneToStep(i2) * upr));
|
|
12087
12282
|
}
|
|
12088
12283
|
core.beginBatch();
|
|
12089
12284
|
for (let bar = 0; bar < numBars; bar++) {
|
|
@@ -12118,14 +12313,17 @@ var applyHarmonicFilter = (targetCore, chordCore, options) => {
|
|
|
12118
12313
|
const isNewBar = halfBar % 2 === 0;
|
|
12119
12314
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12120
12315
|
if (chordHere.length > 0) {
|
|
12121
|
-
currentClasses = new Set(
|
|
12316
|
+
currentClasses = new Set(
|
|
12317
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12318
|
+
);
|
|
12122
12319
|
} else if (isNewBar) {
|
|
12123
12320
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12124
12321
|
}
|
|
12125
12322
|
if (currentClasses.size === 0) continue;
|
|
12126
12323
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12127
12324
|
for (const n of activeHere) {
|
|
12128
|
-
if (!currentClasses.has(n.
|
|
12325
|
+
if (!currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE))
|
|
12326
|
+
targetCore.deleteNoteById(n.id);
|
|
12129
12327
|
}
|
|
12130
12328
|
}
|
|
12131
12329
|
targetCore.endBatch();
|
|
@@ -12147,13 +12345,17 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12147
12345
|
const isNewBar = halfBar % 2 === 0;
|
|
12148
12346
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12149
12347
|
if (chordHere.length > 0) {
|
|
12150
|
-
currentClasses = new Set(
|
|
12348
|
+
currentClasses = new Set(
|
|
12349
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12350
|
+
);
|
|
12151
12351
|
} else if (isNewBar) {
|
|
12152
12352
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12153
12353
|
}
|
|
12154
12354
|
if (currentClasses.size === 0) continue;
|
|
12155
12355
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12156
|
-
const filtered = activeHere.filter(
|
|
12356
|
+
const filtered = activeHere.filter(
|
|
12357
|
+
(n) => currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12358
|
+
);
|
|
12157
12359
|
const filteredIds = new Set(filtered.map((n) => n.id));
|
|
12158
12360
|
for (const n of activeHere) {
|
|
12159
12361
|
if (!filteredIds.has(n.id)) targetCore.deleteNoteById(n.id);
|
|
@@ -12165,7 +12367,7 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12165
12367
|
}
|
|
12166
12368
|
for (const notesAtTime of timeMap.values()) {
|
|
12167
12369
|
if (notesAtTime.length > 1) {
|
|
12168
|
-
notesAtTime.sort((a, b) => b.
|
|
12370
|
+
notesAtTime.sort((a, b) => b.pitchUnits - a.pitchUnits);
|
|
12169
12371
|
const [, ...others] = notesAtTime;
|
|
12170
12372
|
for (const on of others) targetCore.deleteNoteById(on.id);
|
|
12171
12373
|
}
|
|
@@ -12181,18 +12383,23 @@ var shiftNotes = (cores, shiftSteps) => {
|
|
|
12181
12383
|
for (const note of notes) {
|
|
12182
12384
|
const newStart = note.startStep + shiftSteps;
|
|
12183
12385
|
if (newStart < 0) core.deleteNoteById(note.id);
|
|
12184
|
-
else core.moveNote(note.id, newStart, note.
|
|
12386
|
+
else core.moveNote(note.id, newStart, note.pitchUnits);
|
|
12185
12387
|
}
|
|
12186
12388
|
core.saveHistory();
|
|
12187
12389
|
}
|
|
12188
12390
|
};
|
|
12189
|
-
var transposeNotes = (cores,
|
|
12190
|
-
if (
|
|
12391
|
+
var transposeNotes = (cores, steps) => {
|
|
12392
|
+
if (steps === 0) return;
|
|
12191
12393
|
for (const core of cores) {
|
|
12192
12394
|
const notes = [...core.getNotes()];
|
|
12193
12395
|
for (const note of notes) {
|
|
12194
|
-
const newPitch =
|
|
12195
|
-
|
|
12396
|
+
const newPitch = units(
|
|
12397
|
+
Math.max(
|
|
12398
|
+
PITCH_RANGE_START,
|
|
12399
|
+
Math.min(PITCH_RANGE_END, note.pitchUnits + steps)
|
|
12400
|
+
)
|
|
12401
|
+
);
|
|
12402
|
+
if (newPitch !== note.pitchUnits) {
|
|
12196
12403
|
core.moveNote(note.id, note.startStep, newPitch);
|
|
12197
12404
|
}
|
|
12198
12405
|
}
|
|
@@ -12517,11 +12724,31 @@ var exportMIDI = (options) => {
|
|
|
12517
12724
|
const div = 480;
|
|
12518
12725
|
const tickPerStep = div / STEPS_PER_BEAT3;
|
|
12519
12726
|
const midiTracks = [];
|
|
12520
|
-
|
|
12727
|
+
const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
|
|
12728
|
+
const BEND_RANGE_SEMITONES = 2;
|
|
12729
|
+
const bendKey = (cents) => Math.round(cents * 100);
|
|
12730
|
+
const bendUse = /* @__PURE__ */ new Map();
|
|
12731
|
+
for (const track of tracks) {
|
|
12732
|
+
for (const n of track.notes) {
|
|
12733
|
+
const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12734
|
+
const k = bendKey(detuneCents);
|
|
12735
|
+
bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
|
|
12736
|
+
}
|
|
12737
|
+
}
|
|
12738
|
+
const bendChannel = /* @__PURE__ */ new Map();
|
|
12739
|
+
const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
|
|
12740
|
+
for (const [k] of ordered) {
|
|
12741
|
+
if (bendChannel.size >= NOTE_CHANNELS.length) break;
|
|
12742
|
+
bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
|
|
12743
|
+
}
|
|
12744
|
+
tracks.forEach((track) => {
|
|
12521
12745
|
if (track.notes.length === 0) return;
|
|
12522
|
-
const channel = ch < 9 ? ch : ch + 1 & 15;
|
|
12523
12746
|
const events = [];
|
|
12524
12747
|
for (const n of track.notes) {
|
|
12748
|
+
const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12749
|
+
const k = bendKey(detuneCents);
|
|
12750
|
+
const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
|
|
12751
|
+
const note = Math.max(0, Math.min(127, midi));
|
|
12525
12752
|
const startTick = Math.round(n.startStep * tickPerStep);
|
|
12526
12753
|
const endTick = Math.round(
|
|
12527
12754
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
@@ -12529,12 +12756,25 @@ var exportMIDI = (options) => {
|
|
|
12529
12756
|
const vel = Math.round(
|
|
12530
12757
|
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
12531
12758
|
);
|
|
12532
|
-
events.push({ t: startTick, m: [144 | channel,
|
|
12533
|
-
events.push({ t: endTick, m: [144 | channel,
|
|
12759
|
+
events.push({ t: startTick, m: [144 | channel, note, vel] });
|
|
12760
|
+
events.push({ t: endTick, m: [144 | channel, note, 0] });
|
|
12534
12761
|
}
|
|
12535
12762
|
events.sort((a, b) => a.t - b.t);
|
|
12536
12763
|
midiTracks.push(events);
|
|
12537
12764
|
});
|
|
12765
|
+
const bendSetup = [];
|
|
12766
|
+
for (const [k, channel] of bendChannel) {
|
|
12767
|
+
const cents = k / 100;
|
|
12768
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
|
|
12769
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
|
|
12770
|
+
bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
|
|
12771
|
+
bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
|
|
12772
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
|
|
12773
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
|
|
12774
|
+
const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
|
|
12775
|
+
const v = Math.max(0, Math.min(16383, raw));
|
|
12776
|
+
bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
|
|
12777
|
+
}
|
|
12538
12778
|
const maxStep = Math.max(
|
|
12539
12779
|
...tracks.filter((t) => t.notes.length > 0).map(
|
|
12540
12780
|
(t) => Math.max(...t.notes.map((n) => n.startStep + n.durationSteps))
|
|
@@ -12571,6 +12811,7 @@ var exportMIDI = (options) => {
|
|
|
12571
12811
|
headerChunks(arr, midiTracks.length + 1, div);
|
|
12572
12812
|
trackChunks(arr, (a) => {
|
|
12573
12813
|
a.push(0, 255, 81, 3, ...to3byte(Math.round(6e7 / bpm)));
|
|
12814
|
+
for (const ev of bendSetup) a.push(0, ...ev.m);
|
|
12574
12815
|
});
|
|
12575
12816
|
for (const events of midiTracks) {
|
|
12576
12817
|
trackChunks(arr, (a) => {
|
|
@@ -12817,403 +13058,8 @@ var LinkedList = class {
|
|
|
12817
13058
|
}
|
|
12818
13059
|
};
|
|
12819
13060
|
|
|
12820
|
-
// src/renderer.ts
|
|
12821
|
-
var g_header_canvas;
|
|
12822
|
-
var g_key_canvas;
|
|
12823
|
-
var g_grid_canvas;
|
|
12824
|
-
var g_header_ctx;
|
|
12825
|
-
var g_key_ctx;
|
|
12826
|
-
var g_grid_ctx;
|
|
12827
|
-
var g_config;
|
|
12828
|
-
var KEYBOARD_WIDTH = 60;
|
|
12829
|
-
var HEADER_HEIGHT = 20;
|
|
12830
|
-
var getRenderConfig = () => g_config;
|
|
12831
|
-
var g_draw_offset_x = 0;
|
|
12832
|
-
var g_draw_offset_y = 0;
|
|
12833
|
-
var g_bg_active = false;
|
|
12834
|
-
var setBackgroundActive = (active) => {
|
|
12835
|
-
g_bg_active = active;
|
|
12836
|
-
};
|
|
12837
|
-
var getDrawOffset = () => ({
|
|
12838
|
-
x: g_draw_offset_x,
|
|
12839
|
-
y: g_draw_offset_y
|
|
12840
|
-
});
|
|
12841
|
-
var getGridCanvas = () => g_grid_canvas;
|
|
12842
|
-
var getGridContext = () => g_grid_ctx;
|
|
12843
|
-
var getHeaderCanvas = () => g_header_canvas;
|
|
12844
|
-
var init = (mountTarget, width = 800, height = 450, config) => {
|
|
12845
|
-
g_config = config;
|
|
12846
|
-
const headerCanvas = document.createElement("canvas");
|
|
12847
|
-
g_header_canvas = headerCanvas;
|
|
12848
|
-
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
12849
|
-
headerCanvas.height = HEADER_HEIGHT;
|
|
12850
|
-
headerCanvas.style.position = "absolute";
|
|
12851
|
-
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
12852
|
-
headerCanvas.style.top = "0px";
|
|
12853
|
-
const headerCtx = headerCanvas.getContext("2d");
|
|
12854
|
-
if (!headerCtx)
|
|
12855
|
-
throw new Error("Failed to get 2D rendering context for header.");
|
|
12856
|
-
g_header_ctx = headerCtx;
|
|
12857
|
-
const keyCanvas = document.createElement("canvas");
|
|
12858
|
-
g_key_canvas = keyCanvas;
|
|
12859
|
-
keyCanvas.width = KEYBOARD_WIDTH;
|
|
12860
|
-
keyCanvas.height = height - HEADER_HEIGHT;
|
|
12861
|
-
keyCanvas.style.position = "absolute";
|
|
12862
|
-
keyCanvas.style.left = "0px";
|
|
12863
|
-
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
12864
|
-
const keyCtx = keyCanvas.getContext("2d");
|
|
12865
|
-
if (!keyCtx)
|
|
12866
|
-
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
12867
|
-
g_key_ctx = keyCtx;
|
|
12868
|
-
const gridCanvas = document.createElement("canvas");
|
|
12869
|
-
g_grid_canvas = gridCanvas;
|
|
12870
|
-
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
12871
|
-
gridCanvas.height = height - HEADER_HEIGHT;
|
|
12872
|
-
gridCanvas.style.position = "absolute";
|
|
12873
|
-
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
12874
|
-
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
12875
|
-
gridCanvas.style.touchAction = "none";
|
|
12876
|
-
gridCanvas.style.userSelect = "none";
|
|
12877
|
-
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
12878
|
-
if (!gridCtx) throw new Error("Failed to get 2D rendering context for grid.");
|
|
12879
|
-
g_grid_ctx = gridCtx;
|
|
12880
|
-
mountTarget.innerHTML = "";
|
|
12881
|
-
mountTarget.style.position = "relative";
|
|
12882
|
-
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
12883
|
-
mountTarget.style.height = `${height}px`;
|
|
12884
|
-
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
12885
|
-
drawHeaderCorner();
|
|
12886
|
-
};
|
|
12887
|
-
var blackKeyPitches = /* @__PURE__ */ new Set([1, 3, 6, 8, 10]);
|
|
12888
|
-
var KEY_NAMES = [
|
|
12889
|
-
"C",
|
|
12890
|
-
"C#",
|
|
12891
|
-
"D",
|
|
12892
|
-
"D#",
|
|
12893
|
-
"E",
|
|
12894
|
-
"F",
|
|
12895
|
-
"F#",
|
|
12896
|
-
"G",
|
|
12897
|
-
"G#",
|
|
12898
|
-
"A",
|
|
12899
|
-
"A#",
|
|
12900
|
-
"B"
|
|
12901
|
-
];
|
|
12902
|
-
var drawHeaderCorner = () => {
|
|
12903
|
-
const mountTarget = g_key_canvas.parentElement;
|
|
12904
|
-
if (!mountTarget) return;
|
|
12905
|
-
let cornerDiv = mountTarget.querySelector("#header-corner");
|
|
12906
|
-
if (!cornerDiv) {
|
|
12907
|
-
cornerDiv = document.createElement("div");
|
|
12908
|
-
cornerDiv.id = "header-corner";
|
|
12909
|
-
cornerDiv.style.position = "absolute";
|
|
12910
|
-
cornerDiv.style.left = "0px";
|
|
12911
|
-
cornerDiv.style.top = "0px";
|
|
12912
|
-
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
12913
|
-
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
12914
|
-
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
12915
|
-
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
12916
|
-
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
12917
|
-
mountTarget.insertBefore(cornerDiv, g_header_canvas);
|
|
12918
|
-
}
|
|
12919
|
-
};
|
|
12920
|
-
var drawKeyboard = () => {
|
|
12921
|
-
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
12922
|
-
const { keyHeight, keyCount, pitchRangeStart } = g_config;
|
|
12923
|
-
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
12924
|
-
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
12925
|
-
const WHITE_KEY = "#ccc8b4";
|
|
12926
|
-
const BLACK_KEY = "#111111";
|
|
12927
|
-
const BK_EDGE = "#383838";
|
|
12928
|
-
const WW_SEP = "#807a6a";
|
|
12929
|
-
const BK_RATIO = 0.62;
|
|
12930
|
-
for (let y = startY; y < endY; y += keyHeight) {
|
|
12931
|
-
const pitchIndex = keyCount - 1 - y / keyHeight;
|
|
12932
|
-
const totalPitch = pitchIndex + pitchRangeStart;
|
|
12933
|
-
const pitchMod12 = totalPitch % 12;
|
|
12934
|
-
const isBlackKey = blackKeyPitches.has(pitchMod12);
|
|
12935
|
-
const octave = Math.floor(totalPitch / 12) - 1;
|
|
12936
|
-
const isC4Range = octave === 4;
|
|
12937
|
-
const screenY = y - g_draw_offset_y;
|
|
12938
|
-
const bkW = Math.floor(KEYBOARD_WIDTH * BK_RATIO);
|
|
12939
|
-
if (isBlackKey) {
|
|
12940
|
-
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
12941
|
-
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
12942
|
-
g_key_ctx.fillStyle = isC4Range ? "#1a1408" : BLACK_KEY;
|
|
12943
|
-
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
12944
|
-
g_key_ctx.strokeStyle = BK_EDGE;
|
|
12945
|
-
g_key_ctx.lineWidth = 1;
|
|
12946
|
-
g_key_ctx.beginPath();
|
|
12947
|
-
g_key_ctx.moveTo(bkW, screenY);
|
|
12948
|
-
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
12949
|
-
g_key_ctx.stroke();
|
|
12950
|
-
} else {
|
|
12951
|
-
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
12952
|
-
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
12953
|
-
if (pitchMod12 === 5 || pitchMod12 === 0) {
|
|
12954
|
-
g_key_ctx.strokeStyle = WW_SEP;
|
|
12955
|
-
g_key_ctx.lineWidth = 1;
|
|
12956
|
-
g_key_ctx.beginPath();
|
|
12957
|
-
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
12958
|
-
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
12959
|
-
g_key_ctx.stroke();
|
|
12960
|
-
}
|
|
12961
|
-
}
|
|
12962
|
-
if (pitchMod12 === 0) {
|
|
12963
|
-
const octave2 = Math.floor(totalPitch / 12) - 1;
|
|
12964
|
-
g_key_ctx.fillStyle = "#555040";
|
|
12965
|
-
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
12966
|
-
g_key_ctx.textAlign = "right";
|
|
12967
|
-
g_key_ctx.textBaseline = "bottom";
|
|
12968
|
-
g_key_ctx.fillText(
|
|
12969
|
-
`${KEY_NAMES[pitchMod12]}${octave2}`,
|
|
12970
|
-
KEYBOARD_WIDTH - 4,
|
|
12971
|
-
screenY + keyHeight - 2
|
|
12972
|
-
);
|
|
12973
|
-
}
|
|
12974
|
-
}
|
|
12975
|
-
g_key_ctx.beginPath();
|
|
12976
|
-
g_key_ctx.strokeStyle = "#29adff";
|
|
12977
|
-
g_key_ctx.lineWidth = 2;
|
|
12978
|
-
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
12979
|
-
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
12980
|
-
g_key_ctx.stroke();
|
|
12981
|
-
};
|
|
12982
|
-
var drawHeader = () => {
|
|
12983
|
-
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
12984
|
-
const { stepWidth, stepsPerBar } = g_config;
|
|
12985
|
-
g_header_ctx.save();
|
|
12986
|
-
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
12987
|
-
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
12988
|
-
g_header_ctx.fillRect(
|
|
12989
|
-
g_draw_offset_x,
|
|
12990
|
-
0,
|
|
12991
|
-
g_header_canvas.width,
|
|
12992
|
-
HEADER_HEIGHT
|
|
12993
|
-
);
|
|
12994
|
-
g_header_ctx.strokeStyle = "#3d405b";
|
|
12995
|
-
g_header_ctx.lineWidth = 1;
|
|
12996
|
-
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
12997
|
-
g_header_ctx.fillStyle = "#83769c";
|
|
12998
|
-
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
12999
|
-
const endBar = Math.ceil(
|
|
13000
|
-
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13001
|
-
);
|
|
13002
|
-
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13003
|
-
const x2 = bar * stepsPerBar * stepWidth;
|
|
13004
|
-
const screenX = x2;
|
|
13005
|
-
g_header_ctx.beginPath();
|
|
13006
|
-
g_header_ctx.moveTo(screenX, 0);
|
|
13007
|
-
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13008
|
-
g_header_ctx.stroke();
|
|
13009
|
-
if (bar >= 0) {
|
|
13010
|
-
g_header_ctx.textAlign = "left";
|
|
13011
|
-
g_header_ctx.textBaseline = "middle";
|
|
13012
|
-
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13013
|
-
}
|
|
13014
|
-
}
|
|
13015
|
-
g_header_ctx.restore();
|
|
13016
|
-
};
|
|
13017
|
-
var drawGrid = (noteLengthSteps = 1) => {
|
|
13018
|
-
drawKeyboard();
|
|
13019
|
-
drawHeader();
|
|
13020
|
-
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13021
|
-
const { keyHeight, keyCount, stepWidth, stepsPerBar, pitchRangeStart } = g_config;
|
|
13022
|
-
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13023
|
-
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13024
|
-
for (let y = startY; y < endY; y += keyHeight) {
|
|
13025
|
-
const pitchIndex = keyCount - 1 - y / keyHeight;
|
|
13026
|
-
const totalPitch = pitchIndex + pitchRangeStart;
|
|
13027
|
-
const pitchMod12 = totalPitch % 12;
|
|
13028
|
-
const isBlackKey = blackKeyPitches.has(pitchMod12);
|
|
13029
|
-
const isC = pitchMod12 === 0;
|
|
13030
|
-
const octave = Math.floor(totalPitch / 12) - 1;
|
|
13031
|
-
const isC4Range = octave === 4;
|
|
13032
|
-
const screenY = y - g_draw_offset_y;
|
|
13033
|
-
g_grid_ctx.fillStyle = g_bg_active ? isBlackKey ? "rgba(8,11,22,0.55)" : "rgba(17,22,40,0.45)" : isBlackKey ? "#080b16" : "#111628";
|
|
13034
|
-
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13035
|
-
if (isC4Range) {
|
|
13036
|
-
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13037
|
-
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13038
|
-
}
|
|
13039
|
-
g_grid_ctx.beginPath();
|
|
13040
|
-
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13041
|
-
g_grid_ctx.lineWidth = 1;
|
|
13042
|
-
const lineY = screenY + keyHeight;
|
|
13043
|
-
g_grid_ctx.moveTo(0, lineY);
|
|
13044
|
-
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13045
|
-
g_grid_ctx.stroke();
|
|
13046
|
-
}
|
|
13047
|
-
const gridStep = noteLengthSteps || 48;
|
|
13048
|
-
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13049
|
-
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13050
|
-
const lineStep = stepWidth * gridStep;
|
|
13051
|
-
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13052
|
-
const step = x2 / stepWidth;
|
|
13053
|
-
const isBarLine = step % stepsPerBar === 0;
|
|
13054
|
-
const isNoteLine = step % gridStep === 0;
|
|
13055
|
-
const screenX = x2 - g_draw_offset_x;
|
|
13056
|
-
g_grid_ctx.beginPath();
|
|
13057
|
-
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13058
|
-
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13059
|
-
g_grid_ctx.moveTo(screenX, 0);
|
|
13060
|
-
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13061
|
-
g_grid_ctx.stroke();
|
|
13062
|
-
}
|
|
13063
|
-
};
|
|
13064
|
-
var drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13065
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13066
|
-
const canvasWidth = g_grid_canvas.width;
|
|
13067
|
-
const canvasHeight = g_grid_canvas.height;
|
|
13068
|
-
const [r, g, b, a] = color;
|
|
13069
|
-
for (const note of notes) {
|
|
13070
|
-
const logicalX = note.startStep * stepWidth;
|
|
13071
|
-
const yIndex = keyCount - 1 - (note.pitch - pitchRangeStart);
|
|
13072
|
-
const logicalY = yIndex * keyHeight;
|
|
13073
|
-
const w = note.durationSteps * stepWidth;
|
|
13074
|
-
const h = keyHeight;
|
|
13075
|
-
const renderX = logicalX - g_draw_offset_x;
|
|
13076
|
-
const renderY = logicalY - g_draw_offset_y;
|
|
13077
|
-
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13078
|
-
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13079
|
-
if (isActive) {
|
|
13080
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13081
|
-
const finalOpacity = a * velocityOpacity;
|
|
13082
|
-
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13083
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13084
|
-
if (w >= 4 && h >= 4) {
|
|
13085
|
-
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13086
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13087
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13088
|
-
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13089
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13090
|
-
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13091
|
-
}
|
|
13092
|
-
} else {
|
|
13093
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13094
|
-
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13095
|
-
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13096
|
-
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13097
|
-
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13098
|
-
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13099
|
-
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13100
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13101
|
-
}
|
|
13102
|
-
}
|
|
13103
|
-
};
|
|
13104
|
-
var drawNoteLyrics = (notes, syllables) => {
|
|
13105
|
-
if (syllables.length === 0) return;
|
|
13106
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13107
|
-
if (keyHeight < 10) return;
|
|
13108
|
-
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13109
|
-
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13110
|
-
const count = Math.min(sorted.length, syllables.length);
|
|
13111
|
-
g_grid_ctx.save();
|
|
13112
|
-
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13113
|
-
g_grid_ctx.textAlign = "left";
|
|
13114
|
-
g_grid_ctx.textBaseline = "middle";
|
|
13115
|
-
g_grid_ctx.lineWidth = 3;
|
|
13116
|
-
g_grid_ctx.lineJoin = "round";
|
|
13117
|
-
for (let i2 = 0; i2 < count; i2++) {
|
|
13118
|
-
const kana = syllables[i2];
|
|
13119
|
-
if (!kana) continue;
|
|
13120
|
-
const note = sorted[i2];
|
|
13121
|
-
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
13122
|
-
const renderY = (keyCount - 1 - (note.pitch - pitchRangeStart)) * keyHeight - g_draw_offset_y;
|
|
13123
|
-
const w = note.durationSteps * stepWidth;
|
|
13124
|
-
if (w < 6) continue;
|
|
13125
|
-
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
13126
|
-
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
13127
|
-
const textX = renderX + 2;
|
|
13128
|
-
const textY = renderY + keyHeight / 2;
|
|
13129
|
-
g_grid_ctx.save();
|
|
13130
|
-
g_grid_ctx.beginPath();
|
|
13131
|
-
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
13132
|
-
g_grid_ctx.clip();
|
|
13133
|
-
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
13134
|
-
g_grid_ctx.strokeText(kana, textX, textY);
|
|
13135
|
-
g_grid_ctx.fillStyle = "#fff1e8";
|
|
13136
|
-
g_grid_ctx.fillText(kana, textX, textY);
|
|
13137
|
-
g_grid_ctx.restore();
|
|
13138
|
-
}
|
|
13139
|
-
g_grid_ctx.restore();
|
|
13140
|
-
};
|
|
13141
|
-
var drawSelectionRect = (rect) => {
|
|
13142
|
-
if (!rect) return;
|
|
13143
|
-
g_grid_ctx.save();
|
|
13144
|
-
g_grid_ctx.strokeStyle = "#ffec27";
|
|
13145
|
-
g_grid_ctx.lineWidth = 2;
|
|
13146
|
-
g_grid_ctx.setLineDash([4, 4]);
|
|
13147
|
-
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
13148
|
-
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
13149
|
-
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
13150
|
-
g_grid_ctx.restore();
|
|
13151
|
-
};
|
|
13152
|
-
var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
13153
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13154
|
-
for (const note of notes) {
|
|
13155
|
-
if (!selectedIds.has(note.id)) continue;
|
|
13156
|
-
const logicalX = note.startStep * stepWidth;
|
|
13157
|
-
const yIndex = keyCount - 1 - (note.pitch - pitchRangeStart);
|
|
13158
|
-
const logicalY = yIndex * keyHeight;
|
|
13159
|
-
const w = note.durationSteps * stepWidth;
|
|
13160
|
-
const h = keyHeight;
|
|
13161
|
-
const renderX = logicalX - g_draw_offset_x;
|
|
13162
|
-
const renderY = logicalY - g_draw_offset_y;
|
|
13163
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13164
|
-
const [r, g, b, a] = baseColor;
|
|
13165
|
-
const darkenFactor = 1.3;
|
|
13166
|
-
const darkerR = Math.min(255, r * darkenFactor);
|
|
13167
|
-
const darkerG = Math.min(255, g * darkenFactor);
|
|
13168
|
-
const darkerB = Math.min(255, b * darkenFactor);
|
|
13169
|
-
const finalOpacity = a * velocityOpacity;
|
|
13170
|
-
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
13171
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13172
|
-
}
|
|
13173
|
-
};
|
|
13174
|
-
var getXY = (e) => {
|
|
13175
|
-
const { clientX, clientY } = e;
|
|
13176
|
-
const rect = g_grid_canvas.getBoundingClientRect();
|
|
13177
|
-
const x2 = Math.floor(clientX - rect.left);
|
|
13178
|
-
const y = Math.floor(clientY - rect.top);
|
|
13179
|
-
return [x2, y, e.buttons];
|
|
13180
|
-
};
|
|
13181
|
-
var getGridPosition = (e) => {
|
|
13182
|
-
const [x2, y] = getXY(e);
|
|
13183
|
-
const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
|
|
13184
|
-
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13185
|
-
const absoluteY = y + g_draw_offset_y;
|
|
13186
|
-
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13187
|
-
const pitch = keyCount - 1 - yIndex + pitchRangeStart;
|
|
13188
|
-
return { step, pitch, x: x2, y };
|
|
13189
|
-
};
|
|
13190
|
-
var onClick = (callback) => {
|
|
13191
|
-
g_grid_canvas.addEventListener(
|
|
13192
|
-
"click",
|
|
13193
|
-
(e) => {
|
|
13194
|
-
const [x2, y] = getXY(e);
|
|
13195
|
-
const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
|
|
13196
|
-
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13197
|
-
const absoluteY = y + g_draw_offset_y;
|
|
13198
|
-
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13199
|
-
const pitch = keyCount - 1 - yIndex + pitchRangeStart;
|
|
13200
|
-
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
13201
|
-
requestAnimationFrame(() => callback(step, pitch));
|
|
13202
|
-
}
|
|
13203
|
-
},
|
|
13204
|
-
{ passive: true }
|
|
13205
|
-
);
|
|
13206
|
-
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
13207
|
-
};
|
|
13208
|
-
var setDrawOffset = (x2, y) => {
|
|
13209
|
-
g_draw_offset_x = x2;
|
|
13210
|
-
g_draw_offset_y = y;
|
|
13211
|
-
drawKeyboard();
|
|
13212
|
-
drawHeader();
|
|
13213
|
-
};
|
|
13214
|
-
|
|
13215
13061
|
// src/mml-core.ts
|
|
13216
|
-
var
|
|
13062
|
+
var PITCH_MAP = [
|
|
13217
13063
|
"c",
|
|
13218
13064
|
"c+",
|
|
13219
13065
|
"d",
|
|
@@ -13227,6 +13073,39 @@ var PITCH_MAP2 = [
|
|
|
13227
13073
|
"a+",
|
|
13228
13074
|
"b"
|
|
13229
13075
|
];
|
|
13076
|
+
var EDO31_NAMES = [
|
|
13077
|
+
"c",
|
|
13078
|
+
"c+",
|
|
13079
|
+
"c#",
|
|
13080
|
+
"d-",
|
|
13081
|
+
"d_",
|
|
13082
|
+
"d",
|
|
13083
|
+
"d+",
|
|
13084
|
+
"d#",
|
|
13085
|
+
"e-",
|
|
13086
|
+
"e_",
|
|
13087
|
+
"e",
|
|
13088
|
+
"f-",
|
|
13089
|
+
"e#",
|
|
13090
|
+
"f",
|
|
13091
|
+
"f+",
|
|
13092
|
+
"f#",
|
|
13093
|
+
"g-",
|
|
13094
|
+
"g_",
|
|
13095
|
+
"g",
|
|
13096
|
+
"g+",
|
|
13097
|
+
"g#",
|
|
13098
|
+
"a-",
|
|
13099
|
+
"a_",
|
|
13100
|
+
"a",
|
|
13101
|
+
"a+",
|
|
13102
|
+
"a#",
|
|
13103
|
+
"b-",
|
|
13104
|
+
"b_",
|
|
13105
|
+
"b",
|
|
13106
|
+
"b+",
|
|
13107
|
+
"b#"
|
|
13108
|
+
];
|
|
13230
13109
|
var MMLCore = class _MMLCore {
|
|
13231
13110
|
notes = [];
|
|
13232
13111
|
nextNoteId = 0;
|
|
@@ -13240,9 +13119,12 @@ var MMLCore = class _MMLCore {
|
|
|
13240
13119
|
lastUndoTime = 0;
|
|
13241
13120
|
static UNDO_DEBOUNCE_MS = 100;
|
|
13242
13121
|
toolMode = "pen";
|
|
13243
|
-
|
|
13122
|
+
/** グリッド寸法の供給元(描画器ではなく呼び出し側が持つ設定を読む) */
|
|
13123
|
+
getConfig;
|
|
13124
|
+
constructor(handlers, volume = 80, getConfig) {
|
|
13244
13125
|
this.handlers = handlers;
|
|
13245
13126
|
this.volume = volume;
|
|
13127
|
+
this.getConfig = getConfig;
|
|
13246
13128
|
this.lastHistorySnapshot = JSON.stringify(this.notes);
|
|
13247
13129
|
this.history.add([]);
|
|
13248
13130
|
this.generateAndNotify();
|
|
@@ -13329,14 +13211,14 @@ var MMLCore = class _MMLCore {
|
|
|
13329
13211
|
*/
|
|
13330
13212
|
addNote(step, pitch, options) {
|
|
13331
13213
|
const existingIndex = this.notes.findIndex(
|
|
13332
|
-
(n) => n.startStep === step && n.
|
|
13214
|
+
(n) => n.startStep === step && n.pitchUnits === pitch
|
|
13333
13215
|
);
|
|
13334
13216
|
if (existingIndex === -1) {
|
|
13335
13217
|
const newNote = {
|
|
13336
13218
|
id: this.nextNoteId++,
|
|
13337
13219
|
startStep: step,
|
|
13338
13220
|
durationSteps: options.noteLengthSteps,
|
|
13339
|
-
pitch,
|
|
13221
|
+
pitchUnits: pitch,
|
|
13340
13222
|
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
13341
13223
|
};
|
|
13342
13224
|
this.notes.push(newNote);
|
|
@@ -13364,19 +13246,20 @@ var MMLCore = class _MMLCore {
|
|
|
13364
13246
|
moveNote(noteId, startStep, pitch) {
|
|
13365
13247
|
const note = this.notes.find((target) => target.id === noteId);
|
|
13366
13248
|
if (!note) return;
|
|
13367
|
-
const totalSteps = this.getMaxStep() +
|
|
13368
|
-
const
|
|
13369
|
-
const
|
|
13370
|
-
const
|
|
13371
|
-
|
|
13372
|
-
|
|
13249
|
+
const totalSteps = this.getMaxStep() + this.getConfig().stepsPerBar;
|
|
13250
|
+
const cfg = this.getConfig();
|
|
13251
|
+
const upr = cfg.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
13252
|
+
const pitchRangeStart = cfg.pitchRangeStart;
|
|
13253
|
+
const pitchRangeEnd = pitchRangeStart + (cfg.keyCount - 1) * upr;
|
|
13254
|
+
const clampedPitch = units(
|
|
13255
|
+
Math.min(Math.max(pitch, pitchRangeStart), pitchRangeEnd)
|
|
13373
13256
|
);
|
|
13374
13257
|
const clampedStart = Math.min(
|
|
13375
13258
|
Math.max(startStep, 0),
|
|
13376
13259
|
totalSteps - note.durationSteps
|
|
13377
13260
|
);
|
|
13378
13261
|
note.startStep = clampedStart;
|
|
13379
|
-
note.
|
|
13262
|
+
note.pitchUnits = clampedPitch;
|
|
13380
13263
|
this.notes.sort((a, b) => a.startStep - b.startStep);
|
|
13381
13264
|
this.generateAndNotify();
|
|
13382
13265
|
}
|
|
@@ -13421,7 +13304,7 @@ var MMLCore = class _MMLCore {
|
|
|
13421
13304
|
* ただし、残りステップ(limit)は絶対に超えない。
|
|
13422
13305
|
*/
|
|
13423
13306
|
stepsToMMLDuration(steps, limit) {
|
|
13424
|
-
const config =
|
|
13307
|
+
const config = this.getConfig();
|
|
13425
13308
|
const total = config.stepsPerBar;
|
|
13426
13309
|
const candidates = [
|
|
13427
13310
|
{ dur: "1.", s: total * 1.5 },
|
|
@@ -13457,7 +13340,7 @@ var MMLCore = class _MMLCore {
|
|
|
13457
13340
|
* ギャップに収まる最大の音符を探す(減算アルゴリズム用)
|
|
13458
13341
|
*/
|
|
13459
13342
|
findBestFitDuration(gap) {
|
|
13460
|
-
const config =
|
|
13343
|
+
const config = this.getConfig();
|
|
13461
13344
|
const durations = [1, 2, 4, 8, 12, 16, 24, 32, 48, 64];
|
|
13462
13345
|
for (const d of durations) {
|
|
13463
13346
|
const stepLen = config.stepsPerBar / d;
|
|
@@ -13467,12 +13350,20 @@ var MMLCore = class _MMLCore {
|
|
|
13467
13350
|
}
|
|
13468
13351
|
return { dur: 64, steps: config.stepsPerBar / 64 };
|
|
13469
13352
|
}
|
|
13353
|
+
/** ピッチ(units) → その音律での「オクターブ番号」と「音名」。 */
|
|
13354
|
+
spell(pitchUnits) {
|
|
13355
|
+
const edo = this.getConfig().edo ?? 12;
|
|
13356
|
+
const names = edo === 31 ? EDO31_NAMES : PITCH_MAP;
|
|
13357
|
+
const octave = Math.floor(pitchUnits / 372) - 1;
|
|
13358
|
+
const within = (pitchUnits % 372 + 372) % 372;
|
|
13359
|
+
const step = Math.round(within / (372 / edo));
|
|
13360
|
+
return { octave, name: names[step % edo] };
|
|
13361
|
+
}
|
|
13470
13362
|
/**
|
|
13471
13363
|
* ピッチからオクターブ最適化のある音名を取得
|
|
13472
13364
|
*/
|
|
13473
13365
|
getNoteWithOctave(pitch, lastOctave) {
|
|
13474
|
-
const octave =
|
|
13475
|
-
const name = PITCH_MAP2[pitch % 12];
|
|
13366
|
+
const { octave, name } = this.spell(pitch);
|
|
13476
13367
|
if (lastOctave === -1 || Math.abs(octave - lastOctave) >= 2) {
|
|
13477
13368
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
13478
13369
|
}
|
|
@@ -13495,7 +13386,7 @@ var MMLCore = class _MMLCore {
|
|
|
13495
13386
|
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
13496
13387
|
*/
|
|
13497
13388
|
generateMML = (volumeOverride) => {
|
|
13498
|
-
const config =
|
|
13389
|
+
const config = this.getConfig();
|
|
13499
13390
|
const vol = volumeOverride ?? this.volume;
|
|
13500
13391
|
const header = `t${this.tempo} v${vol}`;
|
|
13501
13392
|
const segments = [];
|
|
@@ -13536,14 +13427,13 @@ var MMLCore = class _MMLCore {
|
|
|
13536
13427
|
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
13537
13428
|
if (notes.length > 1) {
|
|
13538
13429
|
const noteStrs = notes.map((n) => {
|
|
13539
|
-
const oct =
|
|
13540
|
-
const name = PITCH_MAP2[n.pitch % 12];
|
|
13430
|
+
const { octave: oct, name } = this.spell(n.pitchUnits);
|
|
13541
13431
|
return `o${oct}${name}`;
|
|
13542
13432
|
});
|
|
13543
13433
|
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
13544
13434
|
} else {
|
|
13545
13435
|
const { text, currentOctave } = this.getNoteWithOctave(
|
|
13546
|
-
notes[0].
|
|
13436
|
+
notes[0].pitchUnits,
|
|
13547
13437
|
lastOctave
|
|
13548
13438
|
);
|
|
13549
13439
|
segments.push(`${text}${durStr}`);
|
|
@@ -13574,7 +13464,7 @@ var MMLCore = class _MMLCore {
|
|
|
13574
13464
|
* MMLの音長文字列("4", "4.", "12"など)をステップ数に変換する
|
|
13575
13465
|
*/
|
|
13576
13466
|
getStepFromDottedMML(durStr) {
|
|
13577
|
-
const config =
|
|
13467
|
+
const config = this.getConfig();
|
|
13578
13468
|
const total = config.stepsPerBar;
|
|
13579
13469
|
const isDotted = durStr.endsWith(".");
|
|
13580
13470
|
const baseDur = parseInt(isDotted ? durStr.slice(0, -1) : durStr, 10);
|
|
@@ -13584,7 +13474,7 @@ var MMLCore = class _MMLCore {
|
|
|
13584
13474
|
};
|
|
13585
13475
|
var decomposeToMonophonic = (notes) => {
|
|
13586
13476
|
const sorted = [...notes].sort(
|
|
13587
|
-
(a, b) => a.startStep - b.startStep || a.
|
|
13477
|
+
(a, b) => a.startStep - b.startStep || a.pitchUnits - b.pitchUnits
|
|
13588
13478
|
);
|
|
13589
13479
|
const tracks = [];
|
|
13590
13480
|
const trackEnds = [];
|
|
@@ -13619,6 +13509,493 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
|
|
|
13619
13509
|
return chordNotes / notes.length >= threshold;
|
|
13620
13510
|
};
|
|
13621
13511
|
|
|
13512
|
+
// src/renderer.ts
|
|
13513
|
+
var KEYBOARD_WIDTH = 60;
|
|
13514
|
+
var HEADER_HEIGHT = 20;
|
|
13515
|
+
var NATURAL_STEPS3 = {
|
|
13516
|
+
12: [0, 2, 4, 5, 7, 9, 11],
|
|
13517
|
+
31: [0, 5, 10, 13, 18, 23, 28]
|
|
13518
|
+
};
|
|
13519
|
+
var keyTier = (step, edo) => {
|
|
13520
|
+
const naturals = NATURAL_STEPS3[edo] ?? NATURAL_STEPS3[12];
|
|
13521
|
+
let best = edo;
|
|
13522
|
+
for (const n of naturals) {
|
|
13523
|
+
const d = Math.abs(step - n);
|
|
13524
|
+
best = Math.min(best, d, edo - d);
|
|
13525
|
+
}
|
|
13526
|
+
if (best === 0) return 0;
|
|
13527
|
+
return best >= (edo === 31 ? 2 : 1) ? 2 : 1;
|
|
13528
|
+
};
|
|
13529
|
+
var KEY_NAMES = [
|
|
13530
|
+
"C",
|
|
13531
|
+
"C#",
|
|
13532
|
+
"D",
|
|
13533
|
+
"D#",
|
|
13534
|
+
"E",
|
|
13535
|
+
"F",
|
|
13536
|
+
"F#",
|
|
13537
|
+
"G",
|
|
13538
|
+
"G#",
|
|
13539
|
+
"A",
|
|
13540
|
+
"A#",
|
|
13541
|
+
"B"
|
|
13542
|
+
];
|
|
13543
|
+
var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
13544
|
+
let g_header_canvas;
|
|
13545
|
+
let g_key_canvas;
|
|
13546
|
+
let g_grid_canvas;
|
|
13547
|
+
let g_header_ctx;
|
|
13548
|
+
let g_key_ctx;
|
|
13549
|
+
let g_grid_ctx;
|
|
13550
|
+
let g_config = config;
|
|
13551
|
+
const getRenderConfig = () => g_config;
|
|
13552
|
+
let g_draw_offset_x = 0;
|
|
13553
|
+
let g_draw_offset_y = 0;
|
|
13554
|
+
let g_bg_active = false;
|
|
13555
|
+
const setBackgroundActive = (active) => {
|
|
13556
|
+
g_bg_active = active;
|
|
13557
|
+
};
|
|
13558
|
+
const getDrawOffset = () => ({
|
|
13559
|
+
x: g_draw_offset_x,
|
|
13560
|
+
y: g_draw_offset_y
|
|
13561
|
+
});
|
|
13562
|
+
const getGridCanvas = () => g_grid_canvas;
|
|
13563
|
+
const getGridContext = () => g_grid_ctx;
|
|
13564
|
+
const getHeaderCanvas = () => g_header_canvas;
|
|
13565
|
+
const setup = () => {
|
|
13566
|
+
g_config = config;
|
|
13567
|
+
const headerCanvas = document.createElement("canvas");
|
|
13568
|
+
g_header_canvas = headerCanvas;
|
|
13569
|
+
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
13570
|
+
headerCanvas.height = HEADER_HEIGHT;
|
|
13571
|
+
headerCanvas.style.position = "absolute";
|
|
13572
|
+
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13573
|
+
headerCanvas.style.top = "0px";
|
|
13574
|
+
const headerCtx = headerCanvas.getContext("2d");
|
|
13575
|
+
if (!headerCtx)
|
|
13576
|
+
throw new Error("Failed to get 2D rendering context for header.");
|
|
13577
|
+
g_header_ctx = headerCtx;
|
|
13578
|
+
const keyCanvas = document.createElement("canvas");
|
|
13579
|
+
g_key_canvas = keyCanvas;
|
|
13580
|
+
keyCanvas.width = KEYBOARD_WIDTH;
|
|
13581
|
+
keyCanvas.height = height - HEADER_HEIGHT;
|
|
13582
|
+
keyCanvas.style.position = "absolute";
|
|
13583
|
+
keyCanvas.style.left = "0px";
|
|
13584
|
+
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13585
|
+
const keyCtx = keyCanvas.getContext("2d");
|
|
13586
|
+
if (!keyCtx)
|
|
13587
|
+
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
13588
|
+
g_key_ctx = keyCtx;
|
|
13589
|
+
const gridCanvas = document.createElement("canvas");
|
|
13590
|
+
g_grid_canvas = gridCanvas;
|
|
13591
|
+
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
13592
|
+
gridCanvas.height = height - HEADER_HEIGHT;
|
|
13593
|
+
gridCanvas.style.position = "absolute";
|
|
13594
|
+
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13595
|
+
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13596
|
+
gridCanvas.style.touchAction = "none";
|
|
13597
|
+
gridCanvas.style.userSelect = "none";
|
|
13598
|
+
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
13599
|
+
if (!gridCtx)
|
|
13600
|
+
throw new Error("Failed to get 2D rendering context for grid.");
|
|
13601
|
+
g_grid_ctx = gridCtx;
|
|
13602
|
+
mountTarget.innerHTML = "";
|
|
13603
|
+
mountTarget.style.position = "relative";
|
|
13604
|
+
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
13605
|
+
mountTarget.style.height = `${height}px`;
|
|
13606
|
+
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
13607
|
+
drawHeaderCorner();
|
|
13608
|
+
};
|
|
13609
|
+
const drawHeaderCorner = () => {
|
|
13610
|
+
const mountTarget2 = g_key_canvas.parentElement;
|
|
13611
|
+
if (!mountTarget2) return;
|
|
13612
|
+
let cornerDiv = mountTarget2.querySelector(
|
|
13613
|
+
"#header-corner"
|
|
13614
|
+
);
|
|
13615
|
+
if (!cornerDiv) {
|
|
13616
|
+
cornerDiv = document.createElement("div");
|
|
13617
|
+
cornerDiv.id = "header-corner";
|
|
13618
|
+
cornerDiv.style.position = "absolute";
|
|
13619
|
+
cornerDiv.style.left = "0px";
|
|
13620
|
+
cornerDiv.style.top = "0px";
|
|
13621
|
+
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
13622
|
+
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
13623
|
+
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
13624
|
+
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
13625
|
+
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
13626
|
+
mountTarget2.insertBefore(cornerDiv, g_header_canvas);
|
|
13627
|
+
}
|
|
13628
|
+
};
|
|
13629
|
+
const drawKeyboard = () => {
|
|
13630
|
+
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
13631
|
+
const {
|
|
13632
|
+
keyHeight,
|
|
13633
|
+
keyCount,
|
|
13634
|
+
pitchRangeStart,
|
|
13635
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13636
|
+
edo = 12
|
|
13637
|
+
} = g_config;
|
|
13638
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13639
|
+
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
13640
|
+
const WHITE_KEY = "#ccc8b4";
|
|
13641
|
+
const BLACK_KEY = "#111111";
|
|
13642
|
+
const MICRO_KEY = "#4a4a4a";
|
|
13643
|
+
const BK_EDGE = "#383838";
|
|
13644
|
+
const WW_SEP = "#807a6a";
|
|
13645
|
+
const BK_RATIO = 0.62;
|
|
13646
|
+
const MICRO_RATIO = 0.45;
|
|
13647
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13648
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13649
|
+
const units2 = pitchRangeStart + rowIndex * upr;
|
|
13650
|
+
const step = Math.round(
|
|
13651
|
+
(units2 % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13652
|
+
);
|
|
13653
|
+
const tier = keyTier(step, edo);
|
|
13654
|
+
const octave = Math.floor(units2 / UNITS_PER_OCTAVE) - 1;
|
|
13655
|
+
const isC4Range = octave === 4;
|
|
13656
|
+
const screenY = y - g_draw_offset_y;
|
|
13657
|
+
const bkW = Math.floor(
|
|
13658
|
+
KEYBOARD_WIDTH * (tier === 1 ? MICRO_RATIO : BK_RATIO)
|
|
13659
|
+
);
|
|
13660
|
+
if (tier !== 0) {
|
|
13661
|
+
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
13662
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13663
|
+
g_key_ctx.fillStyle = tier === 1 ? MICRO_KEY : isC4Range ? "#1a1408" : BLACK_KEY;
|
|
13664
|
+
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
13665
|
+
g_key_ctx.strokeStyle = BK_EDGE;
|
|
13666
|
+
g_key_ctx.lineWidth = 1;
|
|
13667
|
+
g_key_ctx.beginPath();
|
|
13668
|
+
g_key_ctx.moveTo(bkW, screenY);
|
|
13669
|
+
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
13670
|
+
g_key_ctx.stroke();
|
|
13671
|
+
} else {
|
|
13672
|
+
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
13673
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13674
|
+
if (edo === 12 && (step === 5 || step === 0)) {
|
|
13675
|
+
g_key_ctx.strokeStyle = WW_SEP;
|
|
13676
|
+
g_key_ctx.lineWidth = 1;
|
|
13677
|
+
g_key_ctx.beginPath();
|
|
13678
|
+
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
13679
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
13680
|
+
g_key_ctx.stroke();
|
|
13681
|
+
}
|
|
13682
|
+
}
|
|
13683
|
+
if (step === 0) {
|
|
13684
|
+
g_key_ctx.fillStyle = "#555040";
|
|
13685
|
+
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
13686
|
+
g_key_ctx.textAlign = "right";
|
|
13687
|
+
g_key_ctx.textBaseline = "bottom";
|
|
13688
|
+
g_key_ctx.fillText(
|
|
13689
|
+
`${KEY_NAMES[0]}${octave}`,
|
|
13690
|
+
KEYBOARD_WIDTH - 4,
|
|
13691
|
+
screenY + keyHeight - 2
|
|
13692
|
+
);
|
|
13693
|
+
}
|
|
13694
|
+
}
|
|
13695
|
+
g_key_ctx.beginPath();
|
|
13696
|
+
g_key_ctx.strokeStyle = "#29adff";
|
|
13697
|
+
g_key_ctx.lineWidth = 2;
|
|
13698
|
+
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
13699
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
13700
|
+
g_key_ctx.stroke();
|
|
13701
|
+
};
|
|
13702
|
+
const drawHeader = () => {
|
|
13703
|
+
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
13704
|
+
const { stepWidth, stepsPerBar } = g_config;
|
|
13705
|
+
g_header_ctx.save();
|
|
13706
|
+
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
13707
|
+
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
13708
|
+
g_header_ctx.fillRect(
|
|
13709
|
+
g_draw_offset_x,
|
|
13710
|
+
0,
|
|
13711
|
+
g_header_canvas.width,
|
|
13712
|
+
HEADER_HEIGHT
|
|
13713
|
+
);
|
|
13714
|
+
g_header_ctx.strokeStyle = "#3d405b";
|
|
13715
|
+
g_header_ctx.lineWidth = 1;
|
|
13716
|
+
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
13717
|
+
g_header_ctx.fillStyle = "#83769c";
|
|
13718
|
+
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
13719
|
+
const endBar = Math.ceil(
|
|
13720
|
+
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13721
|
+
);
|
|
13722
|
+
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13723
|
+
const x2 = bar * stepsPerBar * stepWidth;
|
|
13724
|
+
const screenX = x2;
|
|
13725
|
+
g_header_ctx.beginPath();
|
|
13726
|
+
g_header_ctx.moveTo(screenX, 0);
|
|
13727
|
+
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13728
|
+
g_header_ctx.stroke();
|
|
13729
|
+
if (bar >= 0) {
|
|
13730
|
+
g_header_ctx.textAlign = "left";
|
|
13731
|
+
g_header_ctx.textBaseline = "middle";
|
|
13732
|
+
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13733
|
+
}
|
|
13734
|
+
}
|
|
13735
|
+
g_header_ctx.restore();
|
|
13736
|
+
};
|
|
13737
|
+
const drawGrid = (noteLengthSteps = 1) => {
|
|
13738
|
+
drawKeyboard();
|
|
13739
|
+
drawHeader();
|
|
13740
|
+
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13741
|
+
const {
|
|
13742
|
+
keyHeight,
|
|
13743
|
+
keyCount,
|
|
13744
|
+
stepWidth,
|
|
13745
|
+
stepsPerBar,
|
|
13746
|
+
pitchRangeStart,
|
|
13747
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13748
|
+
edo = 12
|
|
13749
|
+
} = g_config;
|
|
13750
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13751
|
+
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13752
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13753
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13754
|
+
const units2 = pitchRangeStart + rowIndex * upr;
|
|
13755
|
+
const step = Math.round(
|
|
13756
|
+
(units2 % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13757
|
+
);
|
|
13758
|
+
const tier = keyTier(step, edo);
|
|
13759
|
+
const isC = step === 0;
|
|
13760
|
+
const octave = Math.floor(units2 / UNITS_PER_OCTAVE) - 1;
|
|
13761
|
+
const isC4Range = octave === 4;
|
|
13762
|
+
const screenY = y - g_draw_offset_y;
|
|
13763
|
+
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";
|
|
13764
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13765
|
+
if (isC4Range) {
|
|
13766
|
+
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13767
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13768
|
+
}
|
|
13769
|
+
g_grid_ctx.beginPath();
|
|
13770
|
+
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13771
|
+
g_grid_ctx.lineWidth = 1;
|
|
13772
|
+
const lineY = screenY + keyHeight;
|
|
13773
|
+
g_grid_ctx.moveTo(0, lineY);
|
|
13774
|
+
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13775
|
+
g_grid_ctx.stroke();
|
|
13776
|
+
}
|
|
13777
|
+
const gridStep = noteLengthSteps || 48;
|
|
13778
|
+
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13779
|
+
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13780
|
+
const lineStep = stepWidth * gridStep;
|
|
13781
|
+
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13782
|
+
const step = x2 / stepWidth;
|
|
13783
|
+
const isBarLine = step % stepsPerBar === 0;
|
|
13784
|
+
const isNoteLine = step % gridStep === 0;
|
|
13785
|
+
const screenX = x2 - g_draw_offset_x;
|
|
13786
|
+
g_grid_ctx.beginPath();
|
|
13787
|
+
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13788
|
+
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13789
|
+
g_grid_ctx.moveTo(screenX, 0);
|
|
13790
|
+
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13791
|
+
g_grid_ctx.stroke();
|
|
13792
|
+
}
|
|
13793
|
+
};
|
|
13794
|
+
const drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13795
|
+
const {
|
|
13796
|
+
keyHeight,
|
|
13797
|
+
stepWidth,
|
|
13798
|
+
keyCount,
|
|
13799
|
+
pitchRangeStart,
|
|
13800
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13801
|
+
} = g_config;
|
|
13802
|
+
const canvasWidth = g_grid_canvas.width;
|
|
13803
|
+
const canvasHeight = g_grid_canvas.height;
|
|
13804
|
+
const [r, g, b, a] = color;
|
|
13805
|
+
for (const note of notes) {
|
|
13806
|
+
const logicalX = note.startStep * stepWidth;
|
|
13807
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13808
|
+
const logicalY = yIndex * keyHeight;
|
|
13809
|
+
const w = note.durationSteps * stepWidth;
|
|
13810
|
+
const h = keyHeight;
|
|
13811
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13812
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13813
|
+
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13814
|
+
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13815
|
+
if (isActive) {
|
|
13816
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13817
|
+
const finalOpacity = a * velocityOpacity;
|
|
13818
|
+
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13819
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13820
|
+
if (w >= 4 && h >= 4) {
|
|
13821
|
+
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13822
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13823
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13824
|
+
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13825
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13826
|
+
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13827
|
+
}
|
|
13828
|
+
} else {
|
|
13829
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13830
|
+
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13831
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13832
|
+
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13833
|
+
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13834
|
+
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13835
|
+
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13836
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13837
|
+
}
|
|
13838
|
+
}
|
|
13839
|
+
};
|
|
13840
|
+
const drawNoteLyrics = (notes, syllables) => {
|
|
13841
|
+
if (syllables.length === 0) return;
|
|
13842
|
+
const {
|
|
13843
|
+
keyHeight,
|
|
13844
|
+
stepWidth,
|
|
13845
|
+
keyCount,
|
|
13846
|
+
pitchRangeStart,
|
|
13847
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13848
|
+
} = g_config;
|
|
13849
|
+
if (keyHeight < 7) return;
|
|
13850
|
+
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13851
|
+
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13852
|
+
const count = Math.min(sorted.length, syllables.length);
|
|
13853
|
+
g_grid_ctx.save();
|
|
13854
|
+
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13855
|
+
g_grid_ctx.textAlign = "left";
|
|
13856
|
+
g_grid_ctx.textBaseline = "middle";
|
|
13857
|
+
g_grid_ctx.lineWidth = 3;
|
|
13858
|
+
g_grid_ctx.lineJoin = "round";
|
|
13859
|
+
for (let i2 = 0; i2 < count; i2++) {
|
|
13860
|
+
const kana = syllables[i2];
|
|
13861
|
+
if (!kana) continue;
|
|
13862
|
+
const note = sorted[i2];
|
|
13863
|
+
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
13864
|
+
const renderY = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
|
|
13865
|
+
const w = note.durationSteps * stepWidth;
|
|
13866
|
+
if (w < 6) continue;
|
|
13867
|
+
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
13868
|
+
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
13869
|
+
const textX = renderX + 2;
|
|
13870
|
+
const textY = renderY + keyHeight / 2;
|
|
13871
|
+
g_grid_ctx.save();
|
|
13872
|
+
g_grid_ctx.beginPath();
|
|
13873
|
+
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
13874
|
+
g_grid_ctx.clip();
|
|
13875
|
+
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
13876
|
+
g_grid_ctx.strokeText(kana, textX, textY);
|
|
13877
|
+
g_grid_ctx.fillStyle = "#fff1e8";
|
|
13878
|
+
g_grid_ctx.fillText(kana, textX, textY);
|
|
13879
|
+
g_grid_ctx.restore();
|
|
13880
|
+
}
|
|
13881
|
+
g_grid_ctx.restore();
|
|
13882
|
+
};
|
|
13883
|
+
const drawSelectionRect = (rect) => {
|
|
13884
|
+
if (!rect) return;
|
|
13885
|
+
g_grid_ctx.save();
|
|
13886
|
+
g_grid_ctx.strokeStyle = "#ffec27";
|
|
13887
|
+
g_grid_ctx.lineWidth = 2;
|
|
13888
|
+
g_grid_ctx.setLineDash([4, 4]);
|
|
13889
|
+
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
13890
|
+
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
13891
|
+
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
13892
|
+
g_grid_ctx.restore();
|
|
13893
|
+
};
|
|
13894
|
+
const drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
13895
|
+
const {
|
|
13896
|
+
keyHeight,
|
|
13897
|
+
stepWidth,
|
|
13898
|
+
keyCount,
|
|
13899
|
+
pitchRangeStart,
|
|
13900
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13901
|
+
} = g_config;
|
|
13902
|
+
for (const note of notes) {
|
|
13903
|
+
if (!selectedIds.has(note.id)) continue;
|
|
13904
|
+
const logicalX = note.startStep * stepWidth;
|
|
13905
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13906
|
+
const logicalY = yIndex * keyHeight;
|
|
13907
|
+
const w = note.durationSteps * stepWidth;
|
|
13908
|
+
const h = keyHeight;
|
|
13909
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13910
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13911
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13912
|
+
const [r, g, b, a] = baseColor;
|
|
13913
|
+
const darkenFactor = 1.3;
|
|
13914
|
+
const darkerR = Math.min(255, r * darkenFactor);
|
|
13915
|
+
const darkerG = Math.min(255, g * darkenFactor);
|
|
13916
|
+
const darkerB = Math.min(255, b * darkenFactor);
|
|
13917
|
+
const finalOpacity = a * velocityOpacity;
|
|
13918
|
+
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
13919
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13920
|
+
}
|
|
13921
|
+
};
|
|
13922
|
+
const getXY = (e) => {
|
|
13923
|
+
const { clientX, clientY } = e;
|
|
13924
|
+
const rect = g_grid_canvas.getBoundingClientRect();
|
|
13925
|
+
const x2 = Math.floor(clientX - rect.left);
|
|
13926
|
+
const y = Math.floor(clientY - rect.top);
|
|
13927
|
+
return [x2, y, e.buttons];
|
|
13928
|
+
};
|
|
13929
|
+
const getGridPosition = (e) => {
|
|
13930
|
+
const [x2, y] = getXY(e);
|
|
13931
|
+
const {
|
|
13932
|
+
keyCount,
|
|
13933
|
+
pitchRangeStart,
|
|
13934
|
+
keyHeight,
|
|
13935
|
+
stepWidth,
|
|
13936
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13937
|
+
} = g_config;
|
|
13938
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13939
|
+
const absoluteY = y + g_draw_offset_y;
|
|
13940
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13941
|
+
const pitch = units(pitchRangeStart + (keyCount - 1 - yIndex) * upr);
|
|
13942
|
+
return { step, pitch, x: x2, y };
|
|
13943
|
+
};
|
|
13944
|
+
const onClick = (callback) => {
|
|
13945
|
+
g_grid_canvas.addEventListener(
|
|
13946
|
+
"click",
|
|
13947
|
+
(e) => {
|
|
13948
|
+
const [x2, y] = getXY(e);
|
|
13949
|
+
const {
|
|
13950
|
+
keyCount,
|
|
13951
|
+
pitchRangeStart,
|
|
13952
|
+
keyHeight,
|
|
13953
|
+
stepWidth,
|
|
13954
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13955
|
+
} = g_config;
|
|
13956
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13957
|
+
const absoluteY = y + g_draw_offset_y;
|
|
13958
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13959
|
+
const pitch = units(pitchRangeStart + (keyCount - 1 - yIndex) * upr);
|
|
13960
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
13961
|
+
requestAnimationFrame(() => callback(step, pitch));
|
|
13962
|
+
}
|
|
13963
|
+
},
|
|
13964
|
+
{ passive: true }
|
|
13965
|
+
);
|
|
13966
|
+
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
13967
|
+
};
|
|
13968
|
+
const setDrawOffset = (x2, y) => {
|
|
13969
|
+
g_draw_offset_x = x2;
|
|
13970
|
+
g_draw_offset_y = y;
|
|
13971
|
+
drawKeyboard();
|
|
13972
|
+
drawHeader();
|
|
13973
|
+
};
|
|
13974
|
+
setup();
|
|
13975
|
+
return {
|
|
13976
|
+
getRenderConfig,
|
|
13977
|
+
setBackgroundActive,
|
|
13978
|
+
getDrawOffset,
|
|
13979
|
+
getGridCanvas,
|
|
13980
|
+
getGridContext,
|
|
13981
|
+
getHeaderCanvas,
|
|
13982
|
+
drawKeyboard,
|
|
13983
|
+
drawHeader,
|
|
13984
|
+
drawGrid,
|
|
13985
|
+
drawNotes,
|
|
13986
|
+
drawNoteLyrics,
|
|
13987
|
+
drawSelectionRect,
|
|
13988
|
+
drawSelectedNotes,
|
|
13989
|
+
getXY,
|
|
13990
|
+
getGridPosition,
|
|
13991
|
+
onClick,
|
|
13992
|
+
setDrawOffset,
|
|
13993
|
+
destroy: () => {
|
|
13994
|
+
mountTarget.innerHTML = "";
|
|
13995
|
+
}
|
|
13996
|
+
};
|
|
13997
|
+
};
|
|
13998
|
+
|
|
13622
13999
|
// src/daw.ts
|
|
13623
14000
|
var CHORD_INFO_HTML2 = `
|
|
13624
14001
|
<div class="dtm-modal-body-content">
|
|
@@ -13905,6 +14282,27 @@ var MIDI_INFO_HTML = `
|
|
|
13905
14282
|
<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>
|
|
13906
14283
|
</div>
|
|
13907
14284
|
`;
|
|
14285
|
+
var EDO_INFO_HTML = `
|
|
14286
|
+
<div class="dtm-modal-section">
|
|
14287
|
+
<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>
|
|
14288
|
+
<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>
|
|
14289
|
+
<p style="margin-top:8px;"><strong>31\u5E73\u5747\u5F8B\u306E\u7279\u5FB4</strong></p>
|
|
14290
|
+
<ul>
|
|
14291
|
+
<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>
|
|
14292
|
+
<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>
|
|
14293
|
+
<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>
|
|
14294
|
+
</ul>
|
|
14295
|
+
<p style="margin-top:8px;"><strong>MML\u3067\u306E\u66F8\u304D\u65B9</strong></p>
|
|
14296
|
+
<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>
|
|
14297
|
+
<ul>
|
|
14298
|
+
<li><code>#</code> \u2026 \u534A\u97F3\u4E0A\u3052\uFF082\u5EA6\uFF09\u3000<code>-</code> \u2026 \u534A\u97F3\u4E0B\u3052\uFF082\u5EA6\uFF09</li>
|
|
14299
|
+
<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>
|
|
14300
|
+
</ul>
|
|
14301
|
+
<p>\u4F8B: <code>#edo=31 @0 o4 c c+ c# d- d_ d;</code></p>
|
|
14302
|
+
<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>
|
|
14303
|
+
<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>
|
|
14304
|
+
</div>
|
|
14305
|
+
`;
|
|
13908
14306
|
var KOE_INFO_HTML = `
|
|
13909
14307
|
<div class="dtm-modal-body-content">
|
|
13910
14308
|
<h4>1. UTAU\u97F3\u6E90\u3092 .koe \u306B\u5909\u63DB\u3059\u308B</h4>
|
|
@@ -14122,7 +14520,7 @@ var AUTO_ROLE_BASE_VOLUME = {
|
|
|
14122
14520
|
chord: 72
|
|
14123
14521
|
};
|
|
14124
14522
|
var computeAutoRoleStats = (notes) => {
|
|
14125
|
-
const avgPitch = notes.reduce((s, n) => s + n.
|
|
14523
|
+
const avgPitch = notes.reduce((s, n) => s + n.pitchUnits, 0) / notes.length;
|
|
14126
14524
|
const avgDur = notes.reduce((s, n) => s + n.durationSteps, 0) / notes.length;
|
|
14127
14525
|
const byStart = /* @__PURE__ */ new Map();
|
|
14128
14526
|
for (const n of notes)
|
|
@@ -14136,8 +14534,9 @@ var computeAutoRoleStats = (notes) => {
|
|
|
14136
14534
|
const notesPerBeat = notes.length / spanBeats;
|
|
14137
14535
|
return { avgPitch, avgDur, avgPoly, maxPoly, notesPerBeat };
|
|
14138
14536
|
};
|
|
14537
|
+
var AUTO_ROLE_BASS_UNITS = 48 * UNITS_PER_SEMITONE;
|
|
14139
14538
|
var classifyTrackRole = (stats) => {
|
|
14140
|
-
if (stats.avgPitch <
|
|
14539
|
+
if (stats.avgPitch < AUTO_ROLE_BASS_UNITS) return "bass";
|
|
14141
14540
|
if (stats.maxPoly >= 2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT)
|
|
14142
14541
|
return "chord";
|
|
14143
14542
|
if (stats.notesPerBeat < 1.2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT * 1.5)
|
|
@@ -14149,7 +14548,10 @@ var computeAutoRoleVolume = (role, stats) => {
|
|
|
14149
14548
|
if (role === "chord" && stats.avgPoly > 1) {
|
|
14150
14549
|
vol /= Math.sqrt(stats.avgPoly);
|
|
14151
14550
|
} else if (role === "bass") {
|
|
14152
|
-
const depthSemitones = Math.max(
|
|
14551
|
+
const depthSemitones = Math.max(
|
|
14552
|
+
0,
|
|
14553
|
+
(AUTO_ROLE_BASS_UNITS - stats.avgPitch) / UNITS_PER_SEMITONE
|
|
14554
|
+
);
|
|
14153
14555
|
vol += Math.min(12, depthSemitones * 0.5);
|
|
14154
14556
|
}
|
|
14155
14557
|
return clamp5(Math.round(vol), 1, 127);
|
|
@@ -14241,17 +14643,48 @@ var mountDAW = (target, options = {}) => {
|
|
|
14241
14643
|
refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
|
|
14242
14644
|
const renderConfig = {
|
|
14243
14645
|
stepsPerBar: 192,
|
|
14244
|
-
keyCount:
|
|
14245
|
-
pitchRangeStart:
|
|
14646
|
+
keyCount: KEY_COUNT,
|
|
14647
|
+
pitchRangeStart: PITCH_RANGE_START,
|
|
14648
|
+
unitsPerRow: unitsPerRow(12),
|
|
14649
|
+
edo: 12,
|
|
14246
14650
|
keyHeight: BASE_KEY_HEIGHT,
|
|
14247
14651
|
stepWidth: BASE_STEP_WIDTH * 2
|
|
14248
14652
|
// zoom100% 相当
|
|
14249
14653
|
};
|
|
14654
|
+
let renderer;
|
|
14250
14655
|
let zoomX = 100;
|
|
14251
14656
|
let zoomY = 100;
|
|
14252
14657
|
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
14253
14658
|
let masterVolume = options.masterVolume ?? 50;
|
|
14254
|
-
|
|
14659
|
+
const applyMasterVolume = (volume) => {
|
|
14660
|
+
masterVolume = clamp5(Math.round(volume), 0, 100);
|
|
14661
|
+
refs.masterVolume.value = String(masterVolume);
|
|
14662
|
+
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
14663
|
+
options.singingVoices?.setVolume(masterVolume / 100);
|
|
14664
|
+
};
|
|
14665
|
+
applyMasterVolume(masterVolume);
|
|
14666
|
+
const snapToEdoGrid = (u) => {
|
|
14667
|
+
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
14668
|
+
return units(Math.round(u / upr) * upr);
|
|
14669
|
+
};
|
|
14670
|
+
const applyEdo = (edo, opts) => {
|
|
14671
|
+
const next = edo === 31 ? 31 : 12;
|
|
14672
|
+
if (renderConfig.edo === next) return;
|
|
14673
|
+
const upr = unitsPerRow(next);
|
|
14674
|
+
renderConfig.edo = next;
|
|
14675
|
+
renderConfig.unitsPerRow = upr;
|
|
14676
|
+
renderConfig.keyCount = keyCountFor(next);
|
|
14677
|
+
if (!opts?.snap) return;
|
|
14678
|
+
for (const t of trackStates) {
|
|
14679
|
+
t.core.beginBatch();
|
|
14680
|
+
for (const note of [...t.core.getNotes()]) {
|
|
14681
|
+
const snapped = units(Math.round(note.pitchUnits / upr) * upr);
|
|
14682
|
+
if (snapped !== note.pitchUnits)
|
|
14683
|
+
t.core.moveNote(note.id, note.startStep, snapped);
|
|
14684
|
+
}
|
|
14685
|
+
t.core.endBatch();
|
|
14686
|
+
}
|
|
14687
|
+
};
|
|
14255
14688
|
let reverbAmount = options.reverbAmount ?? 0;
|
|
14256
14689
|
let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
|
|
14257
14690
|
let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
|
|
@@ -14309,8 +14742,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14309
14742
|
let snapGridSteps = 12;
|
|
14310
14743
|
const gridLineSteps = 48;
|
|
14311
14744
|
let currentOffsetX = 0;
|
|
14312
|
-
const _initPitch = options.initialScrollPitch ?? 48;
|
|
14313
|
-
let currentOffsetY = (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215;
|
|
14745
|
+
const _initPitch = pitchV1ToUnits(options.initialScrollPitch ?? 48);
|
|
14746
|
+
let currentOffsetY = (renderConfig.keyCount - 1 - (_initPitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - 215;
|
|
14314
14747
|
let playStartStep = 0;
|
|
14315
14748
|
let isSolo = false;
|
|
14316
14749
|
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
@@ -14405,21 +14838,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
14405
14838
|
if (!ready) return;
|
|
14406
14839
|
if (!suppressPatch && options.onNotesPatch) {
|
|
14407
14840
|
const prevByKey = new Map(
|
|
14408
|
-
prevNotes.map((n) => [`${n.startStep}_${n.
|
|
14841
|
+
prevNotes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14409
14842
|
);
|
|
14410
14843
|
const currByKey = new Map(
|
|
14411
|
-
notes.map((n) => [`${n.startStep}_${n.
|
|
14844
|
+
notes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14412
14845
|
);
|
|
14413
14846
|
const added = notes.filter((n) => {
|
|
14414
|
-
const prev = prevByKey.get(
|
|
14847
|
+
const prev = prevByKey.get(
|
|
14848
|
+
`${n.startStep}_${n.pitchUnits}`
|
|
14849
|
+
);
|
|
14415
14850
|
return !prev || prev.durationSteps !== n.durationSteps || prev.velocity !== n.velocity;
|
|
14416
14851
|
}).map((n) => ({
|
|
14417
14852
|
startStep: n.startStep,
|
|
14418
|
-
|
|
14853
|
+
pitchUnits: n.pitchUnits,
|
|
14419
14854
|
durationSteps: n.durationSteps,
|
|
14420
14855
|
velocity: n.velocity
|
|
14421
14856
|
}));
|
|
14422
|
-
const removed = prevNotes.filter(
|
|
14857
|
+
const removed = prevNotes.filter(
|
|
14858
|
+
(n) => !currByKey.has(`${n.startStep}_${n.pitchUnits}`)
|
|
14859
|
+
).map((n) => ({
|
|
14860
|
+
startStep: n.startStep,
|
|
14861
|
+
pitchUnits: n.pitchUnits
|
|
14862
|
+
}));
|
|
14423
14863
|
if (added.length > 0 || removed.length > 0) {
|
|
14424
14864
|
options.onNotesPatch(config.id, added, removed);
|
|
14425
14865
|
}
|
|
@@ -14429,7 +14869,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14429
14869
|
updateUndoRedo();
|
|
14430
14870
|
}
|
|
14431
14871
|
},
|
|
14432
|
-
config.volume
|
|
14872
|
+
config.volume,
|
|
14873
|
+
() => renderConfig
|
|
14433
14874
|
),
|
|
14434
14875
|
volume: config.volume,
|
|
14435
14876
|
savedChordInput: "",
|
|
@@ -14524,18 +14965,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
14524
14965
|
return (lastNoteMeasure + 2) * renderConfig.stepsPerBar;
|
|
14525
14966
|
};
|
|
14526
14967
|
const getMaxOffsetX = () => {
|
|
14527
|
-
const canvas = getGridCanvas();
|
|
14968
|
+
const canvas = renderer.getGridCanvas();
|
|
14528
14969
|
const maxNoteStep = getMaxNoteStep();
|
|
14529
14970
|
const totalContentWidth = maxNoteStep * renderConfig.stepWidth;
|
|
14530
14971
|
return Math.max(0, totalContentWidth - canvas.width);
|
|
14531
14972
|
};
|
|
14532
14973
|
const getMaxOffsetY = () => {
|
|
14533
14974
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
14534
|
-
return Math.max(0, totalHeight - getGridCanvas().height);
|
|
14975
|
+
return Math.max(0, totalHeight - renderer.getGridCanvas().height);
|
|
14535
14976
|
};
|
|
14536
14977
|
const drawStartLine = () => {
|
|
14537
|
-
const ctx = getGridContext();
|
|
14538
|
-
const canvas = getGridCanvas();
|
|
14978
|
+
const ctx = renderer.getGridContext();
|
|
14979
|
+
const canvas = renderer.getGridCanvas();
|
|
14539
14980
|
if (!ctx) return;
|
|
14540
14981
|
const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
|
|
14541
14982
|
if (x2 < -10 || x2 > canvas.width + 10) return;
|
|
@@ -14550,8 +14991,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14550
14991
|
ctx.restore();
|
|
14551
14992
|
};
|
|
14552
14993
|
const drawPlayhead = () => {
|
|
14553
|
-
const ctx = getGridContext();
|
|
14554
|
-
const canvas = getGridCanvas();
|
|
14994
|
+
const ctx = renderer.getGridContext();
|
|
14995
|
+
const canvas = renderer.getGridCanvas();
|
|
14555
14996
|
if (!ctx) return;
|
|
14556
14997
|
const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
|
|
14557
14998
|
if (x2 < 0 || x2 > canvas.width) return;
|
|
@@ -14565,7 +15006,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14565
15006
|
ctx.restore();
|
|
14566
15007
|
};
|
|
14567
15008
|
const redrawAll = () => {
|
|
14568
|
-
drawGrid(gridLineSteps);
|
|
15009
|
+
renderer.drawGrid(gridLineSteps);
|
|
14569
15010
|
let lyricTargetNotes = null;
|
|
14570
15011
|
let activeTrackState = null;
|
|
14571
15012
|
for (const t of trackStates) {
|
|
@@ -14577,16 +15018,16 @@ var mountDAW = (target, options = {}) => {
|
|
|
14577
15018
|
}
|
|
14578
15019
|
const [r, g, b] = t.config.color;
|
|
14579
15020
|
const notes = t.core.getNotes();
|
|
14580
|
-
drawNotes(notes, [r, g, b, 1], false);
|
|
15021
|
+
renderer.drawNotes(notes, [r, g, b, 1], false);
|
|
14581
15022
|
}
|
|
14582
15023
|
if (activeTrackState) {
|
|
14583
15024
|
const [r, g, b] = activeTrackState.config.color;
|
|
14584
15025
|
const notes = activeTrackState.core.getNotes();
|
|
14585
|
-
drawNotes(notes, [r, g, b, 1], true);
|
|
15026
|
+
renderer.drawNotes(notes, [r, g, b, 1], true);
|
|
14586
15027
|
lyricTargetNotes = notes;
|
|
14587
15028
|
}
|
|
14588
15029
|
if (activeToolMode === "select" && selectionRect) {
|
|
14589
|
-
const ctx = getGridContext();
|
|
15030
|
+
const ctx = renderer.getGridContext();
|
|
14590
15031
|
ctx.save();
|
|
14591
15032
|
ctx.strokeStyle = "#ffec27";
|
|
14592
15033
|
ctx.lineWidth = 2;
|
|
@@ -14609,19 +15050,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
14609
15050
|
if (activeToolMode === "select" && selectedNotes.length > 0) {
|
|
14610
15051
|
const ids = new Set(selectedNotes.map((n) => n.id));
|
|
14611
15052
|
const active = getActive();
|
|
14612
|
-
drawSelectedNotes(active.core.getNotes(), ids, [
|
|
15053
|
+
renderer.drawSelectedNotes(active.core.getNotes(), ids, [
|
|
14613
15054
|
...active.config.color,
|
|
14614
15055
|
1
|
|
14615
15056
|
]);
|
|
14616
15057
|
}
|
|
14617
15058
|
if (lyricTargetNotes)
|
|
14618
|
-
drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
15059
|
+
renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
14619
15060
|
drawStartLine();
|
|
14620
15061
|
if (playbackState === "playing") drawPlayhead();
|
|
14621
15062
|
updateScrollbars();
|
|
14622
15063
|
};
|
|
14623
15064
|
const updateScrollbars = () => {
|
|
14624
|
-
const canvas = getGridCanvas();
|
|
15065
|
+
const canvas = renderer.getGridCanvas();
|
|
14625
15066
|
const maxOffsetX = getMaxOffsetX();
|
|
14626
15067
|
const sbW = refs.hScroll.clientWidth;
|
|
14627
15068
|
if (maxOffsetX <= 0) {
|
|
@@ -14705,7 +15146,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14705
15146
|
const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
14706
15147
|
const ratio = x2 / (rect.width - thumbW);
|
|
14707
15148
|
currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
|
|
14708
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15149
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14709
15150
|
redrawAll();
|
|
14710
15151
|
};
|
|
14711
15152
|
const moveV = (clientY) => {
|
|
@@ -14716,7 +15157,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14716
15157
|
const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
14717
15158
|
const ratio = y / (rect.height - thumbH);
|
|
14718
15159
|
currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
|
|
14719
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15160
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14720
15161
|
redrawAll();
|
|
14721
15162
|
};
|
|
14722
15163
|
};
|
|
@@ -14755,8 +15196,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14755
15196
|
const autoScrollTick = () => {
|
|
14756
15197
|
autoScrollRAF = null;
|
|
14757
15198
|
if (!isSelecting || !lastMoveEvent) return;
|
|
14758
|
-
const canvas = getGridCanvas();
|
|
14759
|
-
const { x: x2, y } = getGridPosition(lastMoveEvent);
|
|
15199
|
+
const canvas = renderer.getGridCanvas();
|
|
15200
|
+
const { x: x2, y } = renderer.getGridPosition(lastMoveEvent);
|
|
14760
15201
|
const dx = computeEdgeSpeed(x2, canvas.width);
|
|
14761
15202
|
const dy = computeEdgeSpeed(y, canvas.height);
|
|
14762
15203
|
if (dx !== 0 || dy !== 0) {
|
|
@@ -14764,7 +15205,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14764
15205
|
const maxOffsetY = getMaxOffsetY();
|
|
14765
15206
|
currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
|
|
14766
15207
|
currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
|
|
14767
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15208
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14768
15209
|
onPointerMove(lastMoveEvent);
|
|
14769
15210
|
}
|
|
14770
15211
|
if (isSelecting) {
|
|
@@ -14785,11 +15226,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14785
15226
|
};
|
|
14786
15227
|
const findActiveNoteAt = (x2, y, margin = 0) => {
|
|
14787
15228
|
const active = getActive();
|
|
14788
|
-
const {
|
|
14789
|
-
|
|
15229
|
+
const {
|
|
15230
|
+
stepWidth,
|
|
15231
|
+
keyHeight,
|
|
15232
|
+
keyCount,
|
|
15233
|
+
pitchRangeStart,
|
|
15234
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15235
|
+
} = renderConfig;
|
|
15236
|
+
const offset = renderer.getDrawOffset();
|
|
14790
15237
|
for (const note of active.core.getNotes()) {
|
|
14791
15238
|
const logicalX = note.startStep * stepWidth;
|
|
14792
|
-
const yIndex = keyCount - 1 - (note.
|
|
15239
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14793
15240
|
const logicalY = yIndex * keyHeight;
|
|
14794
15241
|
const w = note.durationSteps * stepWidth;
|
|
14795
15242
|
const renderX = logicalX - offset.x;
|
|
@@ -14802,7 +15249,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14802
15249
|
const hasNoteAt = (step, pitch, excludeId) => {
|
|
14803
15250
|
const active = getActive();
|
|
14804
15251
|
return active.core.getNotes().some(
|
|
14805
|
-
(n) => n.id !== excludeId && n.
|
|
15252
|
+
(n) => n.id !== excludeId && n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
14806
15253
|
);
|
|
14807
15254
|
};
|
|
14808
15255
|
const snapToGrid = (duration) => Math.max(
|
|
@@ -14813,7 +15260,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14813
15260
|
const onGridPointerDown = (event) => {
|
|
14814
15261
|
event.preventDefault();
|
|
14815
15262
|
options.onResumeAudio?.();
|
|
14816
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15263
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14817
15264
|
const active = getActive();
|
|
14818
15265
|
if (activeToolMode === "eraser") {
|
|
14819
15266
|
if (isActiveLocked()) return;
|
|
@@ -14828,7 +15275,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14828
15275
|
selectedOriginal = selectedNotes.map((n) => ({
|
|
14829
15276
|
id: n.id,
|
|
14830
15277
|
startStep: n.startStep,
|
|
14831
|
-
pitch: n.
|
|
15278
|
+
pitch: n.pitchUnits
|
|
14832
15279
|
}));
|
|
14833
15280
|
isSelecting = true;
|
|
14834
15281
|
dragMode = "move";
|
|
@@ -14847,7 +15294,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14847
15294
|
{
|
|
14848
15295
|
id: clicked.id,
|
|
14849
15296
|
startStep: clicked.startStep,
|
|
14850
|
-
pitch: clicked.
|
|
15297
|
+
pitch: clicked.pitchUnits
|
|
14851
15298
|
}
|
|
14852
15299
|
];
|
|
14853
15300
|
isSelecting = true;
|
|
@@ -14865,9 +15312,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
14865
15312
|
hasDragged = false;
|
|
14866
15313
|
const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
|
|
14867
15314
|
if (existing) {
|
|
14868
|
-
playPreview(existing.
|
|
15315
|
+
playPreview(existing.pitchUnits);
|
|
14869
15316
|
const { stepWidth } = renderConfig;
|
|
14870
|
-
const offset = getDrawOffset();
|
|
15317
|
+
const offset = renderer.getDrawOffset();
|
|
14871
15318
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
14872
15319
|
const w = existing.durationSteps * stepWidth;
|
|
14873
15320
|
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
@@ -14878,17 +15325,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14878
15325
|
dragOffsetPitch: 0,
|
|
14879
15326
|
startStep: existing.startStep,
|
|
14880
15327
|
durationSteps: existing.durationSteps,
|
|
14881
|
-
lastPreviewPitch: existing.
|
|
15328
|
+
lastPreviewPitch: existing.pitchUnits
|
|
14882
15329
|
};
|
|
14883
15330
|
} else {
|
|
14884
15331
|
dragState = {
|
|
14885
15332
|
noteId: existing.id,
|
|
14886
15333
|
mode: "move",
|
|
14887
15334
|
dragOffsetStep: step - existing.startStep,
|
|
14888
|
-
dragOffsetPitch: pitch - existing.
|
|
15335
|
+
dragOffsetPitch: pitch - existing.pitchUnits,
|
|
14889
15336
|
startStep: existing.startStep,
|
|
14890
15337
|
durationSteps: existing.durationSteps,
|
|
14891
|
-
lastPreviewPitch: existing.
|
|
15338
|
+
lastPreviewPitch: existing.pitchUnits
|
|
14892
15339
|
};
|
|
14893
15340
|
}
|
|
14894
15341
|
suppressClick = true;
|
|
@@ -14899,14 +15346,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
14899
15346
|
const newStart = snappedStep;
|
|
14900
15347
|
const newEnd = newStart + currentInsertLength;
|
|
14901
15348
|
const overlapping = active.core.getNotes().some(
|
|
14902
|
-
(n) => n.
|
|
15349
|
+
(n) => n.pitchUnits === pitch && newStart < n.startStep + n.durationSteps && newEnd > n.startStep
|
|
14903
15350
|
);
|
|
14904
15351
|
if (!overlapping) {
|
|
14905
15352
|
active.core.addNote(snappedStep, pitch, {
|
|
14906
15353
|
noteLengthSteps: currentInsertLength
|
|
14907
15354
|
});
|
|
14908
15355
|
playPreview(pitch);
|
|
14909
|
-
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.
|
|
15356
|
+
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.pitchUnits === pitch);
|
|
14910
15357
|
if (newNote) {
|
|
14911
15358
|
dragState = {
|
|
14912
15359
|
noteId: newNote.id,
|
|
@@ -14915,7 +15362,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14915
15362
|
dragOffsetPitch: 0,
|
|
14916
15363
|
startStep: newNote.startStep,
|
|
14917
15364
|
durationSteps: newNote.durationSteps,
|
|
14918
|
-
lastPreviewPitch: newNote.
|
|
15365
|
+
lastPreviewPitch: newNote.pitchUnits
|
|
14919
15366
|
};
|
|
14920
15367
|
hasDragged = true;
|
|
14921
15368
|
}
|
|
@@ -14926,12 +15373,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
14926
15373
|
const active = getActive();
|
|
14927
15374
|
if (activeToolMode === "pen") {
|
|
14928
15375
|
if (!dragState) return;
|
|
14929
|
-
const { step, pitch } = getGridPosition(event);
|
|
15376
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
14930
15377
|
hasDragged = true;
|
|
14931
15378
|
if (dragState.mode === "move") {
|
|
14932
15379
|
const nextStart = step - dragState.dragOffsetStep;
|
|
14933
15380
|
const snappedStart = Math.round(nextStart / snapGridSteps) * snapGridSteps;
|
|
14934
|
-
const nextPitch = pitch - dragState.dragOffsetPitch;
|
|
15381
|
+
const nextPitch = units(pitch - dragState.dragOffsetPitch);
|
|
14935
15382
|
if (hasNoteAt(snappedStart, nextPitch, dragState.noteId)) return;
|
|
14936
15383
|
active.core.moveNote(dragState.noteId, snappedStart, nextPitch);
|
|
14937
15384
|
if (nextPitch !== dragState.lastPreviewPitch) {
|
|
@@ -14950,7 +15397,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14950
15397
|
}
|
|
14951
15398
|
if (activeToolMode === "select" && isSelecting && selectionStart) {
|
|
14952
15399
|
ensureAutoScroll(event);
|
|
14953
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15400
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14954
15401
|
if (dragMode === "rect") {
|
|
14955
15402
|
const rect = {
|
|
14956
15403
|
x: Math.min(x2, selectionStart.x),
|
|
@@ -14959,11 +15406,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14959
15406
|
height: Math.abs(y - selectionStart.y)
|
|
14960
15407
|
};
|
|
14961
15408
|
selectionRect = rect;
|
|
14962
|
-
const {
|
|
14963
|
-
|
|
15409
|
+
const {
|
|
15410
|
+
stepWidth,
|
|
15411
|
+
keyHeight,
|
|
15412
|
+
keyCount,
|
|
15413
|
+
pitchRangeStart,
|
|
15414
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15415
|
+
} = renderConfig;
|
|
15416
|
+
const offset = renderer.getDrawOffset();
|
|
14964
15417
|
selectedNotes = active.core.getNotes().filter((note) => {
|
|
14965
15418
|
const logicalX = note.startStep * stepWidth;
|
|
14966
|
-
const yIndex = keyCount - 1 - (note.
|
|
15419
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14967
15420
|
const logicalY = yIndex * keyHeight;
|
|
14968
15421
|
const nx = logicalX - offset.x;
|
|
14969
15422
|
const ny = logicalY - offset.y;
|
|
@@ -14981,8 +15434,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14981
15434
|
for (const note of selectedNotes) {
|
|
14982
15435
|
const orig = selectedOriginal.find((o) => o.id === note.id);
|
|
14983
15436
|
if (!orig) continue;
|
|
14984
|
-
const newPitch = orig.pitch + deltaPitch;
|
|
14985
|
-
if (newPitch >=
|
|
15437
|
+
const newPitch = units(orig.pitch + deltaPitch);
|
|
15438
|
+
if (newPitch >= PITCH_RANGE_START && newPitch <= PITCH_RANGE_END)
|
|
14986
15439
|
active.core.moveNote(
|
|
14987
15440
|
note.id,
|
|
14988
15441
|
orig.startStep + snappedDelta,
|
|
@@ -14993,7 +15446,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14993
15446
|
const grab = selectedNotes[0];
|
|
14994
15447
|
const orig = selectedOriginal.find((o) => o.id === grab.id);
|
|
14995
15448
|
if (orig) {
|
|
14996
|
-
const newGrab = orig.pitch + deltaPitch;
|
|
15449
|
+
const newGrab = units(orig.pitch + deltaPitch);
|
|
14997
15450
|
if (newGrab !== lastMultiPreviewPitch && newGrab >= 0 && newGrab < 128) {
|
|
14998
15451
|
lastMultiPreviewPitch = newGrab;
|
|
14999
15452
|
playPreview(newGrab);
|
|
@@ -15130,22 +15583,23 @@ var mountDAW = (target, options = {}) => {
|
|
|
15130
15583
|
refs.bgOpacityRow.classList.add("dtm-hidden");
|
|
15131
15584
|
}
|
|
15132
15585
|
refs.bgRemoveBtn.classList.toggle("dtm-hidden", !blob2);
|
|
15133
|
-
setBackgroundActive(!!blob2);
|
|
15586
|
+
renderer.setBackgroundActive(!!blob2);
|
|
15134
15587
|
redrawAll();
|
|
15135
15588
|
};
|
|
15136
15589
|
const setupCanvas = () => {
|
|
15137
15590
|
const w = refs.rollContainer.clientWidth || 800;
|
|
15138
15591
|
const h = refs.rollContainer.clientHeight || 450;
|
|
15139
|
-
|
|
15140
|
-
|
|
15592
|
+
renderer?.destroy();
|
|
15593
|
+
renderer = createRenderer(refs.wrapper, w, h, renderConfig);
|
|
15594
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
15141
15595
|
gridCanvas.addEventListener("pointerdown", onGridPointerDown);
|
|
15142
15596
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
15143
15597
|
event.preventDefault();
|
|
15144
15598
|
if (isActiveLocked()) return;
|
|
15145
|
-
const { step, pitch } = getGridPosition(event);
|
|
15599
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15146
15600
|
const active = getActive();
|
|
15147
15601
|
const note = active.core.getNotes().find(
|
|
15148
|
-
(n) => n.
|
|
15602
|
+
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
15149
15603
|
);
|
|
15150
15604
|
if (note) active.core.deleteNoteById(note.id);
|
|
15151
15605
|
});
|
|
@@ -15163,7 +15617,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15163
15617
|
0,
|
|
15164
15618
|
getMaxOffsetX()
|
|
15165
15619
|
);
|
|
15166
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15620
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15167
15621
|
redrawAll();
|
|
15168
15622
|
},
|
|
15169
15623
|
{ passive: false }
|
|
@@ -15173,7 +15627,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15173
15627
|
suppressClick = false;
|
|
15174
15628
|
}
|
|
15175
15629
|
});
|
|
15176
|
-
const headerCanvas = getHeaderCanvas();
|
|
15630
|
+
const headerCanvas = renderer.getHeaderCanvas();
|
|
15177
15631
|
headerCanvas.addEventListener("click", (event) => {
|
|
15178
15632
|
if (playbackState === "playing") return;
|
|
15179
15633
|
const rect = headerCanvas.getBoundingClientRect();
|
|
@@ -15189,11 +15643,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15189
15643
|
}
|
|
15190
15644
|
redrawAll();
|
|
15191
15645
|
});
|
|
15192
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15646
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15193
15647
|
redrawAll();
|
|
15194
15648
|
};
|
|
15195
15649
|
const applyZoomX = () => {
|
|
15196
|
-
const canvas = getGridCanvas();
|
|
15650
|
+
const canvas = renderer.getGridCanvas();
|
|
15197
15651
|
const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
|
|
15198
15652
|
renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
|
|
15199
15653
|
refs.zoomXLabel.textContent = `${zoomX}%`;
|
|
@@ -15202,11 +15656,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15202
15656
|
0,
|
|
15203
15657
|
getMaxOffsetX()
|
|
15204
15658
|
);
|
|
15205
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15659
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15206
15660
|
redrawAll();
|
|
15207
15661
|
};
|
|
15208
15662
|
const applyZoomY = () => {
|
|
15209
|
-
const canvas = getGridCanvas();
|
|
15663
|
+
const canvas = renderer.getGridCanvas();
|
|
15210
15664
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
15211
15665
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
15212
15666
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
@@ -15215,7 +15669,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15215
15669
|
0,
|
|
15216
15670
|
getMaxOffsetY()
|
|
15217
15671
|
);
|
|
15218
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15672
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15219
15673
|
redrawAll();
|
|
15220
15674
|
};
|
|
15221
15675
|
const getViewState = () => ({
|
|
@@ -15227,7 +15681,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15227
15681
|
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
15228
15682
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
15229
15683
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
15230
|
-
options.onPlayNote?.({
|
|
15684
|
+
options.onPlayNote?.({
|
|
15685
|
+
trackId,
|
|
15686
|
+
pitchUnits: pitch,
|
|
15687
|
+
velocity,
|
|
15688
|
+
volume,
|
|
15689
|
+
when,
|
|
15690
|
+
duration
|
|
15691
|
+
});
|
|
15231
15692
|
};
|
|
15232
15693
|
const sequencer = createSequencer({
|
|
15233
15694
|
getTracks: () => trackStates.map((t) => ({
|
|
@@ -15255,7 +15716,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15255
15716
|
},
|
|
15256
15717
|
onTick: (step) => {
|
|
15257
15718
|
currentPlayStep = step;
|
|
15258
|
-
const canvas = getGridCanvas();
|
|
15719
|
+
const canvas = renderer.getGridCanvas();
|
|
15259
15720
|
const visibleSteps = canvas.width / renderConfig.stepWidth;
|
|
15260
15721
|
const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
|
|
15261
15722
|
if (currentPlayStep > threshold) {
|
|
@@ -15265,7 +15726,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15265
15726
|
0,
|
|
15266
15727
|
getMaxOffsetX()
|
|
15267
15728
|
);
|
|
15268
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15729
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15269
15730
|
}
|
|
15270
15731
|
redrawAll();
|
|
15271
15732
|
},
|
|
@@ -15296,7 +15757,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15296
15757
|
(a, b) => a.startStep - b.startStep
|
|
15297
15758
|
);
|
|
15298
15759
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
15299
|
-
const semis = (lt.octave ?? 0) *
|
|
15760
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
15300
15761
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
15301
15762
|
const notes = [];
|
|
15302
15763
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -15304,7 +15765,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15304
15765
|
if (n.startStep < fromStep) continue;
|
|
15305
15766
|
notes.push({
|
|
15306
15767
|
syllable: lt.syllables[i2],
|
|
15307
|
-
pitch: n.
|
|
15768
|
+
pitch: units(n.pitchUnits + semis),
|
|
15308
15769
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
15309
15770
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
15310
15771
|
});
|
|
@@ -15347,13 +15808,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
15347
15808
|
}
|
|
15348
15809
|
}
|
|
15349
15810
|
if (playbackState !== "paused") {
|
|
15350
|
-
const canvas = getGridCanvas();
|
|
15811
|
+
const canvas = renderer.getGridCanvas();
|
|
15351
15812
|
currentOffsetX = clamp5(
|
|
15352
15813
|
playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
|
|
15353
15814
|
0,
|
|
15354
15815
|
getMaxOffsetX()
|
|
15355
15816
|
);
|
|
15356
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15817
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15357
15818
|
}
|
|
15358
15819
|
playbackState = "playing";
|
|
15359
15820
|
sequencer.start(fromStep);
|
|
@@ -16340,6 +16801,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16340
16801
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16341
16802
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16342
16803
|
mode,
|
|
16804
|
+
edo: renderConfig.edo,
|
|
16343
16805
|
trackInstruments: trackInstMeta,
|
|
16344
16806
|
trackCompression: trackCompMeta,
|
|
16345
16807
|
trackWidth: trackWidthMeta,
|
|
@@ -16368,6 +16830,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16368
16830
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16369
16831
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16370
16832
|
mode,
|
|
16833
|
+
edo: renderConfig.edo,
|
|
16371
16834
|
trackInstruments: trackInstMeta,
|
|
16372
16835
|
trackCompression: trackCompMeta,
|
|
16373
16836
|
trackWidth: trackWidthMeta,
|
|
@@ -16507,19 +16970,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
16507
16970
|
}
|
|
16508
16971
|
}
|
|
16509
16972
|
if (candidateNotes.length === 0) return null;
|
|
16510
|
-
const sum = candidateNotes.reduce((acc, note) => acc + note.
|
|
16511
|
-
return Math.round(sum / candidateNotes.length);
|
|
16973
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitchUnits, 0);
|
|
16974
|
+
return units(Math.round(sum / candidateNotes.length));
|
|
16512
16975
|
};
|
|
16513
16976
|
const centerPitch = (pitch) => {
|
|
16514
|
-
const canvas = getGridCanvas();
|
|
16515
|
-
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
16977
|
+
const canvas = renderer.getGridCanvas();
|
|
16978
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE);
|
|
16516
16979
|
const logicalY = yIndex * renderConfig.keyHeight;
|
|
16517
16980
|
currentOffsetY = clamp5(
|
|
16518
16981
|
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
16519
16982
|
0,
|
|
16520
16983
|
getMaxOffsetY()
|
|
16521
16984
|
);
|
|
16522
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16985
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16523
16986
|
};
|
|
16524
16987
|
const clearAll = () => {
|
|
16525
16988
|
for (const t of trackStates) {
|
|
@@ -16577,10 +17040,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
16577
17040
|
if (!meta.drumFont) applyDrumPatternFont(meta.drum);
|
|
16578
17041
|
}
|
|
16579
17042
|
if (meta.volume !== void 0) {
|
|
16580
|
-
|
|
16581
|
-
refs.masterVolume.value = String(meta.volume);
|
|
16582
|
-
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
17043
|
+
applyMasterVolume(meta.volume);
|
|
16583
17044
|
}
|
|
17045
|
+
applyEdo(meta.edo ?? 12);
|
|
17046
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16584
17047
|
if (meta.drumVolume !== void 0) {
|
|
16585
17048
|
drumVolume = meta.drumVolume;
|
|
16586
17049
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -16745,7 +17208,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16745
17208
|
if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
|
|
16746
17209
|
const t = trackStates[p.trackIndex];
|
|
16747
17210
|
if (!t) continue;
|
|
16748
|
-
t.core.addNote(p.startStep, p.
|
|
17211
|
+
t.core.addNote(p.startStep, p.pitchUnits, {
|
|
16749
17212
|
noteLengthSteps: p.durationSteps,
|
|
16750
17213
|
velocity: DEFAULT_VELOCITY
|
|
16751
17214
|
});
|
|
@@ -16762,7 +17225,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16762
17225
|
if (firstPitch !== null) {
|
|
16763
17226
|
centerPitch(firstPitch);
|
|
16764
17227
|
} else {
|
|
16765
|
-
centerPitch(48);
|
|
17228
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16766
17229
|
}
|
|
16767
17230
|
redrawAll();
|
|
16768
17231
|
updateTrackPanel();
|
|
@@ -16780,6 +17243,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16780
17243
|
const chordTrack = trackStates.find((t) => t.config.id === "chord");
|
|
16781
17244
|
if (!chordTrack) return;
|
|
16782
17245
|
const placements = buildChordPlacements({
|
|
17246
|
+
// 和音は五度連鎖経由で音律の格子へ写す(31平均律では長3度が10度になる)
|
|
17247
|
+
edo: renderConfig.edo,
|
|
16783
17248
|
chordStr: active.savedChordInput,
|
|
16784
17249
|
patternType: active.savedChordPattern,
|
|
16785
17250
|
rootShift: active.savedChordRoot,
|
|
@@ -16789,7 +17254,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16789
17254
|
chordTrack.core.clearNotesWithoutHistory();
|
|
16790
17255
|
chordTrack.core.beginBatch();
|
|
16791
17256
|
for (const p of placements) {
|
|
16792
|
-
chordTrack.core.addNote(p.startStep, p.
|
|
17257
|
+
chordTrack.core.addNote(p.startStep, p.pitchUnits, {
|
|
16793
17258
|
noteLengthSteps: Math.max(1, p.durationSteps),
|
|
16794
17259
|
velocity: p.velocity
|
|
16795
17260
|
});
|
|
@@ -16853,7 +17318,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16853
17318
|
if (applyActiveOnly && p.trackId !== activeTrackId) continue;
|
|
16854
17319
|
const t = trackStates.find((ts) => ts.config.id === p.trackId);
|
|
16855
17320
|
if (!t) continue;
|
|
16856
|
-
t.core.addNote(p.startStep, p.pitch, {
|
|
17321
|
+
t.core.addNote(p.startStep, snapToEdoGrid(pitchV1ToUnits(p.pitch)), {
|
|
16857
17322
|
noteLengthSteps: p.durationSteps,
|
|
16858
17323
|
velocity: p.velocity
|
|
16859
17324
|
});
|
|
@@ -16870,7 +17335,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16870
17335
|
if (firstPitch !== null) {
|
|
16871
17336
|
centerPitch(firstPitch);
|
|
16872
17337
|
} else {
|
|
16873
|
-
centerPitch(48);
|
|
17338
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16874
17339
|
}
|
|
16875
17340
|
redrawAll();
|
|
16876
17341
|
updateTrackPanel();
|
|
@@ -16934,18 +17399,21 @@ var mountDAW = (target, options = {}) => {
|
|
|
16934
17399
|
}
|
|
16935
17400
|
}, 30);
|
|
16936
17401
|
};
|
|
16937
|
-
const showConfirmModal = (message) => new Promise((resolve) => {
|
|
17402
|
+
const showConfirmModal = (message, opts) => new Promise((resolve) => {
|
|
17403
|
+
const title = opts?.title ?? "\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D";
|
|
17404
|
+
const yes = opts?.yes ?? "\u306F\u3044\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u308B\uFF09";
|
|
17405
|
+
const no = opts?.no ?? "\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09";
|
|
16938
17406
|
const overlay = document.createElement("div");
|
|
16939
17407
|
overlay.className = "dtm-modal-overlay";
|
|
16940
17408
|
overlay.innerHTML = `
|
|
16941
17409
|
<div class="dtm-modal">
|
|
16942
17410
|
<div class="dtm-modal-header">
|
|
16943
|
-
<span class="dtm-modal-title"
|
|
17411
|
+
<span class="dtm-modal-title">${title}</span>
|
|
16944
17412
|
</div>
|
|
16945
17413
|
<div class="dtm-modal-body"><p>${message}</p></div>
|
|
16946
17414
|
<div class="dtm-confirm-footer">
|
|
16947
|
-
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no"
|
|
16948
|
-
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes"
|
|
17415
|
+
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no">${no}</button>
|
|
17416
|
+
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes">${yes}</button>
|
|
16949
17417
|
</div>
|
|
16950
17418
|
</div>`;
|
|
16951
17419
|
const close = (result) => {
|
|
@@ -17053,10 +17521,45 @@ var mountDAW = (target, options = {}) => {
|
|
|
17053
17521
|
});
|
|
17054
17522
|
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
17055
17523
|
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
17524
|
+
refs.edoSelect.addEventListener("change", async () => {
|
|
17525
|
+
const next = Number.parseInt(refs.edoSelect.value, 10) === 31 ? 31 : 12;
|
|
17526
|
+
const prev = renderConfig.edo ?? 12;
|
|
17527
|
+
if (next === prev) return;
|
|
17528
|
+
const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
|
|
17529
|
+
if (next === 12 && prev === 31 && hasNotes) {
|
|
17530
|
+
const okToDrop = await showConfirmModal(
|
|
17531
|
+
"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",
|
|
17532
|
+
{
|
|
17533
|
+
title: "\u97F3\u5F8B\u306E\u78BA\u8A8D",
|
|
17534
|
+
yes: "\u5207\u308A\u66FF\u3048\u308B",
|
|
17535
|
+
no: "\u3084\u3081\u308B"
|
|
17536
|
+
}
|
|
17537
|
+
);
|
|
17538
|
+
if (!okToDrop) {
|
|
17539
|
+
refs.edoSelect.value = String(prev);
|
|
17540
|
+
return;
|
|
17541
|
+
}
|
|
17542
|
+
}
|
|
17543
|
+
overlayDuring(() => {
|
|
17544
|
+
applyEdo(next, { snap: true });
|
|
17545
|
+
if (next === 31 && prev === 12) {
|
|
17546
|
+
zoomY = Math.max(50, Math.round(zoomY / 2 / 25) * 25);
|
|
17547
|
+
} else if (next === 12 && prev === 31) {
|
|
17548
|
+
zoomY = Math.min(200, Math.round(zoomY * 2 / 25) * 25);
|
|
17549
|
+
}
|
|
17550
|
+
applyZoomY();
|
|
17551
|
+
const first = getFirstDetectedPitch();
|
|
17552
|
+
centerPitch(first ?? pitchV1ToUnits(48));
|
|
17553
|
+
redrawAll();
|
|
17554
|
+
updateUndoRedo();
|
|
17555
|
+
notifyViewState();
|
|
17556
|
+
});
|
|
17557
|
+
});
|
|
17558
|
+
refs.edoInfoBtn.addEventListener("click", () => {
|
|
17559
|
+
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
17560
|
+
});
|
|
17056
17561
|
refs.masterVolume.addEventListener("input", () => {
|
|
17057
|
-
|
|
17058
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17059
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17562
|
+
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
17060
17563
|
});
|
|
17061
17564
|
refs.masterComp.addEventListener("input", () => {
|
|
17062
17565
|
masterCompression = Number.parseInt(refs.masterComp.value, 10) || 0;
|
|
@@ -17120,10 +17623,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17120
17623
|
const scale = targetPeak / observedPeakMax;
|
|
17121
17624
|
const suggested = clamp5(Math.round(masterVolume * scale), 10, 100);
|
|
17122
17625
|
if (suggested !== masterVolume) {
|
|
17123
|
-
|
|
17124
|
-
refs.masterVolume.value = String(masterVolume);
|
|
17125
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17126
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17626
|
+
applyMasterVolume(suggested);
|
|
17127
17627
|
}
|
|
17128
17628
|
observedPeakMax = 0;
|
|
17129
17629
|
observedPlayMs = 0;
|
|
@@ -17283,7 +17783,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
17283
17783
|
generateRandomPattern(getActive().core, {
|
|
17284
17784
|
stepsPerBar: renderConfig.stepsPerBar,
|
|
17285
17785
|
startStep: playStartStep,
|
|
17286
|
-
pitchRangeStart: renderConfig.pitchRangeStart
|
|
17786
|
+
pitchRangeStart: renderConfig.pitchRangeStart,
|
|
17787
|
+
edo: renderConfig.edo
|
|
17287
17788
|
});
|
|
17288
17789
|
redrawAll();
|
|
17289
17790
|
});
|
|
@@ -17620,7 +18121,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17620
18121
|
id: 0,
|
|
17621
18122
|
startStep: p.startStep,
|
|
17622
18123
|
durationSteps: p.durationSteps,
|
|
17623
|
-
|
|
18124
|
+
pitchUnits: snapToEdoGrid(pitchV1ToUnits(p.pitch)),
|
|
17624
18125
|
velocity: p.velocity
|
|
17625
18126
|
}));
|
|
17626
18127
|
const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
|
|
@@ -18075,10 +18576,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18075
18576
|
const newStart = playStartStep + (note.startStep - minStart);
|
|
18076
18577
|
const newEnd = newStart + note.durationSteps;
|
|
18077
18578
|
const overlap = notes.some(
|
|
18078
|
-
(ex) => ex.
|
|
18579
|
+
(ex) => ex.pitchUnits === note.pitchUnits && newStart < ex.startStep + ex.durationSteps && newEnd > ex.startStep
|
|
18079
18580
|
);
|
|
18080
18581
|
if (!overlap)
|
|
18081
|
-
core.addNote(newStart, note.
|
|
18582
|
+
core.addNote(newStart, note.pitchUnits, {
|
|
18082
18583
|
noteLengthSteps: note.durationSteps,
|
|
18083
18584
|
velocity: note.velocity
|
|
18084
18585
|
});
|
|
@@ -18151,13 +18652,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
18151
18652
|
pausedPlayStep = step;
|
|
18152
18653
|
currentPlayStep = step;
|
|
18153
18654
|
playbackState = "paused";
|
|
18154
|
-
const canvas = getGridCanvas();
|
|
18655
|
+
const canvas = renderer.getGridCanvas();
|
|
18155
18656
|
currentOffsetX = clamp5(
|
|
18156
18657
|
step * renderConfig.stepWidth - canvas.width * 0.5,
|
|
18157
18658
|
0,
|
|
18158
18659
|
getMaxOffsetX()
|
|
18159
18660
|
);
|
|
18160
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18661
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18161
18662
|
updateTransport();
|
|
18162
18663
|
redrawAll();
|
|
18163
18664
|
};
|
|
@@ -18225,16 +18726,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18225
18726
|
forcePauseAt,
|
|
18226
18727
|
setLoading,
|
|
18227
18728
|
setMasterVolume: (volume) => {
|
|
18228
|
-
|
|
18229
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18230
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18231
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18729
|
+
applyMasterVolume(volume);
|
|
18232
18730
|
},
|
|
18233
18731
|
setVolume: (volume) => {
|
|
18234
|
-
|
|
18235
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18236
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18237
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18732
|
+
applyMasterVolume(volume);
|
|
18238
18733
|
},
|
|
18239
18734
|
setDrumVolume: (volume) => {
|
|
18240
18735
|
drumVolume = clamp5(volume, 0, 100);
|
|
@@ -18277,15 +18772,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
18277
18772
|
suppressPatch = true;
|
|
18278
18773
|
track.core.beginBatch();
|
|
18279
18774
|
for (const n of added) {
|
|
18280
|
-
const existing = track.core.getNotes().find(
|
|
18775
|
+
const existing = track.core.getNotes().find(
|
|
18776
|
+
(e) => e.startStep === n.startStep && e.pitchUnits === n.pitchUnits
|
|
18777
|
+
);
|
|
18281
18778
|
if (existing) track.core.deleteNoteById(existing.id);
|
|
18282
|
-
track.core.addNote(n.startStep, n.
|
|
18779
|
+
track.core.addNote(n.startStep, n.pitchUnits, {
|
|
18283
18780
|
noteLengthSteps: n.durationSteps,
|
|
18284
18781
|
velocity: n.velocity
|
|
18285
18782
|
});
|
|
18286
18783
|
}
|
|
18287
18784
|
for (const r of removed) {
|
|
18288
|
-
const note = track.core.getNotes().find(
|
|
18785
|
+
const note = track.core.getNotes().find(
|
|
18786
|
+
(n) => n.startStep === r.startStep && n.pitchUnits === r.pitchUnits
|
|
18787
|
+
);
|
|
18289
18788
|
if (note) track.core.deleteNoteById(note.id);
|
|
18290
18789
|
}
|
|
18291
18790
|
track.core.endBatch();
|
|
@@ -18327,9 +18826,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
18327
18826
|
if (t.config.id === activeTrackId) updateTrackPanel();
|
|
18328
18827
|
},
|
|
18329
18828
|
noteToCanvas: (step, pitch) => {
|
|
18330
|
-
const canvas = getGridCanvas();
|
|
18829
|
+
const canvas = renderer.getGridCanvas();
|
|
18331
18830
|
const x2 = step * renderConfig.stepWidth - currentOffsetX;
|
|
18332
|
-
const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
|
|
18831
|
+
const y = (renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - currentOffsetY;
|
|
18333
18832
|
const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
|
|
18334
18833
|
let side = null;
|
|
18335
18834
|
if (!onScreen) {
|
|
@@ -18393,7 +18892,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18393
18892
|
id: id++,
|
|
18394
18893
|
startStep: p.startStep,
|
|
18395
18894
|
durationSteps: p.durationSteps,
|
|
18396
|
-
|
|
18895
|
+
pitchUnits: p.pitchUnits,
|
|
18397
18896
|
velocity: p.velocity
|
|
18398
18897
|
}));
|
|
18399
18898
|
return {
|
|
@@ -18419,7 +18918,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18419
18918
|
(a, b) => a.startStep - b.startStep
|
|
18420
18919
|
);
|
|
18421
18920
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
18422
|
-
const semis = (lt.octave ?? 0) *
|
|
18921
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
18423
18922
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
18424
18923
|
const notes = [];
|
|
18425
18924
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -18427,7 +18926,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18427
18926
|
if (n.startStep < fromStep) continue;
|
|
18428
18927
|
notes.push({
|
|
18429
18928
|
syllable: lt.syllables[i2],
|
|
18430
|
-
pitch: n.
|
|
18929
|
+
pitch: units(n.pitchUnits + semis),
|
|
18431
18930
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
18432
18931
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
18433
18932
|
});
|
|
@@ -18615,24 +19114,28 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18615
19114
|
config,
|
|
18616
19115
|
noteLengthSteps = 1
|
|
18617
19116
|
} = options;
|
|
18618
|
-
|
|
19117
|
+
const renderer = createRenderer(mountTarget, width, height, config);
|
|
18619
19118
|
let currentNoteLengthSteps = noteLengthSteps;
|
|
18620
19119
|
let selectionRect = null;
|
|
18621
19120
|
let isSelecting = false;
|
|
18622
19121
|
let selectionStart = null;
|
|
18623
19122
|
let selectedNotes = [];
|
|
18624
19123
|
let copiedNotes = [];
|
|
18625
|
-
const core = new MMLCore(
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
19124
|
+
const core = new MMLCore(
|
|
19125
|
+
{
|
|
19126
|
+
onMMLGenerated: handlers.onMMLGenerated,
|
|
19127
|
+
onNotesChanged: (notes) => {
|
|
19128
|
+
handlers.onNotesChanged(notes);
|
|
19129
|
+
}
|
|
19130
|
+
},
|
|
19131
|
+
80,
|
|
19132
|
+
() => config
|
|
19133
|
+
);
|
|
18631
19134
|
const getAddNoteOptions = () => ({
|
|
18632
19135
|
noteLengthSteps: currentNoteLengthSteps
|
|
18633
19136
|
});
|
|
18634
19137
|
let suppressClick = false;
|
|
18635
|
-
onClick((step, pitch) => {
|
|
19138
|
+
renderer.onClick((step, pitch) => {
|
|
18636
19139
|
if (suppressClick) {
|
|
18637
19140
|
suppressClick = false;
|
|
18638
19141
|
return;
|
|
@@ -18644,7 +19147,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18644
19147
|
} else if (mode === "eraser") {
|
|
18645
19148
|
const notes = core.getNotes();
|
|
18646
19149
|
const note = notes.find(
|
|
18647
|
-
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.
|
|
19150
|
+
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.pitchUnits === pitch
|
|
18648
19151
|
);
|
|
18649
19152
|
if (note) {
|
|
18650
19153
|
core.deleteNoteById(note.id);
|
|
@@ -18652,17 +19155,23 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18652
19155
|
}
|
|
18653
19156
|
}
|
|
18654
19157
|
});
|
|
18655
|
-
const gridCanvas = getGridCanvas();
|
|
19158
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
18656
19159
|
const resizeHandleWidth = 6;
|
|
18657
19160
|
let dragState = null;
|
|
18658
19161
|
let hasDragged = false;
|
|
18659
19162
|
let lastPreviewPitch = null;
|
|
18660
19163
|
const findNoteAtPosition = (x2, y) => {
|
|
18661
|
-
const {
|
|
18662
|
-
|
|
19164
|
+
const {
|
|
19165
|
+
stepWidth,
|
|
19166
|
+
keyHeight,
|
|
19167
|
+
keyCount,
|
|
19168
|
+
pitchRangeStart,
|
|
19169
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19170
|
+
} = renderer.getRenderConfig();
|
|
19171
|
+
const offset = renderer.getDrawOffset();
|
|
18663
19172
|
for (const note of core.getNotes()) {
|
|
18664
19173
|
const logicalX = note.startStep * stepWidth;
|
|
18665
|
-
const yIndex = keyCount - 1 - (note.
|
|
19174
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18666
19175
|
const logicalY = yIndex * keyHeight;
|
|
18667
19176
|
const w = note.durationSteps * stepWidth;
|
|
18668
19177
|
const h = keyHeight;
|
|
@@ -18676,7 +19185,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18676
19185
|
};
|
|
18677
19186
|
const handlePointerMove = (e) => {
|
|
18678
19187
|
if (core.getToolMode() === "select" && isSelecting && selectionStart) {
|
|
18679
|
-
const { x: x2, y } = getGridPosition(e);
|
|
19188
|
+
const { x: x2, y } = renderer.getGridPosition(e);
|
|
18680
19189
|
const minX = Math.min(x2, selectionStart.x);
|
|
18681
19190
|
const minY = Math.min(y, selectionStart.y);
|
|
18682
19191
|
const width2 = Math.abs(x2 - selectionStart.x);
|
|
@@ -18688,7 +19197,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18688
19197
|
}
|
|
18689
19198
|
if (!dragState) return;
|
|
18690
19199
|
hasDragged = true;
|
|
18691
|
-
const { step, pitch } = getGridPosition(e);
|
|
19200
|
+
const { step, pitch } = renderer.getGridPosition(e);
|
|
18692
19201
|
if (dragState.mode === "move") {
|
|
18693
19202
|
if (dragState.selectedNotes && dragState.selectedNotes.length > 0) {
|
|
18694
19203
|
const noteId = dragState.noteId;
|
|
@@ -18697,10 +19206,10 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18697
19206
|
const nextStart2 = step - dragState.dragOffsetStep;
|
|
18698
19207
|
const nextPitch2 = pitch - dragState.dragOffsetPitch;
|
|
18699
19208
|
const stepDelta = nextStart2 - baseNote.startStep;
|
|
18700
|
-
const pitchDelta = nextPitch2 - baseNote.
|
|
19209
|
+
const pitchDelta = nextPitch2 - baseNote.pitchUnits;
|
|
18701
19210
|
for (const note of dragState.selectedNotes) {
|
|
18702
19211
|
const newStart = note.startStep + stepDelta;
|
|
18703
|
-
const newPitch = note.
|
|
19212
|
+
const newPitch = units(note.pitchUnits + pitchDelta);
|
|
18704
19213
|
core.moveNote(note.id, newStart, newPitch);
|
|
18705
19214
|
}
|
|
18706
19215
|
if (options.onPreviewSound && pitch !== lastPreviewPitch) {
|
|
@@ -18711,7 +19220,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18711
19220
|
return;
|
|
18712
19221
|
}
|
|
18713
19222
|
const nextStart = step - dragState.dragOffsetStep;
|
|
18714
|
-
const nextPitch = pitch - dragState.dragOffsetPitch;
|
|
19223
|
+
const nextPitch = units(pitch - dragState.dragOffsetPitch);
|
|
18715
19224
|
core.moveNote(dragState.noteId, nextStart, nextPitch);
|
|
18716
19225
|
return;
|
|
18717
19226
|
}
|
|
@@ -18741,7 +19250,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18741
19250
|
}
|
|
18742
19251
|
};
|
|
18743
19252
|
gridCanvas.addEventListener("pointerdown", (e) => {
|
|
18744
|
-
const { x: x2, y, step, pitch } = getGridPosition(e);
|
|
19253
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(e);
|
|
18745
19254
|
const currentMode = core.getToolMode();
|
|
18746
19255
|
if (currentMode === "select") {
|
|
18747
19256
|
const clickedNote = findNoteAtPosition(x2, y);
|
|
@@ -18752,7 +19261,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18752
19261
|
noteId: clickedNote.id,
|
|
18753
19262
|
mode: "move",
|
|
18754
19263
|
dragOffsetStep: step - clickedNote.startStep,
|
|
18755
|
-
dragOffsetPitch: pitch - clickedNote.
|
|
19264
|
+
dragOffsetPitch: pitch - clickedNote.pitchUnits,
|
|
18756
19265
|
startStep: clickedNote.startStep,
|
|
18757
19266
|
selectedNotes: notesInRect
|
|
18758
19267
|
// 複数選択ノートを保存
|
|
@@ -18770,10 +19279,16 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18770
19279
|
}
|
|
18771
19280
|
const note = findNoteAtPosition(x2, y);
|
|
18772
19281
|
if (!note) return;
|
|
18773
|
-
const {
|
|
18774
|
-
|
|
19282
|
+
const {
|
|
19283
|
+
stepWidth,
|
|
19284
|
+
keyHeight,
|
|
19285
|
+
keyCount,
|
|
19286
|
+
pitchRangeStart,
|
|
19287
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19288
|
+
} = renderer.getRenderConfig();
|
|
19289
|
+
const offset = renderer.getDrawOffset();
|
|
18775
19290
|
const logicalX = note.startStep * stepWidth;
|
|
18776
|
-
const yIndex = keyCount - 1 - (note.
|
|
19291
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18777
19292
|
const logicalY = yIndex * keyHeight;
|
|
18778
19293
|
const renderX = logicalX - offset.x;
|
|
18779
19294
|
const renderY = logicalY - offset.y;
|
|
@@ -18792,7 +19307,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18792
19307
|
noteId: note.id,
|
|
18793
19308
|
mode: "move",
|
|
18794
19309
|
dragOffsetStep: step - note.startStep,
|
|
18795
|
-
dragOffsetPitch: pitch - note.
|
|
19310
|
+
dragOffsetPitch: pitch - note.pitchUnits,
|
|
18796
19311
|
startStep: note.startStep
|
|
18797
19312
|
};
|
|
18798
19313
|
});
|
|
@@ -18803,41 +19318,47 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18803
19318
|
"wheel",
|
|
18804
19319
|
(e) => {
|
|
18805
19320
|
e.preventDefault();
|
|
18806
|
-
const configValues = getRenderConfig();
|
|
19321
|
+
const configValues = renderer.getRenderConfig();
|
|
18807
19322
|
const gridHeight = gridCanvas.height;
|
|
18808
19323
|
const maxOffsetY = Math.max(
|
|
18809
19324
|
0,
|
|
18810
19325
|
configValues.keyCount * configValues.keyHeight - gridHeight
|
|
18811
19326
|
);
|
|
18812
|
-
const currentOffset = getDrawOffset();
|
|
19327
|
+
const currentOffset = renderer.getDrawOffset();
|
|
18813
19328
|
const nextOffsetY = Math.min(
|
|
18814
19329
|
Math.max(currentOffset.y + e.deltaY, 0),
|
|
18815
19330
|
maxOffsetY
|
|
18816
19331
|
);
|
|
18817
|
-
setDrawOffset(currentOffset.x, nextOffsetY);
|
|
18818
|
-
drawGrid();
|
|
18819
|
-
drawNotes(core.getNotes());
|
|
19332
|
+
renderer.setDrawOffset(currentOffset.x, nextOffsetY);
|
|
19333
|
+
renderer.drawGrid();
|
|
19334
|
+
renderer.drawNotes(core.getNotes());
|
|
18820
19335
|
},
|
|
18821
19336
|
{ passive: false }
|
|
18822
19337
|
);
|
|
18823
19338
|
const redraw = () => {
|
|
18824
|
-
drawGrid();
|
|
18825
|
-
drawNotes(core.getNotes());
|
|
19339
|
+
renderer.drawGrid();
|
|
19340
|
+
renderer.drawNotes(core.getNotes());
|
|
18826
19341
|
if (core.getToolMode() === "select") {
|
|
18827
|
-
drawSelectionRect(selectionRect);
|
|
19342
|
+
renderer.drawSelectionRect(selectionRect);
|
|
18828
19343
|
if (selectedNotes.length > 0) {
|
|
18829
19344
|
const selectedIds = new Set(selectedNotes.map((n) => n.id));
|
|
18830
|
-
drawSelectedNotes(core.getNotes(), selectedIds);
|
|
19345
|
+
renderer.drawSelectedNotes(core.getNotes(), selectedIds);
|
|
18831
19346
|
}
|
|
18832
19347
|
}
|
|
18833
19348
|
};
|
|
18834
19349
|
const getNotesInRect = (rect) => {
|
|
18835
|
-
const {
|
|
18836
|
-
|
|
19350
|
+
const {
|
|
19351
|
+
stepWidth,
|
|
19352
|
+
keyHeight,
|
|
19353
|
+
keyCount,
|
|
19354
|
+
pitchRangeStart,
|
|
19355
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19356
|
+
} = renderer.getRenderConfig();
|
|
19357
|
+
const offset = renderer.getDrawOffset();
|
|
18837
19358
|
const notes = [];
|
|
18838
19359
|
for (const note of core.getNotes()) {
|
|
18839
19360
|
const logicalX = note.startStep * stepWidth;
|
|
18840
|
-
const yIndex = keyCount - 1 - (note.
|
|
19361
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18841
19362
|
const logicalY = yIndex * keyHeight;
|
|
18842
19363
|
const noteRect = {
|
|
18843
19364
|
x: logicalX - offset.x,
|
|
@@ -18884,7 +19405,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18884
19405
|
const minStart = Math.min(...copiedNotes.map((n) => n.startStep));
|
|
18885
19406
|
copiedNotes.forEach((note) => {
|
|
18886
19407
|
const newStep = startStep + (note.startStep - minStart);
|
|
18887
|
-
core.addNote(newStep, note.
|
|
19408
|
+
core.addNote(newStep, note.pitchUnits, {
|
|
18888
19409
|
noteLengthSteps: note.durationSteps,
|
|
18889
19410
|
velocity: note.velocity
|
|
18890
19411
|
});
|
|
@@ -19725,10 +20246,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
19725
20246
|
);
|
|
19726
20247
|
}
|
|
19727
20248
|
if (!sfInst) return;
|
|
20249
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
19728
20250
|
sfInst.play({
|
|
19729
20251
|
ctx: audioCtx,
|
|
19730
20252
|
destination: getChannelStrip(e.trackId).input,
|
|
19731
|
-
pitch:
|
|
20253
|
+
pitch: midi,
|
|
20254
|
+
detuneCents,
|
|
19732
20255
|
volume: e.volume,
|
|
19733
20256
|
velocity: e.velocity,
|
|
19734
20257
|
when: e.when,
|
|
@@ -20044,10 +20567,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20044
20567
|
);
|
|
20045
20568
|
}
|
|
20046
20569
|
if (!sfInst) return;
|
|
20570
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20047
20571
|
sfInst.play({
|
|
20048
20572
|
ctx: audioCtx,
|
|
20049
20573
|
destination: getChannelStrip(e.trackId).input,
|
|
20050
|
-
pitch:
|
|
20574
|
+
pitch: midi,
|
|
20575
|
+
detuneCents,
|
|
20051
20576
|
volume: e.volume,
|
|
20052
20577
|
velocity: e.velocity,
|
|
20053
20578
|
when: e.when,
|
|
@@ -20133,10 +20658,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20133
20658
|
);
|
|
20134
20659
|
}
|
|
20135
20660
|
if (!sfInst) return;
|
|
20661
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20136
20662
|
sfInst.play({
|
|
20137
20663
|
ctx: audioCtx,
|
|
20138
20664
|
destination: getChannelStrip(e.trackId).input,
|
|
20139
|
-
pitch:
|
|
20665
|
+
pitch: midi,
|
|
20666
|
+
detuneCents,
|
|
20140
20667
|
volume: e.volume,
|
|
20141
20668
|
velocity: e.velocity,
|
|
20142
20669
|
when: e.when,
|
|
@@ -20215,10 +20742,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20215
20742
|
);
|
|
20216
20743
|
}
|
|
20217
20744
|
if (!sfInst) return;
|
|
20745
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20218
20746
|
sfInst.play({
|
|
20219
20747
|
ctx: audioCtx,
|
|
20220
20748
|
destination: getChannelStrip(e.trackId).input,
|
|
20221
|
-
pitch:
|
|
20749
|
+
pitch: midi,
|
|
20750
|
+
detuneCents,
|
|
20222
20751
|
volume: e.volume,
|
|
20223
20752
|
velocity: e.velocity,
|
|
20224
20753
|
when: e.when,
|
|
@@ -20255,10 +20784,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20255
20784
|
sfInst = resolveSoundFont(defaultPreset, role, "simple");
|
|
20256
20785
|
}
|
|
20257
20786
|
if (!sfInst) return;
|
|
20787
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20258
20788
|
sfInst.play({
|
|
20259
20789
|
ctx: audioCtx,
|
|
20260
20790
|
destination: getChannelStrip(e.trackId).input,
|
|
20261
|
-
pitch:
|
|
20791
|
+
pitch: midi,
|
|
20792
|
+
detuneCents,
|
|
20262
20793
|
volume: e.volume,
|
|
20263
20794
|
velocity: e.velocity,
|
|
20264
20795
|
when: e.when,
|
|
@@ -20281,10 +20812,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20281
20812
|
if (audioCtx.state === "suspended") {
|
|
20282
20813
|
await audioCtx.resume();
|
|
20283
20814
|
}
|
|
20815
|
+
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
20284
20816
|
sfInst.play({
|
|
20285
20817
|
ctx: audioCtx,
|
|
20286
20818
|
destination: masterGain,
|
|
20287
|
-
pitch:
|
|
20819
|
+
pitch: midi,
|
|
20820
|
+
detuneCents,
|
|
20288
20821
|
volume: vol / 100,
|
|
20289
20822
|
when: 0,
|
|
20290
20823
|
duration: dur
|
|
@@ -20304,7 +20837,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20304
20837
|
// 伴奏トラック
|
|
20305
20838
|
startStep: p.startStep,
|
|
20306
20839
|
durationSteps: p.durationSteps,
|
|
20307
|
-
|
|
20840
|
+
pitchUnits: p.pitchUnits,
|
|
20308
20841
|
velocity: p.velocity
|
|
20309
20842
|
}));
|
|
20310
20843
|
const playerPreset = defaultPreset;
|
|
@@ -20312,10 +20845,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20312
20845
|
const playPlayerNote = (e) => {
|
|
20313
20846
|
const sfInst = resolveSoundFont(playerPreset, "chord");
|
|
20314
20847
|
if (!sfInst) return;
|
|
20848
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20315
20849
|
sfInst.play({
|
|
20316
20850
|
ctx: audioCtx,
|
|
20317
20851
|
destination: getChannelStrip("chord").input,
|
|
20318
|
-
pitch:
|
|
20852
|
+
pitch: midi,
|
|
20853
|
+
detuneCents,
|
|
20319
20854
|
volume: e.volume,
|
|
20320
20855
|
velocity: e.velocity,
|
|
20321
20856
|
when: e.when,
|
|
@@ -20453,8 +20988,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20453
20988
|
};
|
|
20454
20989
|
};
|
|
20455
20990
|
export {
|
|
20991
|
+
A4_HZ,
|
|
20992
|
+
A4_UNITS,
|
|
20993
|
+
CENTS_PER_UNIT,
|
|
20456
20994
|
DAW_CSS,
|
|
20457
20995
|
DEFAULT_BPM,
|
|
20996
|
+
DEFAULT_EDO,
|
|
20458
20997
|
DEFAULT_GATE,
|
|
20459
20998
|
DEFAULT_PAN,
|
|
20460
20999
|
DEFAULT_PLAYBACK_VELOCITY,
|
|
@@ -20464,30 +21003,41 @@ export {
|
|
|
20464
21003
|
DRUM_FONT,
|
|
20465
21004
|
DRUM_KEYS,
|
|
20466
21005
|
DRUM_PATTERNS,
|
|
21006
|
+
EDO31_NAMES,
|
|
20467
21007
|
GM_INSTRUMENT_NAMES,
|
|
20468
21008
|
INSTRUMENT_PRESETS,
|
|
21009
|
+
KEY_COUNT,
|
|
20469
21010
|
KOE_BASE_URL,
|
|
20470
21011
|
KOE_VOICEBANKS,
|
|
20471
21012
|
KOE_VOICEBANK_LABELS,
|
|
20472
21013
|
KOE_VOICEBANK_TERMS,
|
|
20473
21014
|
LinkedList,
|
|
20474
21015
|
MAX_VOCAL_VOLUME,
|
|
21016
|
+
MICRO_STEP,
|
|
20475
21017
|
MMLCore,
|
|
20476
21018
|
MML_END_MARKER,
|
|
20477
21019
|
MidiSearchClient,
|
|
20478
|
-
|
|
21020
|
+
PITCH_ENCODING_VERSION,
|
|
21021
|
+
PITCH_MAP,
|
|
21022
|
+
PITCH_RANGE_END,
|
|
21023
|
+
PITCH_RANGE_START,
|
|
20479
21024
|
PREWARM_NOTES,
|
|
20480
21025
|
TRACKS_ADVANCED,
|
|
20481
21026
|
TRACKS_SIMPLE,
|
|
21027
|
+
UNITS_PER_EDO31_DEGREE,
|
|
21028
|
+
UNITS_PER_OCTAVE,
|
|
21029
|
+
UNITS_PER_SEMITONE,
|
|
20482
21030
|
VIBRATO_MIN_SEC,
|
|
20483
21031
|
VOICE_IMAGES,
|
|
20484
21032
|
VOICE_IMAGE_KEY,
|
|
21033
|
+
addUnits,
|
|
20485
21034
|
analyzeMidiTracks,
|
|
20486
21035
|
applyHarmonicFilter,
|
|
20487
21036
|
applyMonophonic,
|
|
20488
21037
|
buildChordPlacements,
|
|
20489
21038
|
buildDrumPatternJson,
|
|
20490
21039
|
buildNameToKeyMapping,
|
|
21040
|
+
chromaticStep,
|
|
20491
21041
|
collectPitchTokens,
|
|
20492
21042
|
concatFloat32,
|
|
20493
21043
|
createAudioContext,
|
|
@@ -20496,19 +21046,13 @@ export {
|
|
|
20496
21046
|
createKoeVoice,
|
|
20497
21047
|
createLyricsConductor,
|
|
20498
21048
|
createPianoRoll,
|
|
21049
|
+
createRenderer,
|
|
20499
21050
|
createSequencer,
|
|
20500
21051
|
createSingingVoices,
|
|
20501
21052
|
createSynth,
|
|
20502
21053
|
createVoiceRegistry,
|
|
20503
21054
|
decodeMml,
|
|
20504
21055
|
decomposeToMonophonic,
|
|
20505
|
-
drawGrid,
|
|
20506
|
-
drawHeader,
|
|
20507
|
-
drawKeyboard,
|
|
20508
|
-
drawNoteLyrics,
|
|
20509
|
-
drawNotes,
|
|
20510
|
-
drawSelectedNotes,
|
|
20511
|
-
drawSelectionRect,
|
|
20512
21056
|
encodeMml,
|
|
20513
21057
|
encodeWavPCM16,
|
|
20514
21058
|
exportMIDI,
|
|
@@ -20516,36 +21060,35 @@ export {
|
|
|
20516
21060
|
extractMidiDrumPattern,
|
|
20517
21061
|
extractMidiPlacements,
|
|
20518
21062
|
extractMidiPlacementsByTrack,
|
|
21063
|
+
fifthToStep,
|
|
21064
|
+
fifthToUnits,
|
|
20519
21065
|
formatMmlMeta,
|
|
20520
21066
|
freqFromPitch,
|
|
20521
21067
|
generateRandomPattern,
|
|
20522
|
-
getDrawOffset,
|
|
20523
21068
|
getDrumPatternKeys,
|
|
20524
|
-
getGridCanvas,
|
|
20525
|
-
getGridContext,
|
|
20526
|
-
getGridPosition,
|
|
20527
|
-
getHeaderCanvas,
|
|
20528
21069
|
getMidiBPM,
|
|
20529
|
-
getRenderConfig,
|
|
20530
|
-
getXY,
|
|
20531
21070
|
icon,
|
|
20532
|
-
init,
|
|
20533
21071
|
injectStyles,
|
|
20534
21072
|
isChordHeavyTrack,
|
|
21073
|
+
isNaturalLetter,
|
|
20535
21074
|
isPlausibleMidiTranscription,
|
|
20536
21075
|
isValidHttpUrl,
|
|
21076
|
+
keyCountFor,
|
|
20537
21077
|
koeUrl,
|
|
21078
|
+
midiNote,
|
|
21079
|
+
midiToUnits,
|
|
20538
21080
|
mountChordPlayer,
|
|
20539
21081
|
mountDAW,
|
|
20540
21082
|
mountMmlPlayer,
|
|
21083
|
+
naturalStep,
|
|
20541
21084
|
normalizeDrumPatterns,
|
|
20542
21085
|
normalizeLyrics,
|
|
20543
|
-
onClick,
|
|
20544
21086
|
panToStereo,
|
|
20545
21087
|
parseCustomVocals,
|
|
20546
21088
|
parseLyrics,
|
|
20547
21089
|
parseMML,
|
|
20548
21090
|
parseMmlMeta,
|
|
21091
|
+
pitchV1ToUnits,
|
|
20549
21092
|
playChords,
|
|
20550
21093
|
playMML,
|
|
20551
21094
|
playNote,
|
|
@@ -20553,13 +21096,19 @@ export {
|
|
|
20553
21096
|
playSingingMML,
|
|
20554
21097
|
resolveDrumPattern,
|
|
20555
21098
|
resolveLoopPoint,
|
|
20556
|
-
setBackgroundActive,
|
|
20557
|
-
setDrawOffset,
|
|
20558
21099
|
shiftNotes,
|
|
20559
21100
|
showLoadingOverlay,
|
|
21101
|
+
spellingToUnits,
|
|
20560
21102
|
stripCustomVocals,
|
|
20561
21103
|
stripLyrics,
|
|
20562
21104
|
stripMmlMeta,
|
|
20563
21105
|
transposeNotes,
|
|
21106
|
+
units,
|
|
21107
|
+
unitsPerRow,
|
|
21108
|
+
unitsPerStep,
|
|
21109
|
+
unitsToHz,
|
|
21110
|
+
unitsToMidi,
|
|
21111
|
+
unitsToMidiDetune,
|
|
21112
|
+
unitsToPitchV1,
|
|
20564
21113
|
vocalVolumeToGain
|
|
20565
21114
|
};
|