@onjmin/dtm 1.0.8 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/dist/index.d.mts +267 -103
- package/dist/index.d.ts +267 -103
- package/dist/index.js +1269 -727
- package/dist/index.mjs +1240 -709
- 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,72 @@ var createChannelStrip = (ctx, destination, options = {}) => {
|
|
|
1499
1568
|
};
|
|
1500
1569
|
};
|
|
1501
1570
|
|
|
1571
|
+
// src/tuning.ts
|
|
1572
|
+
var UNITS_PER_OCTAVE = 372;
|
|
1573
|
+
var UNITS_PER_SEMITONE = 372 / 12;
|
|
1574
|
+
var UNITS_PER_EDO31_DEGREE = 372 / 31;
|
|
1575
|
+
var CENTS_PER_UNIT = 1200 / UNITS_PER_OCTAVE;
|
|
1576
|
+
var A4_UNITS = 69 * UNITS_PER_SEMITONE;
|
|
1577
|
+
var A4_HZ = 440;
|
|
1578
|
+
var DEFAULT_EDO = 12;
|
|
1579
|
+
var unitsPerStep = (edo) => UNITS_PER_OCTAVE / edo;
|
|
1580
|
+
var midiToUnits = (midi) => midi * UNITS_PER_SEMITONE;
|
|
1581
|
+
var unitsToMidi = (units) => units / UNITS_PER_SEMITONE;
|
|
1582
|
+
var unitsToHz = (units) => A4_HZ * 2 ** ((units - A4_UNITS) / UNITS_PER_OCTAVE);
|
|
1583
|
+
var unitsToMidiDetune = (units) => {
|
|
1584
|
+
const midi = Math.round(units / UNITS_PER_SEMITONE);
|
|
1585
|
+
return {
|
|
1586
|
+
midi,
|
|
1587
|
+
detuneCents: (units - midi * UNITS_PER_SEMITONE) * CENTS_PER_UNIT
|
|
1588
|
+
};
|
|
1589
|
+
};
|
|
1590
|
+
var NATURAL_STEPS = {
|
|
1591
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
1592
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
1593
|
+
};
|
|
1594
|
+
var chromaticStep = (edo) => edo === 31 ? 2 : 1;
|
|
1595
|
+
var MICRO_STEP = 1;
|
|
1596
|
+
var isNaturalLetter = (ch) => Object.hasOwn(NATURAL_STEPS[12], ch);
|
|
1597
|
+
var naturalStep = (ch, edo) => NATURAL_STEPS[edo][ch] ?? null;
|
|
1598
|
+
var spellingToUnits = (letter, octave, chromatic, micro, edo) => {
|
|
1599
|
+
const step = naturalStep(letter, edo);
|
|
1600
|
+
if (step === null) return null;
|
|
1601
|
+
const perStep = unitsPerStep(edo);
|
|
1602
|
+
const stepsFromC = step + chromatic * chromaticStep(edo) + micro * MICRO_STEP;
|
|
1603
|
+
return (octave + 1) * UNITS_PER_OCTAVE + stepsFromC * perStep;
|
|
1604
|
+
};
|
|
1605
|
+
var FIFTH_STEPS = { 12: 7, 31: 18 };
|
|
1606
|
+
var fifthToStep = (fifthIndex, edo) => {
|
|
1607
|
+
const n = fifthIndex * FIFTH_STEPS[edo];
|
|
1608
|
+
return (n % edo + edo) % edo;
|
|
1609
|
+
};
|
|
1610
|
+
var fifthToUnits = (fifthIndex, edo) => fifthToStep(fifthIndex, edo) * unitsPerStep(edo);
|
|
1611
|
+
var PITCH_ENCODING_VERSION = 2;
|
|
1612
|
+
var pitchV1ToUnits = (pitch) => pitch * UNITS_PER_SEMITONE;
|
|
1613
|
+
var unitsToPitchV1 = (units) => Math.round(units / UNITS_PER_SEMITONE);
|
|
1614
|
+
|
|
1502
1615
|
// src/chords.ts
|
|
1503
1616
|
var C3 = 48;
|
|
1617
|
+
var MEANTONE_STEP_BY_SEMITONE = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
1618
|
+
var chordToneToUnits = (relSemitone, relFifth, rootSemitone, rootFifth, edo) => {
|
|
1619
|
+
const perStep = UNITS_PER_OCTAVE / edo;
|
|
1620
|
+
const rootOct = Math.floor(rootSemitone / 12);
|
|
1621
|
+
const rootWithin = (rootSemitone % 12 + 12) % 12;
|
|
1622
|
+
const rootStep = edo === 31 ? (fifthToStep(rootFifth, 31) + MEANTONE_STEP_BY_SEMITONE[rootWithin] - fifthToStep(rootFifth, 31) + 31) % 31 : rootWithin;
|
|
1623
|
+
const relWithin12 = (relSemitone % 12 + 12) % 12;
|
|
1624
|
+
const relOct = Math.round((relSemitone - relWithin12) / 12);
|
|
1625
|
+
const relStep = (fifthToStep(relFifth, edo) - fifthToStep(rootFifth, edo) + edo) % edo;
|
|
1626
|
+
return (rootOct + relOct) * UNITS_PER_OCTAVE + (rootStep + relStep) * perStep;
|
|
1627
|
+
};
|
|
1504
1628
|
var buildChordPlacements = (options) => {
|
|
1505
|
-
const {
|
|
1629
|
+
const {
|
|
1630
|
+
chordStr,
|
|
1631
|
+
patternType,
|
|
1632
|
+
rootShift,
|
|
1633
|
+
bpm,
|
|
1634
|
+
stepsPerBar,
|
|
1635
|
+
edo = 12
|
|
1636
|
+
} = options;
|
|
1506
1637
|
const placements = [];
|
|
1507
1638
|
if (!chordStr.trim()) return placements;
|
|
1508
1639
|
const offset = rootShift;
|
|
@@ -1531,8 +1662,20 @@ var buildChordPlacements = (options) => {
|
|
|
1531
1662
|
for (const group of Object.values(chordGroups)) {
|
|
1532
1663
|
for (const chord of group) {
|
|
1533
1664
|
let notes;
|
|
1665
|
+
let toUnits;
|
|
1534
1666
|
try {
|
|
1535
|
-
|
|
1667
|
+
const parsed = parseChord(`${chord.key}${chord.chord}`);
|
|
1668
|
+
notes = [...parsed.notes];
|
|
1669
|
+
const fifthOf = new Map(
|
|
1670
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1671
|
+
);
|
|
1672
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1673
|
+
rel,
|
|
1674
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1675
|
+
C3 + offset,
|
|
1676
|
+
parsed.rootFifth,
|
|
1677
|
+
edo === 31 ? 31 : 12
|
|
1678
|
+
);
|
|
1536
1679
|
} catch {
|
|
1537
1680
|
continue;
|
|
1538
1681
|
}
|
|
@@ -1541,7 +1684,7 @@ var buildChordPlacements = (options) => {
|
|
|
1541
1684
|
for (const noteOffset of notes) {
|
|
1542
1685
|
placements.push({
|
|
1543
1686
|
startStep: chord.whenStep,
|
|
1544
|
-
|
|
1687
|
+
pitchUnits: toUnits(noteOffset),
|
|
1545
1688
|
durationSteps: noteLength,
|
|
1546
1689
|
velocity: 100
|
|
1547
1690
|
});
|
|
@@ -1551,7 +1694,7 @@ var buildChordPlacements = (options) => {
|
|
|
1551
1694
|
notes.forEach((noteOffset, i2) => {
|
|
1552
1695
|
placements.push({
|
|
1553
1696
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1554
|
-
|
|
1697
|
+
pitchUnits: toUnits(noteOffset),
|
|
1555
1698
|
durationSteps: noteLength - i2 * arpInterval,
|
|
1556
1699
|
velocity: 100
|
|
1557
1700
|
});
|
|
@@ -1561,7 +1704,7 @@ var buildChordPlacements = (options) => {
|
|
|
1561
1704
|
notes.forEach((noteOffset, i2) => {
|
|
1562
1705
|
placements.push({
|
|
1563
1706
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1564
|
-
|
|
1707
|
+
pitchUnits: toUnits(noteOffset),
|
|
1565
1708
|
durationSteps: Math.max(12, noteLength - i2 * arpInterval),
|
|
1566
1709
|
velocity: 100
|
|
1567
1710
|
});
|
|
@@ -1575,7 +1718,7 @@ var buildChordPlacements = (options) => {
|
|
|
1575
1718
|
for (const noteOffset of notes) {
|
|
1576
1719
|
placements.push({
|
|
1577
1720
|
startStep: syncopatedStep,
|
|
1578
|
-
|
|
1721
|
+
pitchUnits: toUnits(noteOffset),
|
|
1579
1722
|
durationSteps: Math.min(halfBeat, 12),
|
|
1580
1723
|
velocity: 100
|
|
1581
1724
|
});
|
|
@@ -1594,7 +1737,7 @@ var buildChordPlacements = (options) => {
|
|
|
1594
1737
|
for (const noteOffset of notes) {
|
|
1595
1738
|
placements.push({
|
|
1596
1739
|
startStep: noteStart,
|
|
1597
|
-
|
|
1740
|
+
pitchUnits: toUnits(noteOffset),
|
|
1598
1741
|
durationSteps: yatsumeLengthSteps,
|
|
1599
1742
|
velocity: 100
|
|
1600
1743
|
});
|
|
@@ -1606,7 +1749,7 @@ var buildChordPlacements = (options) => {
|
|
|
1606
1749
|
const stepOffset = i2 * Math.floor(stepsPerBar / 4);
|
|
1607
1750
|
placements.push({
|
|
1608
1751
|
startStep: chord.whenStep + stepOffset,
|
|
1609
|
-
|
|
1752
|
+
pitchUnits: toUnits(noteOffset),
|
|
1610
1753
|
durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
|
|
1611
1754
|
velocity: 100
|
|
1612
1755
|
});
|
|
@@ -1618,8 +1761,20 @@ var buildChordPlacements = (options) => {
|
|
|
1618
1761
|
const chordNames = chordStr.split(/[\s,]+/).filter((c) => c);
|
|
1619
1762
|
chordNames.forEach((chordName, barIndex) => {
|
|
1620
1763
|
let notes;
|
|
1764
|
+
let toUnits;
|
|
1621
1765
|
try {
|
|
1622
|
-
|
|
1766
|
+
const parsed = parseChord(chordName);
|
|
1767
|
+
notes = [...parsed.notes];
|
|
1768
|
+
const fifthOf = new Map(
|
|
1769
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1770
|
+
);
|
|
1771
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1772
|
+
rel,
|
|
1773
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1774
|
+
C3 + offset,
|
|
1775
|
+
parsed.rootFifth,
|
|
1776
|
+
edo === 31 ? 31 : 12
|
|
1777
|
+
);
|
|
1623
1778
|
} catch {
|
|
1624
1779
|
return;
|
|
1625
1780
|
}
|
|
@@ -1629,7 +1784,7 @@ var buildChordPlacements = (options) => {
|
|
|
1629
1784
|
const stepOffset = i2 * 3;
|
|
1630
1785
|
placements.push({
|
|
1631
1786
|
startStep: startStep + stepOffset,
|
|
1632
|
-
|
|
1787
|
+
pitchUnits: toUnits(noteOffset),
|
|
1633
1788
|
durationSteps: chordLength - stepOffset,
|
|
1634
1789
|
velocity: 100
|
|
1635
1790
|
});
|
|
@@ -2161,6 +2316,11 @@ var DEFAULT_VELOCITY = 100;
|
|
|
2161
2316
|
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
2162
2317
|
var DEFAULT_STEPS_PER_BAR = 192;
|
|
2163
2318
|
var MML_END_MARKER = "#end;";
|
|
2319
|
+
var PITCH_RANGE_START = 0;
|
|
2320
|
+
var PITCH_RANGE_END = 3937;
|
|
2321
|
+
var unitsPerRow = (edo) => 372 / edo;
|
|
2322
|
+
var keyCountFor = (edo) => Math.floor((PITCH_RANGE_END - PITCH_RANGE_START) / unitsPerRow(edo)) + 1;
|
|
2323
|
+
var KEY_COUNT = keyCountFor(12);
|
|
2164
2324
|
|
|
2165
2325
|
// src/vibrato.ts
|
|
2166
2326
|
var VIBRATO_MIN_SEC = 0.35;
|
|
@@ -2560,7 +2720,8 @@ var FORMANTS = {
|
|
|
2560
2720
|
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
2561
2721
|
N: [250, 1e3]
|
|
2562
2722
|
};
|
|
2563
|
-
var
|
|
2723
|
+
var unitsToFreq = (units) => 440 * 2 ** ((units - 2139) / 372);
|
|
2724
|
+
var unitsToMidiFloat = (units) => units / 31;
|
|
2564
2725
|
var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
2565
2726
|
const active = /* @__PURE__ */ new Set();
|
|
2566
2727
|
const voice = (syllable, e) => {
|
|
@@ -2594,7 +2755,7 @@ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
|
2594
2755
|
}
|
|
2595
2756
|
const osc = ctx.createOscillator();
|
|
2596
2757
|
osc.type = "sawtooth";
|
|
2597
|
-
osc.frequency.value =
|
|
2758
|
+
osc.frequency.value = unitsToFreq(e.pitchUnits);
|
|
2598
2759
|
const makeFormant = (freq, q2, gainScale) => {
|
|
2599
2760
|
const filter = ctx.createBiquadFilter();
|
|
2600
2761
|
filter.type = "bandpass";
|
|
@@ -2846,7 +3007,7 @@ var createLocalBackend = async (options) => {
|
|
|
2846
3007
|
if (!consonantPcm || !vowelPcm) return null;
|
|
2847
3008
|
const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
|
|
2848
3009
|
if (!spliced) return null;
|
|
2849
|
-
const targetHz =
|
|
3010
|
+
const targetHz = unitsToFreq(pitch);
|
|
2850
3011
|
const audio = worldline.renderNote({
|
|
2851
3012
|
pcm: spliced.pcm,
|
|
2852
3013
|
pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
|
|
@@ -2875,7 +3036,7 @@ var createLocalBackend = async (options) => {
|
|
|
2875
3036
|
if (!pcm || pcm.length === 0) return null;
|
|
2876
3037
|
const entry = bank.manifest.phonemes[alias];
|
|
2877
3038
|
const lead = leadInFromEntry(entry);
|
|
2878
|
-
const targetHz =
|
|
3039
|
+
const targetHz = unitsToFreq(pitch);
|
|
2879
3040
|
if (worldline) {
|
|
2880
3041
|
const audio = worldline.renderNote({
|
|
2881
3042
|
pcm,
|
|
@@ -3082,7 +3243,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3082
3243
|
backend.pitchTokens,
|
|
3083
3244
|
syllable,
|
|
3084
3245
|
prevVowel,
|
|
3085
|
-
e.
|
|
3246
|
+
unitsToMidiFloat(e.pitchUnits)
|
|
3086
3247
|
);
|
|
3087
3248
|
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
3088
3249
|
if (!alias) return;
|
|
@@ -3090,7 +3251,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3090
3251
|
const peak = Math.max(1e-4, e.volume);
|
|
3091
3252
|
const pan = e.pan ?? 0;
|
|
3092
3253
|
const durationMs = Math.max(60, e.duration * 1e3);
|
|
3093
|
-
void renderInto(alias, e.
|
|
3254
|
+
void renderInto(alias, e.pitchUnits, durationMs).then((r) => {
|
|
3094
3255
|
if (r)
|
|
3095
3256
|
schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
|
|
3096
3257
|
});
|
|
@@ -3102,7 +3263,10 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3102
3263
|
backend.pitchTokens,
|
|
3103
3264
|
syllable,
|
|
3104
3265
|
prevVowelArg,
|
|
3105
|
-
|
|
3266
|
+
// 多音階バンクのピッチトークン("_G4" 等)は録音の音名なので、
|
|
3267
|
+
// 最寄り選択はMIDIノート番号の尺度で行う。units のまま渡すと常に
|
|
3268
|
+
// 最高音のトークンが選ばれてしまう。
|
|
3269
|
+
unitsToMidiFloat(pitch)
|
|
3106
3270
|
);
|
|
3107
3271
|
if (!alias) return null;
|
|
3108
3272
|
const dMs = Math.max(60, durationMs);
|
|
@@ -3134,13 +3298,14 @@ var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
|
3134
3298
|
var STREAM_POLL_MS = 100;
|
|
3135
3299
|
var OCTAVE_UNISON_PEAK_SCALE = 0.6;
|
|
3136
3300
|
var octaveUnisonOffsets = (mode) => {
|
|
3301
|
+
const OCT = 372;
|
|
3137
3302
|
switch (mode) {
|
|
3138
3303
|
case "down":
|
|
3139
|
-
return [-
|
|
3304
|
+
return [-OCT];
|
|
3140
3305
|
case "up":
|
|
3141
|
-
return [
|
|
3306
|
+
return [OCT];
|
|
3142
3307
|
case "both":
|
|
3143
|
-
return [-
|
|
3308
|
+
return [-OCT, OCT];
|
|
3144
3309
|
default:
|
|
3145
3310
|
return [];
|
|
3146
3311
|
}
|
|
@@ -3350,7 +3515,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3350
3515
|
const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
|
|
3351
3516
|
model(note.syllable, {
|
|
3352
3517
|
trackId: track.id ?? "",
|
|
3353
|
-
pitch,
|
|
3518
|
+
pitchUnits: pitch,
|
|
3354
3519
|
velocity: 100,
|
|
3355
3520
|
volume: effPeak,
|
|
3356
3521
|
when,
|
|
@@ -3416,17 +3581,13 @@ var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
|
3416
3581
|
};
|
|
3417
3582
|
|
|
3418
3583
|
// src/mml-parser.ts
|
|
3419
|
-
var
|
|
3420
|
-
c: 0,
|
|
3421
|
-
d:
|
|
3422
|
-
e: 4,
|
|
3423
|
-
f: 5,
|
|
3424
|
-
g: 7,
|
|
3425
|
-
a: 9,
|
|
3426
|
-
b: 11
|
|
3584
|
+
var NATURAL_STEPS2 = {
|
|
3585
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
3586
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
3427
3587
|
};
|
|
3588
|
+
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
3428
3589
|
var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
3429
|
-
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode)=([\w-]+)/gi;
|
|
3590
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
|
|
3430
3591
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
3431
3592
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
3432
3593
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -3476,6 +3637,9 @@ var parseMmlMeta = (mml) => {
|
|
|
3476
3637
|
if (m[2] === "simple" || m[2] === "advanced") {
|
|
3477
3638
|
meta.mode = m[2];
|
|
3478
3639
|
}
|
|
3640
|
+
} else if (key === "edo") {
|
|
3641
|
+
const e = Number.parseInt(m[2], 10);
|
|
3642
|
+
if (e === 12 || e === 31) meta.edo = e;
|
|
3479
3643
|
}
|
|
3480
3644
|
}
|
|
3481
3645
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -3578,6 +3742,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
3578
3742
|
if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
|
|
3579
3743
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
3580
3744
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
3745
|
+
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
3581
3746
|
if (meta.trackInstruments) {
|
|
3582
3747
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
3583
3748
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -3652,6 +3817,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
3652
3817
|
const endMarkerBase = MML_END_MARKER.replace(/;+$/, "");
|
|
3653
3818
|
const endRegex = new RegExp(`(?<![cdafgCDAFG])${endMarkerBase}\\b;?`, "gi");
|
|
3654
3819
|
const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
|
|
3820
|
+
const edo = meta.edo === 31 ? 31 : 12;
|
|
3821
|
+
const naturals = NATURAL_STEPS2[edo];
|
|
3822
|
+
const unitsPerStep2 = 372 / edo;
|
|
3823
|
+
const chromaticStep2 = edo === 31 ? 2 : 1;
|
|
3655
3824
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
3656
3825
|
let trackIndex = 0;
|
|
3657
3826
|
let sourceTrackIndex = 0;
|
|
@@ -3699,6 +3868,19 @@ var parseMML = (mml, options = {}) => {
|
|
|
3699
3868
|
type
|
|
3700
3869
|
});
|
|
3701
3870
|
};
|
|
3871
|
+
const readAccidentals = () => {
|
|
3872
|
+
let delta = 0;
|
|
3873
|
+
while (j < body.length) {
|
|
3874
|
+
const a = body[j];
|
|
3875
|
+
if (a === "#") delta += chromaticStep2;
|
|
3876
|
+
else if (a === "-") delta -= chromaticStep2;
|
|
3877
|
+
else if (a === "+") delta += edo === 31 ? 1 : chromaticStep2;
|
|
3878
|
+
else if (a === "_") delta -= edo === 31 ? 1 : chromaticStep2;
|
|
3879
|
+
else break;
|
|
3880
|
+
j++;
|
|
3881
|
+
}
|
|
3882
|
+
return delta;
|
|
3883
|
+
};
|
|
3702
3884
|
const parseLength = () => {
|
|
3703
3885
|
let numStr = "";
|
|
3704
3886
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -3770,17 +3952,11 @@ var parseMML = (mml, options = {}) => {
|
|
|
3770
3952
|
const savedOctave = octave;
|
|
3771
3953
|
while (j < body.length && body[j] !== "]") {
|
|
3772
3954
|
const c = body[j];
|
|
3773
|
-
if (
|
|
3774
|
-
let
|
|
3955
|
+
if (isNatural(c)) {
|
|
3956
|
+
let pitchSteps = naturals[c];
|
|
3775
3957
|
j++;
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
j++;
|
|
3779
|
-
} else if (j < body.length && body[j] === "-") {
|
|
3780
|
-
pitch--;
|
|
3781
|
-
j++;
|
|
3782
|
-
}
|
|
3783
|
-
chordNotes.push((octave + 1) * 12 + pitch);
|
|
3958
|
+
pitchSteps += readAccidentals();
|
|
3959
|
+
chordNotes.push((octave + 1) * 372 + pitchSteps * unitsPerStep2);
|
|
3784
3960
|
} else if (c === ">") {
|
|
3785
3961
|
octave = Math.min(8, octave + 1);
|
|
3786
3962
|
j++;
|
|
@@ -3806,7 +3982,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
3806
3982
|
placements.push({
|
|
3807
3983
|
trackIndex,
|
|
3808
3984
|
startStep: currentStep,
|
|
3809
|
-
|
|
3985
|
+
pitchUnits: p,
|
|
3810
3986
|
durationSteps: Math.max(1, steps),
|
|
3811
3987
|
velocity
|
|
3812
3988
|
});
|
|
@@ -3814,23 +3990,17 @@ var parseMML = (mml, options = {}) => {
|
|
|
3814
3990
|
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
3815
3991
|
currentStep += steps;
|
|
3816
3992
|
octave = savedOctave;
|
|
3817
|
-
} else if (
|
|
3818
|
-
let
|
|
3993
|
+
} else if (isNatural(ch)) {
|
|
3994
|
+
let pitchSteps = naturals[ch];
|
|
3819
3995
|
j++;
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
j++;
|
|
3823
|
-
} else if (j < body.length && body[j] === "-") {
|
|
3824
|
-
pitch--;
|
|
3825
|
-
j++;
|
|
3826
|
-
}
|
|
3827
|
-
const midiPitch = (octave + 1) * 12 + pitch;
|
|
3996
|
+
pitchSteps += readAccidentals();
|
|
3997
|
+
const midiPitch = (octave + 1) * 372 + pitchSteps * unitsPerStep2;
|
|
3828
3998
|
const steps = parseLength();
|
|
3829
3999
|
recordContributor();
|
|
3830
4000
|
placements.push({
|
|
3831
4001
|
trackIndex,
|
|
3832
4002
|
startStep: currentStep,
|
|
3833
|
-
|
|
4003
|
+
pitchUnits: midiPitch,
|
|
3834
4004
|
durationSteps: Math.max(1, steps),
|
|
3835
4005
|
velocity
|
|
3836
4006
|
});
|
|
@@ -3962,7 +4132,7 @@ var createSequencer = (options) => {
|
|
|
3962
4132
|
maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
|
|
3963
4133
|
timeline.push({
|
|
3964
4134
|
trackId: track.id,
|
|
3965
|
-
pitch: note.
|
|
4135
|
+
pitch: note.pitchUnits,
|
|
3966
4136
|
volume: track.volume / 100,
|
|
3967
4137
|
velocity: note.velocity ?? DEFAULT_PLAYBACK_VELOCITY,
|
|
3968
4138
|
when,
|
|
@@ -4025,7 +4195,7 @@ var createSequencer = (options) => {
|
|
|
4025
4195
|
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
4026
4196
|
options.onPlayNote({
|
|
4027
4197
|
trackId: ev.trackId,
|
|
4028
|
-
|
|
4198
|
+
pitchUnits: ev.pitch,
|
|
4029
4199
|
velocity: ev.velocity,
|
|
4030
4200
|
volume: currentVolume * velocityVolume,
|
|
4031
4201
|
when: Math.max(0, _when),
|
|
@@ -6345,7 +6515,7 @@ var SONG_DRUM_PATTERNS = {
|
|
|
6345
6515
|
};
|
|
6346
6516
|
|
|
6347
6517
|
// src/synth.ts
|
|
6348
|
-
var freqFromPitch = (
|
|
6518
|
+
var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
|
|
6349
6519
|
var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
6350
6520
|
const wave = tone.wave ?? "square";
|
|
6351
6521
|
const attack = tone.attack ?? 0;
|
|
@@ -6361,7 +6531,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
|
6361
6531
|
const osc = ctx.createOscillator();
|
|
6362
6532
|
const gain = ctx.createGain();
|
|
6363
6533
|
osc.type = wave;
|
|
6364
|
-
osc.frequency.value = freqFromPitch(e.
|
|
6534
|
+
osc.frequency.value = freqFromPitch(e.pitchUnits);
|
|
6365
6535
|
const t0 = ctx.currentTime + e.when;
|
|
6366
6536
|
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
|
|
6367
6537
|
if (tone.decay) {
|
|
@@ -6466,7 +6636,7 @@ var playPlacements = (placements, options) => {
|
|
|
6466
6636
|
id: id++,
|
|
6467
6637
|
startStep: p.startStep,
|
|
6468
6638
|
durationSteps: p.durationSteps,
|
|
6469
|
-
|
|
6639
|
+
pitchUnits: p.pitchUnits,
|
|
6470
6640
|
velocity: p.velocity
|
|
6471
6641
|
}));
|
|
6472
6642
|
return {
|
|
@@ -6637,7 +6807,7 @@ var playNote = (options) => {
|
|
|
6637
6807
|
const dur = options.duration ?? 1;
|
|
6638
6808
|
synth.playNote({
|
|
6639
6809
|
trackId: "melody",
|
|
6640
|
-
|
|
6810
|
+
pitchUnits: options.pitchUnits,
|
|
6641
6811
|
velocity: 100,
|
|
6642
6812
|
volume: vol / 100,
|
|
6643
6813
|
when: 0,
|
|
@@ -6658,7 +6828,7 @@ var playChords = (chordStr, options = {}) => {
|
|
|
6658
6828
|
// 伴奏トラック
|
|
6659
6829
|
startStep: p.startStep,
|
|
6660
6830
|
durationSteps: p.durationSteps,
|
|
6661
|
-
|
|
6831
|
+
pitchUnits: p.pitchUnits,
|
|
6662
6832
|
velocity: p.velocity
|
|
6663
6833
|
}));
|
|
6664
6834
|
return playPlacements(placements, {
|
|
@@ -8815,7 +8985,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8815
8985
|
0
|
|
8816
8986
|
);
|
|
8817
8987
|
const timedNotes = placements.map((p) => ({
|
|
8818
|
-
pitch: p.
|
|
8988
|
+
pitch: unitsToPitchV1(p.pitchUnits),
|
|
8819
8989
|
when: p.startStep * secondsPerStep,
|
|
8820
8990
|
duration: p.durationSteps * secondsPerStep
|
|
8821
8991
|
}));
|
|
@@ -8848,7 +9018,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8848
9018
|
id: id++,
|
|
8849
9019
|
startStep: p.startStep,
|
|
8850
9020
|
durationSteps: p.durationSteps,
|
|
8851
|
-
|
|
9021
|
+
pitchUnits: p.pitchUnits,
|
|
8852
9022
|
velocity: DEFAULT_VELOCITY
|
|
8853
9023
|
}));
|
|
8854
9024
|
return {
|
|
@@ -9673,7 +9843,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9673
9843
|
(a, b) => a.startStep - b.startStep
|
|
9674
9844
|
);
|
|
9675
9845
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
9676
|
-
const semis = (lt.octave ?? 0) *
|
|
9846
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
9677
9847
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
9678
9848
|
const notes = [];
|
|
9679
9849
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -9681,7 +9851,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9681
9851
|
if (n.startStep < fromStep) continue;
|
|
9682
9852
|
notes.push({
|
|
9683
9853
|
syllable: lt.syllables[i2],
|
|
9684
|
-
pitch: n.
|
|
9854
|
+
pitch: n.pitchUnits + semis,
|
|
9685
9855
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
9686
9856
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
9687
9857
|
});
|
|
@@ -9956,6 +10126,7 @@ var SoundFont = class _SoundFont {
|
|
|
9956
10126
|
pitch = 60,
|
|
9957
10127
|
volume = 1,
|
|
9958
10128
|
velocity,
|
|
10129
|
+
detuneCents = 0,
|
|
9959
10130
|
when = 0,
|
|
9960
10131
|
duration = 1
|
|
9961
10132
|
} = {}) {
|
|
@@ -9975,7 +10146,7 @@ var SoundFont = class _SoundFont {
|
|
|
9975
10146
|
Object.assign(src, _param.src);
|
|
9976
10147
|
const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
|
|
9977
10148
|
const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
|
|
9978
|
-
src.detune.setValueAtTime(humanizeCents, 0);
|
|
10149
|
+
src.detune.setValueAtTime(humanizeCents + detuneCents, 0);
|
|
9979
10150
|
const effectiveVolume = volume * humanizeGainMul;
|
|
9980
10151
|
const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
|
|
9981
10152
|
const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
|
|
@@ -11114,7 +11285,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11114
11285
|
scheduleMetronomeBeats(ctx, cutGain);
|
|
11115
11286
|
};
|
|
11116
11287
|
const wafPlayDynamic = ({
|
|
11117
|
-
|
|
11288
|
+
pitchUnits,
|
|
11118
11289
|
velocity,
|
|
11119
11290
|
volume,
|
|
11120
11291
|
when,
|
|
@@ -11124,10 +11295,12 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11124
11295
|
}) => {
|
|
11125
11296
|
const waf = activeGmName ? _wafCache.get(activeGmName) : null;
|
|
11126
11297
|
if (waf) {
|
|
11298
|
+
const { midi, detuneCents } = unitsToMidiDetune(pitchUnits);
|
|
11127
11299
|
waf.play({
|
|
11128
11300
|
ctx,
|
|
11129
11301
|
destination,
|
|
11130
|
-
pitch,
|
|
11302
|
+
pitch: midi,
|
|
11303
|
+
detuneCents,
|
|
11131
11304
|
volume: volume * 0.85,
|
|
11132
11305
|
velocity,
|
|
11133
11306
|
when,
|
|
@@ -11137,7 +11310,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11137
11310
|
const synth = createSynth(ctx, destination);
|
|
11138
11311
|
synth.playNote({
|
|
11139
11312
|
trackId: "chord",
|
|
11140
|
-
|
|
11313
|
+
pitchUnits,
|
|
11141
11314
|
velocity: 100,
|
|
11142
11315
|
volume,
|
|
11143
11316
|
when,
|
|
@@ -11166,7 +11339,8 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11166
11339
|
trackIndex: 3,
|
|
11167
11340
|
startStep: whenStep,
|
|
11168
11341
|
durationSteps,
|
|
11169
|
-
|
|
11342
|
+
// chord-parser の構成音は12平均律の半音。内部表現の units へ写す。
|
|
11343
|
+
pitchUnits: pitchV1ToUnits(C32 + noteOffset),
|
|
11170
11344
|
velocity: 100
|
|
11171
11345
|
});
|
|
11172
11346
|
}
|
|
@@ -11179,7 +11353,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11179
11353
|
synth: false,
|
|
11180
11354
|
onPlayNote: (e) => {
|
|
11181
11355
|
wafPlayDynamic({
|
|
11182
|
-
|
|
11356
|
+
pitchUnits: e.pitchUnits,
|
|
11183
11357
|
velocity: e.velocity,
|
|
11184
11358
|
volume: e.volume,
|
|
11185
11359
|
when: e.when,
|
|
@@ -11621,6 +11795,14 @@ var buildUI = (target, options) => {
|
|
|
11621
11795
|
<summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
|
|
11622
11796
|
<div class="dtm-panel-body">
|
|
11623
11797
|
<div data-dtm="preset-select-slot"></div>
|
|
11798
|
+
<div class="dtm-row">
|
|
11799
|
+
<span class="dtm-label">\u97F3\u5F8B</span>
|
|
11800
|
+
<select class="dtm-select dtm-grow" data-dtm="edo-select">
|
|
11801
|
+
<option value="12">12\u5E73\u5747\u5F8B\uFF08\u901A\u5E38\uFF09</option>
|
|
11802
|
+
<option value="31">31\u5E73\u5747\u5F8B\uFF08\u5FAE\u5206\u97F3\uFF09</option>
|
|
11803
|
+
</select>
|
|
11804
|
+
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
11805
|
+
</div>
|
|
11624
11806
|
<div class="dtm-row">
|
|
11625
11807
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
11626
11808
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -11953,6 +12135,8 @@ var buildUI = (target, options) => {
|
|
|
11953
12135
|
copyMiniBtn: sel("copy-mini"),
|
|
11954
12136
|
overlay: sel("overlay"),
|
|
11955
12137
|
mmlInfoBtn: sel("mml-info"),
|
|
12138
|
+
edoSelect: sel("edo-select"),
|
|
12139
|
+
edoInfoBtn: sel("edo-info"),
|
|
11956
12140
|
modalOverlay: sel("modal-overlay"),
|
|
11957
12141
|
modalTitle: sel("modal-title"),
|
|
11958
12142
|
modalBody: sel("modal-body"),
|
|
@@ -12073,17 +12257,21 @@ var SCALES = [
|
|
|
12073
12257
|
[0, 2, 4, 7, 9]
|
|
12074
12258
|
// Pentatonic Major
|
|
12075
12259
|
];
|
|
12260
|
+
var MEANTONE_STEP_BY_SEMITONE2 = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
12076
12261
|
var generateRandomPattern = (core, options) => {
|
|
12077
|
-
const { stepsPerBar, startStep, pitchRangeStart } = options;
|
|
12262
|
+
const { stepsPerBar, startStep, pitchRangeStart, edo = 12 } = options;
|
|
12078
12263
|
const numBars = 8;
|
|
12079
12264
|
const noteLength = 24;
|
|
12080
|
-
const
|
|
12265
|
+
const semitoneToStep = (semi) => edo === 31 ? MEANTONE_STEP_BY_SEMITONE2[semi] : semi;
|
|
12266
|
+
const upr = UNITS_PER_OCTAVE / edo;
|
|
12267
|
+
const basePitch = pitchRangeStart + 60 * UNITS_PER_SEMITONE;
|
|
12081
12268
|
const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
|
|
12082
12269
|
const rootOffset = Math.floor(Math.random() * 12);
|
|
12083
12270
|
const availablePitches = [];
|
|
12084
12271
|
for (let i2 = 0; i2 < 12; i2++) {
|
|
12085
12272
|
const noteInOctave = (i2 - rootOffset + 12) % 12;
|
|
12086
|
-
if (scale.includes(noteInOctave))
|
|
12273
|
+
if (scale.includes(noteInOctave))
|
|
12274
|
+
availablePitches.push(basePitch + semitoneToStep(i2) * upr);
|
|
12087
12275
|
}
|
|
12088
12276
|
core.beginBatch();
|
|
12089
12277
|
for (let bar = 0; bar < numBars; bar++) {
|
|
@@ -12118,14 +12306,17 @@ var applyHarmonicFilter = (targetCore, chordCore, options) => {
|
|
|
12118
12306
|
const isNewBar = halfBar % 2 === 0;
|
|
12119
12307
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12120
12308
|
if (chordHere.length > 0) {
|
|
12121
|
-
currentClasses = new Set(
|
|
12309
|
+
currentClasses = new Set(
|
|
12310
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12311
|
+
);
|
|
12122
12312
|
} else if (isNewBar) {
|
|
12123
12313
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12124
12314
|
}
|
|
12125
12315
|
if (currentClasses.size === 0) continue;
|
|
12126
12316
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12127
12317
|
for (const n of activeHere) {
|
|
12128
|
-
if (!currentClasses.has(n.
|
|
12318
|
+
if (!currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE))
|
|
12319
|
+
targetCore.deleteNoteById(n.id);
|
|
12129
12320
|
}
|
|
12130
12321
|
}
|
|
12131
12322
|
targetCore.endBatch();
|
|
@@ -12147,13 +12338,17 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12147
12338
|
const isNewBar = halfBar % 2 === 0;
|
|
12148
12339
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12149
12340
|
if (chordHere.length > 0) {
|
|
12150
|
-
currentClasses = new Set(
|
|
12341
|
+
currentClasses = new Set(
|
|
12342
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12343
|
+
);
|
|
12151
12344
|
} else if (isNewBar) {
|
|
12152
12345
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12153
12346
|
}
|
|
12154
12347
|
if (currentClasses.size === 0) continue;
|
|
12155
12348
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12156
|
-
const filtered = activeHere.filter(
|
|
12349
|
+
const filtered = activeHere.filter(
|
|
12350
|
+
(n) => currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12351
|
+
);
|
|
12157
12352
|
const filteredIds = new Set(filtered.map((n) => n.id));
|
|
12158
12353
|
for (const n of activeHere) {
|
|
12159
12354
|
if (!filteredIds.has(n.id)) targetCore.deleteNoteById(n.id);
|
|
@@ -12165,7 +12360,7 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12165
12360
|
}
|
|
12166
12361
|
for (const notesAtTime of timeMap.values()) {
|
|
12167
12362
|
if (notesAtTime.length > 1) {
|
|
12168
|
-
notesAtTime.sort((a, b) => b.
|
|
12363
|
+
notesAtTime.sort((a, b) => b.pitchUnits - a.pitchUnits);
|
|
12169
12364
|
const [, ...others] = notesAtTime;
|
|
12170
12365
|
for (const on of others) targetCore.deleteNoteById(on.id);
|
|
12171
12366
|
}
|
|
@@ -12181,18 +12376,21 @@ var shiftNotes = (cores, shiftSteps) => {
|
|
|
12181
12376
|
for (const note of notes) {
|
|
12182
12377
|
const newStart = note.startStep + shiftSteps;
|
|
12183
12378
|
if (newStart < 0) core.deleteNoteById(note.id);
|
|
12184
|
-
else core.moveNote(note.id, newStart, note.
|
|
12379
|
+
else core.moveNote(note.id, newStart, note.pitchUnits);
|
|
12185
12380
|
}
|
|
12186
12381
|
core.saveHistory();
|
|
12187
12382
|
}
|
|
12188
12383
|
};
|
|
12189
|
-
var transposeNotes = (cores,
|
|
12190
|
-
if (
|
|
12384
|
+
var transposeNotes = (cores, steps) => {
|
|
12385
|
+
if (steps === 0) return;
|
|
12191
12386
|
for (const core of cores) {
|
|
12192
12387
|
const notes = [...core.getNotes()];
|
|
12193
12388
|
for (const note of notes) {
|
|
12194
|
-
const newPitch = Math.max(
|
|
12195
|
-
|
|
12389
|
+
const newPitch = Math.max(
|
|
12390
|
+
PITCH_RANGE_START,
|
|
12391
|
+
Math.min(PITCH_RANGE_END, note.pitchUnits + steps)
|
|
12392
|
+
);
|
|
12393
|
+
if (newPitch !== note.pitchUnits) {
|
|
12196
12394
|
core.moveNote(note.id, note.startStep, newPitch);
|
|
12197
12395
|
}
|
|
12198
12396
|
}
|
|
@@ -12517,11 +12715,31 @@ var exportMIDI = (options) => {
|
|
|
12517
12715
|
const div = 480;
|
|
12518
12716
|
const tickPerStep = div / STEPS_PER_BEAT3;
|
|
12519
12717
|
const midiTracks = [];
|
|
12520
|
-
|
|
12718
|
+
const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
|
|
12719
|
+
const BEND_RANGE_SEMITONES = 2;
|
|
12720
|
+
const bendKey = (cents) => Math.round(cents * 100);
|
|
12721
|
+
const bendUse = /* @__PURE__ */ new Map();
|
|
12722
|
+
for (const track of tracks) {
|
|
12723
|
+
for (const n of track.notes) {
|
|
12724
|
+
const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12725
|
+
const k = bendKey(detuneCents);
|
|
12726
|
+
bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
|
|
12727
|
+
}
|
|
12728
|
+
}
|
|
12729
|
+
const bendChannel = /* @__PURE__ */ new Map();
|
|
12730
|
+
const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
|
|
12731
|
+
for (const [k] of ordered) {
|
|
12732
|
+
if (bendChannel.size >= NOTE_CHANNELS.length) break;
|
|
12733
|
+
bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
|
|
12734
|
+
}
|
|
12735
|
+
tracks.forEach((track) => {
|
|
12521
12736
|
if (track.notes.length === 0) return;
|
|
12522
|
-
const channel = ch < 9 ? ch : ch + 1 & 15;
|
|
12523
12737
|
const events = [];
|
|
12524
12738
|
for (const n of track.notes) {
|
|
12739
|
+
const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12740
|
+
const k = bendKey(detuneCents);
|
|
12741
|
+
const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
|
|
12742
|
+
const note = Math.max(0, Math.min(127, midi));
|
|
12525
12743
|
const startTick = Math.round(n.startStep * tickPerStep);
|
|
12526
12744
|
const endTick = Math.round(
|
|
12527
12745
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
@@ -12529,12 +12747,25 @@ var exportMIDI = (options) => {
|
|
|
12529
12747
|
const vel = Math.round(
|
|
12530
12748
|
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
12531
12749
|
);
|
|
12532
|
-
events.push({ t: startTick, m: [144 | channel,
|
|
12533
|
-
events.push({ t: endTick, m: [144 | channel,
|
|
12750
|
+
events.push({ t: startTick, m: [144 | channel, note, vel] });
|
|
12751
|
+
events.push({ t: endTick, m: [144 | channel, note, 0] });
|
|
12534
12752
|
}
|
|
12535
12753
|
events.sort((a, b) => a.t - b.t);
|
|
12536
12754
|
midiTracks.push(events);
|
|
12537
12755
|
});
|
|
12756
|
+
const bendSetup = [];
|
|
12757
|
+
for (const [k, channel] of bendChannel) {
|
|
12758
|
+
const cents = k / 100;
|
|
12759
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
|
|
12760
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
|
|
12761
|
+
bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
|
|
12762
|
+
bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
|
|
12763
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
|
|
12764
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
|
|
12765
|
+
const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
|
|
12766
|
+
const v = Math.max(0, Math.min(16383, raw));
|
|
12767
|
+
bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
|
|
12768
|
+
}
|
|
12538
12769
|
const maxStep = Math.max(
|
|
12539
12770
|
...tracks.filter((t) => t.notes.length > 0).map(
|
|
12540
12771
|
(t) => Math.max(...t.notes.map((n) => n.startStep + n.durationSteps))
|
|
@@ -12571,6 +12802,7 @@ var exportMIDI = (options) => {
|
|
|
12571
12802
|
headerChunks(arr, midiTracks.length + 1, div);
|
|
12572
12803
|
trackChunks(arr, (a) => {
|
|
12573
12804
|
a.push(0, 255, 81, 3, ...to3byte(Math.round(6e7 / bpm)));
|
|
12805
|
+
for (const ev of bendSetup) a.push(0, ...ev.m);
|
|
12574
12806
|
});
|
|
12575
12807
|
for (const events of midiTracks) {
|
|
12576
12808
|
trackChunks(arr, (a) => {
|
|
@@ -12817,403 +13049,8 @@ var LinkedList = class {
|
|
|
12817
13049
|
}
|
|
12818
13050
|
};
|
|
12819
13051
|
|
|
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
13052
|
// src/mml-core.ts
|
|
13216
|
-
var
|
|
13053
|
+
var PITCH_MAP = [
|
|
13217
13054
|
"c",
|
|
13218
13055
|
"c+",
|
|
13219
13056
|
"d",
|
|
@@ -13227,6 +13064,39 @@ var PITCH_MAP2 = [
|
|
|
13227
13064
|
"a+",
|
|
13228
13065
|
"b"
|
|
13229
13066
|
];
|
|
13067
|
+
var EDO31_NAMES = [
|
|
13068
|
+
"c",
|
|
13069
|
+
"c+",
|
|
13070
|
+
"c#",
|
|
13071
|
+
"d-",
|
|
13072
|
+
"d_",
|
|
13073
|
+
"d",
|
|
13074
|
+
"d+",
|
|
13075
|
+
"d#",
|
|
13076
|
+
"e-",
|
|
13077
|
+
"e_",
|
|
13078
|
+
"e",
|
|
13079
|
+
"f-",
|
|
13080
|
+
"e#",
|
|
13081
|
+
"f",
|
|
13082
|
+
"f+",
|
|
13083
|
+
"f#",
|
|
13084
|
+
"g-",
|
|
13085
|
+
"g_",
|
|
13086
|
+
"g",
|
|
13087
|
+
"g+",
|
|
13088
|
+
"g#",
|
|
13089
|
+
"a-",
|
|
13090
|
+
"a_",
|
|
13091
|
+
"a",
|
|
13092
|
+
"a+",
|
|
13093
|
+
"a#",
|
|
13094
|
+
"b-",
|
|
13095
|
+
"b_",
|
|
13096
|
+
"b",
|
|
13097
|
+
"b+",
|
|
13098
|
+
"b#"
|
|
13099
|
+
];
|
|
13230
13100
|
var MMLCore = class _MMLCore {
|
|
13231
13101
|
notes = [];
|
|
13232
13102
|
nextNoteId = 0;
|
|
@@ -13240,9 +13110,12 @@ var MMLCore = class _MMLCore {
|
|
|
13240
13110
|
lastUndoTime = 0;
|
|
13241
13111
|
static UNDO_DEBOUNCE_MS = 100;
|
|
13242
13112
|
toolMode = "pen";
|
|
13243
|
-
|
|
13113
|
+
/** グリッド寸法の供給元(描画器ではなく呼び出し側が持つ設定を読む) */
|
|
13114
|
+
getConfig;
|
|
13115
|
+
constructor(handlers, volume = 80, getConfig) {
|
|
13244
13116
|
this.handlers = handlers;
|
|
13245
13117
|
this.volume = volume;
|
|
13118
|
+
this.getConfig = getConfig;
|
|
13246
13119
|
this.lastHistorySnapshot = JSON.stringify(this.notes);
|
|
13247
13120
|
this.history.add([]);
|
|
13248
13121
|
this.generateAndNotify();
|
|
@@ -13329,14 +13202,14 @@ var MMLCore = class _MMLCore {
|
|
|
13329
13202
|
*/
|
|
13330
13203
|
addNote(step, pitch, options) {
|
|
13331
13204
|
const existingIndex = this.notes.findIndex(
|
|
13332
|
-
(n) => n.startStep === step && n.
|
|
13205
|
+
(n) => n.startStep === step && n.pitchUnits === pitch
|
|
13333
13206
|
);
|
|
13334
13207
|
if (existingIndex === -1) {
|
|
13335
13208
|
const newNote = {
|
|
13336
13209
|
id: this.nextNoteId++,
|
|
13337
13210
|
startStep: step,
|
|
13338
13211
|
durationSteps: options.noteLengthSteps,
|
|
13339
|
-
pitch,
|
|
13212
|
+
pitchUnits: pitch,
|
|
13340
13213
|
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
13341
13214
|
};
|
|
13342
13215
|
this.notes.push(newNote);
|
|
@@ -13364,9 +13237,11 @@ var MMLCore = class _MMLCore {
|
|
|
13364
13237
|
moveNote(noteId, startStep, pitch) {
|
|
13365
13238
|
const note = this.notes.find((target) => target.id === noteId);
|
|
13366
13239
|
if (!note) return;
|
|
13367
|
-
const totalSteps = this.getMaxStep() +
|
|
13368
|
-
const
|
|
13369
|
-
const
|
|
13240
|
+
const totalSteps = this.getMaxStep() + this.getConfig().stepsPerBar;
|
|
13241
|
+
const cfg = this.getConfig();
|
|
13242
|
+
const upr = cfg.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
13243
|
+
const pitchRangeStart = cfg.pitchRangeStart;
|
|
13244
|
+
const pitchRangeEnd = pitchRangeStart + (cfg.keyCount - 1) * upr;
|
|
13370
13245
|
const clampedPitch = Math.min(
|
|
13371
13246
|
Math.max(pitch, pitchRangeStart),
|
|
13372
13247
|
pitchRangeEnd
|
|
@@ -13376,7 +13251,7 @@ var MMLCore = class _MMLCore {
|
|
|
13376
13251
|
totalSteps - note.durationSteps
|
|
13377
13252
|
);
|
|
13378
13253
|
note.startStep = clampedStart;
|
|
13379
|
-
note.
|
|
13254
|
+
note.pitchUnits = clampedPitch;
|
|
13380
13255
|
this.notes.sort((a, b) => a.startStep - b.startStep);
|
|
13381
13256
|
this.generateAndNotify();
|
|
13382
13257
|
}
|
|
@@ -13421,7 +13296,7 @@ var MMLCore = class _MMLCore {
|
|
|
13421
13296
|
* ただし、残りステップ(limit)は絶対に超えない。
|
|
13422
13297
|
*/
|
|
13423
13298
|
stepsToMMLDuration(steps, limit) {
|
|
13424
|
-
const config =
|
|
13299
|
+
const config = this.getConfig();
|
|
13425
13300
|
const total = config.stepsPerBar;
|
|
13426
13301
|
const candidates = [
|
|
13427
13302
|
{ dur: "1.", s: total * 1.5 },
|
|
@@ -13457,7 +13332,7 @@ var MMLCore = class _MMLCore {
|
|
|
13457
13332
|
* ギャップに収まる最大の音符を探す(減算アルゴリズム用)
|
|
13458
13333
|
*/
|
|
13459
13334
|
findBestFitDuration(gap) {
|
|
13460
|
-
const config =
|
|
13335
|
+
const config = this.getConfig();
|
|
13461
13336
|
const durations = [1, 2, 4, 8, 12, 16, 24, 32, 48, 64];
|
|
13462
13337
|
for (const d of durations) {
|
|
13463
13338
|
const stepLen = config.stepsPerBar / d;
|
|
@@ -13467,12 +13342,20 @@ var MMLCore = class _MMLCore {
|
|
|
13467
13342
|
}
|
|
13468
13343
|
return { dur: 64, steps: config.stepsPerBar / 64 };
|
|
13469
13344
|
}
|
|
13345
|
+
/** ピッチ(units) → その音律での「オクターブ番号」と「音名」。 */
|
|
13346
|
+
spell(pitchUnits) {
|
|
13347
|
+
const edo = this.getConfig().edo ?? 12;
|
|
13348
|
+
const names = edo === 31 ? EDO31_NAMES : PITCH_MAP;
|
|
13349
|
+
const octave = Math.floor(pitchUnits / 372) - 1;
|
|
13350
|
+
const within = (pitchUnits % 372 + 372) % 372;
|
|
13351
|
+
const step = Math.round(within / (372 / edo));
|
|
13352
|
+
return { octave, name: names[step % edo] };
|
|
13353
|
+
}
|
|
13470
13354
|
/**
|
|
13471
13355
|
* ピッチからオクターブ最適化のある音名を取得
|
|
13472
13356
|
*/
|
|
13473
13357
|
getNoteWithOctave(pitch, lastOctave) {
|
|
13474
|
-
const octave =
|
|
13475
|
-
const name = PITCH_MAP2[pitch % 12];
|
|
13358
|
+
const { octave, name } = this.spell(pitch);
|
|
13476
13359
|
if (lastOctave === -1 || Math.abs(octave - lastOctave) >= 2) {
|
|
13477
13360
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
13478
13361
|
}
|
|
@@ -13495,7 +13378,7 @@ var MMLCore = class _MMLCore {
|
|
|
13495
13378
|
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
13496
13379
|
*/
|
|
13497
13380
|
generateMML = (volumeOverride) => {
|
|
13498
|
-
const config =
|
|
13381
|
+
const config = this.getConfig();
|
|
13499
13382
|
const vol = volumeOverride ?? this.volume;
|
|
13500
13383
|
const header = `t${this.tempo} v${vol}`;
|
|
13501
13384
|
const segments = [];
|
|
@@ -13536,14 +13419,13 @@ var MMLCore = class _MMLCore {
|
|
|
13536
13419
|
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
13537
13420
|
if (notes.length > 1) {
|
|
13538
13421
|
const noteStrs = notes.map((n) => {
|
|
13539
|
-
const oct =
|
|
13540
|
-
const name = PITCH_MAP2[n.pitch % 12];
|
|
13422
|
+
const { octave: oct, name } = this.spell(n.pitchUnits);
|
|
13541
13423
|
return `o${oct}${name}`;
|
|
13542
13424
|
});
|
|
13543
13425
|
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
13544
13426
|
} else {
|
|
13545
13427
|
const { text, currentOctave } = this.getNoteWithOctave(
|
|
13546
|
-
notes[0].
|
|
13428
|
+
notes[0].pitchUnits,
|
|
13547
13429
|
lastOctave
|
|
13548
13430
|
);
|
|
13549
13431
|
segments.push(`${text}${durStr}`);
|
|
@@ -13574,7 +13456,7 @@ var MMLCore = class _MMLCore {
|
|
|
13574
13456
|
* MMLの音長文字列("4", "4.", "12"など)をステップ数に変換する
|
|
13575
13457
|
*/
|
|
13576
13458
|
getStepFromDottedMML(durStr) {
|
|
13577
|
-
const config =
|
|
13459
|
+
const config = this.getConfig();
|
|
13578
13460
|
const total = config.stepsPerBar;
|
|
13579
13461
|
const isDotted = durStr.endsWith(".");
|
|
13580
13462
|
const baseDur = parseInt(isDotted ? durStr.slice(0, -1) : durStr, 10);
|
|
@@ -13584,7 +13466,7 @@ var MMLCore = class _MMLCore {
|
|
|
13584
13466
|
};
|
|
13585
13467
|
var decomposeToMonophonic = (notes) => {
|
|
13586
13468
|
const sorted = [...notes].sort(
|
|
13587
|
-
(a, b) => a.startStep - b.startStep || a.
|
|
13469
|
+
(a, b) => a.startStep - b.startStep || a.pitchUnits - b.pitchUnits
|
|
13588
13470
|
);
|
|
13589
13471
|
const tracks = [];
|
|
13590
13472
|
const trackEnds = [];
|
|
@@ -13619,6 +13501,493 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
|
|
|
13619
13501
|
return chordNotes / notes.length >= threshold;
|
|
13620
13502
|
};
|
|
13621
13503
|
|
|
13504
|
+
// src/renderer.ts
|
|
13505
|
+
var KEYBOARD_WIDTH = 60;
|
|
13506
|
+
var HEADER_HEIGHT = 20;
|
|
13507
|
+
var NATURAL_STEPS3 = {
|
|
13508
|
+
12: [0, 2, 4, 5, 7, 9, 11],
|
|
13509
|
+
31: [0, 5, 10, 13, 18, 23, 28]
|
|
13510
|
+
};
|
|
13511
|
+
var keyTier = (step, edo) => {
|
|
13512
|
+
const naturals = NATURAL_STEPS3[edo] ?? NATURAL_STEPS3[12];
|
|
13513
|
+
let best = edo;
|
|
13514
|
+
for (const n of naturals) {
|
|
13515
|
+
const d = Math.abs(step - n);
|
|
13516
|
+
best = Math.min(best, d, edo - d);
|
|
13517
|
+
}
|
|
13518
|
+
if (best === 0) return 0;
|
|
13519
|
+
return best >= (edo === 31 ? 2 : 1) ? 2 : 1;
|
|
13520
|
+
};
|
|
13521
|
+
var KEY_NAMES = [
|
|
13522
|
+
"C",
|
|
13523
|
+
"C#",
|
|
13524
|
+
"D",
|
|
13525
|
+
"D#",
|
|
13526
|
+
"E",
|
|
13527
|
+
"F",
|
|
13528
|
+
"F#",
|
|
13529
|
+
"G",
|
|
13530
|
+
"G#",
|
|
13531
|
+
"A",
|
|
13532
|
+
"A#",
|
|
13533
|
+
"B"
|
|
13534
|
+
];
|
|
13535
|
+
var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
13536
|
+
let g_header_canvas;
|
|
13537
|
+
let g_key_canvas;
|
|
13538
|
+
let g_grid_canvas;
|
|
13539
|
+
let g_header_ctx;
|
|
13540
|
+
let g_key_ctx;
|
|
13541
|
+
let g_grid_ctx;
|
|
13542
|
+
let g_config = config;
|
|
13543
|
+
const getRenderConfig = () => g_config;
|
|
13544
|
+
let g_draw_offset_x = 0;
|
|
13545
|
+
let g_draw_offset_y = 0;
|
|
13546
|
+
let g_bg_active = false;
|
|
13547
|
+
const setBackgroundActive = (active) => {
|
|
13548
|
+
g_bg_active = active;
|
|
13549
|
+
};
|
|
13550
|
+
const getDrawOffset = () => ({
|
|
13551
|
+
x: g_draw_offset_x,
|
|
13552
|
+
y: g_draw_offset_y
|
|
13553
|
+
});
|
|
13554
|
+
const getGridCanvas = () => g_grid_canvas;
|
|
13555
|
+
const getGridContext = () => g_grid_ctx;
|
|
13556
|
+
const getHeaderCanvas = () => g_header_canvas;
|
|
13557
|
+
const setup = () => {
|
|
13558
|
+
g_config = config;
|
|
13559
|
+
const headerCanvas = document.createElement("canvas");
|
|
13560
|
+
g_header_canvas = headerCanvas;
|
|
13561
|
+
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
13562
|
+
headerCanvas.height = HEADER_HEIGHT;
|
|
13563
|
+
headerCanvas.style.position = "absolute";
|
|
13564
|
+
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13565
|
+
headerCanvas.style.top = "0px";
|
|
13566
|
+
const headerCtx = headerCanvas.getContext("2d");
|
|
13567
|
+
if (!headerCtx)
|
|
13568
|
+
throw new Error("Failed to get 2D rendering context for header.");
|
|
13569
|
+
g_header_ctx = headerCtx;
|
|
13570
|
+
const keyCanvas = document.createElement("canvas");
|
|
13571
|
+
g_key_canvas = keyCanvas;
|
|
13572
|
+
keyCanvas.width = KEYBOARD_WIDTH;
|
|
13573
|
+
keyCanvas.height = height - HEADER_HEIGHT;
|
|
13574
|
+
keyCanvas.style.position = "absolute";
|
|
13575
|
+
keyCanvas.style.left = "0px";
|
|
13576
|
+
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13577
|
+
const keyCtx = keyCanvas.getContext("2d");
|
|
13578
|
+
if (!keyCtx)
|
|
13579
|
+
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
13580
|
+
g_key_ctx = keyCtx;
|
|
13581
|
+
const gridCanvas = document.createElement("canvas");
|
|
13582
|
+
g_grid_canvas = gridCanvas;
|
|
13583
|
+
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
13584
|
+
gridCanvas.height = height - HEADER_HEIGHT;
|
|
13585
|
+
gridCanvas.style.position = "absolute";
|
|
13586
|
+
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13587
|
+
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13588
|
+
gridCanvas.style.touchAction = "none";
|
|
13589
|
+
gridCanvas.style.userSelect = "none";
|
|
13590
|
+
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
13591
|
+
if (!gridCtx)
|
|
13592
|
+
throw new Error("Failed to get 2D rendering context for grid.");
|
|
13593
|
+
g_grid_ctx = gridCtx;
|
|
13594
|
+
mountTarget.innerHTML = "";
|
|
13595
|
+
mountTarget.style.position = "relative";
|
|
13596
|
+
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
13597
|
+
mountTarget.style.height = `${height}px`;
|
|
13598
|
+
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
13599
|
+
drawHeaderCorner();
|
|
13600
|
+
};
|
|
13601
|
+
const drawHeaderCorner = () => {
|
|
13602
|
+
const mountTarget2 = g_key_canvas.parentElement;
|
|
13603
|
+
if (!mountTarget2) return;
|
|
13604
|
+
let cornerDiv = mountTarget2.querySelector(
|
|
13605
|
+
"#header-corner"
|
|
13606
|
+
);
|
|
13607
|
+
if (!cornerDiv) {
|
|
13608
|
+
cornerDiv = document.createElement("div");
|
|
13609
|
+
cornerDiv.id = "header-corner";
|
|
13610
|
+
cornerDiv.style.position = "absolute";
|
|
13611
|
+
cornerDiv.style.left = "0px";
|
|
13612
|
+
cornerDiv.style.top = "0px";
|
|
13613
|
+
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
13614
|
+
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
13615
|
+
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
13616
|
+
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
13617
|
+
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
13618
|
+
mountTarget2.insertBefore(cornerDiv, g_header_canvas);
|
|
13619
|
+
}
|
|
13620
|
+
};
|
|
13621
|
+
const drawKeyboard = () => {
|
|
13622
|
+
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
13623
|
+
const {
|
|
13624
|
+
keyHeight,
|
|
13625
|
+
keyCount,
|
|
13626
|
+
pitchRangeStart,
|
|
13627
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13628
|
+
edo = 12
|
|
13629
|
+
} = g_config;
|
|
13630
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13631
|
+
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
13632
|
+
const WHITE_KEY = "#ccc8b4";
|
|
13633
|
+
const BLACK_KEY = "#111111";
|
|
13634
|
+
const MICRO_KEY = "#4a4a4a";
|
|
13635
|
+
const BK_EDGE = "#383838";
|
|
13636
|
+
const WW_SEP = "#807a6a";
|
|
13637
|
+
const BK_RATIO = 0.62;
|
|
13638
|
+
const MICRO_RATIO = 0.45;
|
|
13639
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13640
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13641
|
+
const units = pitchRangeStart + rowIndex * upr;
|
|
13642
|
+
const step = Math.round(
|
|
13643
|
+
(units % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13644
|
+
);
|
|
13645
|
+
const tier = keyTier(step, edo);
|
|
13646
|
+
const octave = Math.floor(units / UNITS_PER_OCTAVE) - 1;
|
|
13647
|
+
const isC4Range = octave === 4;
|
|
13648
|
+
const screenY = y - g_draw_offset_y;
|
|
13649
|
+
const bkW = Math.floor(
|
|
13650
|
+
KEYBOARD_WIDTH * (tier === 1 ? MICRO_RATIO : BK_RATIO)
|
|
13651
|
+
);
|
|
13652
|
+
if (tier !== 0) {
|
|
13653
|
+
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
13654
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13655
|
+
g_key_ctx.fillStyle = tier === 1 ? MICRO_KEY : isC4Range ? "#1a1408" : BLACK_KEY;
|
|
13656
|
+
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
13657
|
+
g_key_ctx.strokeStyle = BK_EDGE;
|
|
13658
|
+
g_key_ctx.lineWidth = 1;
|
|
13659
|
+
g_key_ctx.beginPath();
|
|
13660
|
+
g_key_ctx.moveTo(bkW, screenY);
|
|
13661
|
+
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
13662
|
+
g_key_ctx.stroke();
|
|
13663
|
+
} else {
|
|
13664
|
+
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
13665
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13666
|
+
if (edo === 12 && (step === 5 || step === 0)) {
|
|
13667
|
+
g_key_ctx.strokeStyle = WW_SEP;
|
|
13668
|
+
g_key_ctx.lineWidth = 1;
|
|
13669
|
+
g_key_ctx.beginPath();
|
|
13670
|
+
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
13671
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
13672
|
+
g_key_ctx.stroke();
|
|
13673
|
+
}
|
|
13674
|
+
}
|
|
13675
|
+
if (step === 0) {
|
|
13676
|
+
g_key_ctx.fillStyle = "#555040";
|
|
13677
|
+
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
13678
|
+
g_key_ctx.textAlign = "right";
|
|
13679
|
+
g_key_ctx.textBaseline = "bottom";
|
|
13680
|
+
g_key_ctx.fillText(
|
|
13681
|
+
`${KEY_NAMES[0]}${octave}`,
|
|
13682
|
+
KEYBOARD_WIDTH - 4,
|
|
13683
|
+
screenY + keyHeight - 2
|
|
13684
|
+
);
|
|
13685
|
+
}
|
|
13686
|
+
}
|
|
13687
|
+
g_key_ctx.beginPath();
|
|
13688
|
+
g_key_ctx.strokeStyle = "#29adff";
|
|
13689
|
+
g_key_ctx.lineWidth = 2;
|
|
13690
|
+
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
13691
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
13692
|
+
g_key_ctx.stroke();
|
|
13693
|
+
};
|
|
13694
|
+
const drawHeader = () => {
|
|
13695
|
+
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
13696
|
+
const { stepWidth, stepsPerBar } = g_config;
|
|
13697
|
+
g_header_ctx.save();
|
|
13698
|
+
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
13699
|
+
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
13700
|
+
g_header_ctx.fillRect(
|
|
13701
|
+
g_draw_offset_x,
|
|
13702
|
+
0,
|
|
13703
|
+
g_header_canvas.width,
|
|
13704
|
+
HEADER_HEIGHT
|
|
13705
|
+
);
|
|
13706
|
+
g_header_ctx.strokeStyle = "#3d405b";
|
|
13707
|
+
g_header_ctx.lineWidth = 1;
|
|
13708
|
+
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
13709
|
+
g_header_ctx.fillStyle = "#83769c";
|
|
13710
|
+
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
13711
|
+
const endBar = Math.ceil(
|
|
13712
|
+
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13713
|
+
);
|
|
13714
|
+
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13715
|
+
const x2 = bar * stepsPerBar * stepWidth;
|
|
13716
|
+
const screenX = x2;
|
|
13717
|
+
g_header_ctx.beginPath();
|
|
13718
|
+
g_header_ctx.moveTo(screenX, 0);
|
|
13719
|
+
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13720
|
+
g_header_ctx.stroke();
|
|
13721
|
+
if (bar >= 0) {
|
|
13722
|
+
g_header_ctx.textAlign = "left";
|
|
13723
|
+
g_header_ctx.textBaseline = "middle";
|
|
13724
|
+
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13725
|
+
}
|
|
13726
|
+
}
|
|
13727
|
+
g_header_ctx.restore();
|
|
13728
|
+
};
|
|
13729
|
+
const drawGrid = (noteLengthSteps = 1) => {
|
|
13730
|
+
drawKeyboard();
|
|
13731
|
+
drawHeader();
|
|
13732
|
+
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13733
|
+
const {
|
|
13734
|
+
keyHeight,
|
|
13735
|
+
keyCount,
|
|
13736
|
+
stepWidth,
|
|
13737
|
+
stepsPerBar,
|
|
13738
|
+
pitchRangeStart,
|
|
13739
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13740
|
+
edo = 12
|
|
13741
|
+
} = g_config;
|
|
13742
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13743
|
+
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13744
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13745
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13746
|
+
const units = pitchRangeStart + rowIndex * upr;
|
|
13747
|
+
const step = Math.round(
|
|
13748
|
+
(units % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13749
|
+
);
|
|
13750
|
+
const tier = keyTier(step, edo);
|
|
13751
|
+
const isC = step === 0;
|
|
13752
|
+
const octave = Math.floor(units / UNITS_PER_OCTAVE) - 1;
|
|
13753
|
+
const isC4Range = octave === 4;
|
|
13754
|
+
const screenY = y - g_draw_offset_y;
|
|
13755
|
+
g_grid_ctx.fillStyle = g_bg_active ? tier === 2 ? "rgba(8,11,22,0.55)" : tier === 1 ? "rgba(12,16,30,0.5)" : "rgba(17,22,40,0.45)" : tier === 2 ? "#080b16" : tier === 1 ? "#0c101d" : "#111628";
|
|
13756
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13757
|
+
if (isC4Range) {
|
|
13758
|
+
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13759
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13760
|
+
}
|
|
13761
|
+
g_grid_ctx.beginPath();
|
|
13762
|
+
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13763
|
+
g_grid_ctx.lineWidth = 1;
|
|
13764
|
+
const lineY = screenY + keyHeight;
|
|
13765
|
+
g_grid_ctx.moveTo(0, lineY);
|
|
13766
|
+
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13767
|
+
g_grid_ctx.stroke();
|
|
13768
|
+
}
|
|
13769
|
+
const gridStep = noteLengthSteps || 48;
|
|
13770
|
+
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13771
|
+
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13772
|
+
const lineStep = stepWidth * gridStep;
|
|
13773
|
+
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13774
|
+
const step = x2 / stepWidth;
|
|
13775
|
+
const isBarLine = step % stepsPerBar === 0;
|
|
13776
|
+
const isNoteLine = step % gridStep === 0;
|
|
13777
|
+
const screenX = x2 - g_draw_offset_x;
|
|
13778
|
+
g_grid_ctx.beginPath();
|
|
13779
|
+
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13780
|
+
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13781
|
+
g_grid_ctx.moveTo(screenX, 0);
|
|
13782
|
+
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13783
|
+
g_grid_ctx.stroke();
|
|
13784
|
+
}
|
|
13785
|
+
};
|
|
13786
|
+
const drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13787
|
+
const {
|
|
13788
|
+
keyHeight,
|
|
13789
|
+
stepWidth,
|
|
13790
|
+
keyCount,
|
|
13791
|
+
pitchRangeStart,
|
|
13792
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13793
|
+
} = g_config;
|
|
13794
|
+
const canvasWidth = g_grid_canvas.width;
|
|
13795
|
+
const canvasHeight = g_grid_canvas.height;
|
|
13796
|
+
const [r, g, b, a] = color;
|
|
13797
|
+
for (const note of notes) {
|
|
13798
|
+
const logicalX = note.startStep * stepWidth;
|
|
13799
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13800
|
+
const logicalY = yIndex * keyHeight;
|
|
13801
|
+
const w = note.durationSteps * stepWidth;
|
|
13802
|
+
const h = keyHeight;
|
|
13803
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13804
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13805
|
+
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13806
|
+
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13807
|
+
if (isActive) {
|
|
13808
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13809
|
+
const finalOpacity = a * velocityOpacity;
|
|
13810
|
+
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13811
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13812
|
+
if (w >= 4 && h >= 4) {
|
|
13813
|
+
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13814
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13815
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13816
|
+
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13817
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13818
|
+
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13819
|
+
}
|
|
13820
|
+
} else {
|
|
13821
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13822
|
+
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13823
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13824
|
+
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13825
|
+
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13826
|
+
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13827
|
+
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13828
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
};
|
|
13832
|
+
const drawNoteLyrics = (notes, syllables) => {
|
|
13833
|
+
if (syllables.length === 0) return;
|
|
13834
|
+
const {
|
|
13835
|
+
keyHeight,
|
|
13836
|
+
stepWidth,
|
|
13837
|
+
keyCount,
|
|
13838
|
+
pitchRangeStart,
|
|
13839
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13840
|
+
} = g_config;
|
|
13841
|
+
if (keyHeight < 7) return;
|
|
13842
|
+
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13843
|
+
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13844
|
+
const count = Math.min(sorted.length, syllables.length);
|
|
13845
|
+
g_grid_ctx.save();
|
|
13846
|
+
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13847
|
+
g_grid_ctx.textAlign = "left";
|
|
13848
|
+
g_grid_ctx.textBaseline = "middle";
|
|
13849
|
+
g_grid_ctx.lineWidth = 3;
|
|
13850
|
+
g_grid_ctx.lineJoin = "round";
|
|
13851
|
+
for (let i2 = 0; i2 < count; i2++) {
|
|
13852
|
+
const kana = syllables[i2];
|
|
13853
|
+
if (!kana) continue;
|
|
13854
|
+
const note = sorted[i2];
|
|
13855
|
+
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
13856
|
+
const renderY = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
|
|
13857
|
+
const w = note.durationSteps * stepWidth;
|
|
13858
|
+
if (w < 6) continue;
|
|
13859
|
+
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
13860
|
+
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
13861
|
+
const textX = renderX + 2;
|
|
13862
|
+
const textY = renderY + keyHeight / 2;
|
|
13863
|
+
g_grid_ctx.save();
|
|
13864
|
+
g_grid_ctx.beginPath();
|
|
13865
|
+
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
13866
|
+
g_grid_ctx.clip();
|
|
13867
|
+
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
13868
|
+
g_grid_ctx.strokeText(kana, textX, textY);
|
|
13869
|
+
g_grid_ctx.fillStyle = "#fff1e8";
|
|
13870
|
+
g_grid_ctx.fillText(kana, textX, textY);
|
|
13871
|
+
g_grid_ctx.restore();
|
|
13872
|
+
}
|
|
13873
|
+
g_grid_ctx.restore();
|
|
13874
|
+
};
|
|
13875
|
+
const drawSelectionRect = (rect) => {
|
|
13876
|
+
if (!rect) return;
|
|
13877
|
+
g_grid_ctx.save();
|
|
13878
|
+
g_grid_ctx.strokeStyle = "#ffec27";
|
|
13879
|
+
g_grid_ctx.lineWidth = 2;
|
|
13880
|
+
g_grid_ctx.setLineDash([4, 4]);
|
|
13881
|
+
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
13882
|
+
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
13883
|
+
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
13884
|
+
g_grid_ctx.restore();
|
|
13885
|
+
};
|
|
13886
|
+
const drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
13887
|
+
const {
|
|
13888
|
+
keyHeight,
|
|
13889
|
+
stepWidth,
|
|
13890
|
+
keyCount,
|
|
13891
|
+
pitchRangeStart,
|
|
13892
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13893
|
+
} = g_config;
|
|
13894
|
+
for (const note of notes) {
|
|
13895
|
+
if (!selectedIds.has(note.id)) continue;
|
|
13896
|
+
const logicalX = note.startStep * stepWidth;
|
|
13897
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13898
|
+
const logicalY = yIndex * keyHeight;
|
|
13899
|
+
const w = note.durationSteps * stepWidth;
|
|
13900
|
+
const h = keyHeight;
|
|
13901
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13902
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13903
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13904
|
+
const [r, g, b, a] = baseColor;
|
|
13905
|
+
const darkenFactor = 1.3;
|
|
13906
|
+
const darkerR = Math.min(255, r * darkenFactor);
|
|
13907
|
+
const darkerG = Math.min(255, g * darkenFactor);
|
|
13908
|
+
const darkerB = Math.min(255, b * darkenFactor);
|
|
13909
|
+
const finalOpacity = a * velocityOpacity;
|
|
13910
|
+
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
13911
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13912
|
+
}
|
|
13913
|
+
};
|
|
13914
|
+
const getXY = (e) => {
|
|
13915
|
+
const { clientX, clientY } = e;
|
|
13916
|
+
const rect = g_grid_canvas.getBoundingClientRect();
|
|
13917
|
+
const x2 = Math.floor(clientX - rect.left);
|
|
13918
|
+
const y = Math.floor(clientY - rect.top);
|
|
13919
|
+
return [x2, y, e.buttons];
|
|
13920
|
+
};
|
|
13921
|
+
const getGridPosition = (e) => {
|
|
13922
|
+
const [x2, y] = getXY(e);
|
|
13923
|
+
const {
|
|
13924
|
+
keyCount,
|
|
13925
|
+
pitchRangeStart,
|
|
13926
|
+
keyHeight,
|
|
13927
|
+
stepWidth,
|
|
13928
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13929
|
+
} = g_config;
|
|
13930
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13931
|
+
const absoluteY = y + g_draw_offset_y;
|
|
13932
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13933
|
+
const pitch = pitchRangeStart + (keyCount - 1 - yIndex) * upr;
|
|
13934
|
+
return { step, pitch, x: x2, y };
|
|
13935
|
+
};
|
|
13936
|
+
const onClick = (callback) => {
|
|
13937
|
+
g_grid_canvas.addEventListener(
|
|
13938
|
+
"click",
|
|
13939
|
+
(e) => {
|
|
13940
|
+
const [x2, y] = getXY(e);
|
|
13941
|
+
const {
|
|
13942
|
+
keyCount,
|
|
13943
|
+
pitchRangeStart,
|
|
13944
|
+
keyHeight,
|
|
13945
|
+
stepWidth,
|
|
13946
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13947
|
+
} = g_config;
|
|
13948
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13949
|
+
const absoluteY = y + g_draw_offset_y;
|
|
13950
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13951
|
+
const pitch = pitchRangeStart + (keyCount - 1 - yIndex) * upr;
|
|
13952
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
13953
|
+
requestAnimationFrame(() => callback(step, pitch));
|
|
13954
|
+
}
|
|
13955
|
+
},
|
|
13956
|
+
{ passive: true }
|
|
13957
|
+
);
|
|
13958
|
+
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
13959
|
+
};
|
|
13960
|
+
const setDrawOffset = (x2, y) => {
|
|
13961
|
+
g_draw_offset_x = x2;
|
|
13962
|
+
g_draw_offset_y = y;
|
|
13963
|
+
drawKeyboard();
|
|
13964
|
+
drawHeader();
|
|
13965
|
+
};
|
|
13966
|
+
setup();
|
|
13967
|
+
return {
|
|
13968
|
+
getRenderConfig,
|
|
13969
|
+
setBackgroundActive,
|
|
13970
|
+
getDrawOffset,
|
|
13971
|
+
getGridCanvas,
|
|
13972
|
+
getGridContext,
|
|
13973
|
+
getHeaderCanvas,
|
|
13974
|
+
drawKeyboard,
|
|
13975
|
+
drawHeader,
|
|
13976
|
+
drawGrid,
|
|
13977
|
+
drawNotes,
|
|
13978
|
+
drawNoteLyrics,
|
|
13979
|
+
drawSelectionRect,
|
|
13980
|
+
drawSelectedNotes,
|
|
13981
|
+
getXY,
|
|
13982
|
+
getGridPosition,
|
|
13983
|
+
onClick,
|
|
13984
|
+
setDrawOffset,
|
|
13985
|
+
destroy: () => {
|
|
13986
|
+
mountTarget.innerHTML = "";
|
|
13987
|
+
}
|
|
13988
|
+
};
|
|
13989
|
+
};
|
|
13990
|
+
|
|
13622
13991
|
// src/daw.ts
|
|
13623
13992
|
var CHORD_INFO_HTML2 = `
|
|
13624
13993
|
<div class="dtm-modal-body-content">
|
|
@@ -13905,6 +14274,27 @@ var MIDI_INFO_HTML = `
|
|
|
13905
14274
|
<p style="margin-top:4px;"><small>\u6B4C\u8A5E\u306E\u62BD\u51FA: <a href="https://rpgen3.github.io/ust2txt/" target="_blank" rel="noopener">ust2txt</a></small></p>
|
|
13906
14275
|
</div>
|
|
13907
14276
|
`;
|
|
14277
|
+
var EDO_INFO_HTML = `
|
|
14278
|
+
<div class="dtm-modal-section">
|
|
14279
|
+
<p><strong>\u97F3\u5F8B</strong>\u306F1\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u4F55\u7B49\u5206\u3059\u308B\u304B\u306E\u8A2D\u5B9A\u3067\u3059\u3002\u66F2\u5168\u4F53\u306B\u52B9\u304D\u307E\u3059\uFF08\u30C8\u30E9\u30C3\u30AF\u3054\u3068\u30FB\u5C0F\u7BC0\u3054\u3068\u306B\u306F\u5909\u3048\u3089\u308C\u307E\u305B\u3093\uFF09\u3002</p>
|
|
14280
|
+
<p><strong>12\u5E73\u5747\u5F8B</strong>\u304C\u901A\u5E38\u306E\u30D4\u30A2\u30CE\u3068\u540C\u3058\u8ABF\u5F8B\u3067\u3059\u3002<strong>31\u5E73\u5747\u5F8B</strong>\u306F1\u30AA\u30AF\u30BF\u30FC\u30D6\u309231\u7B49\u5206\u3057\u3001\u30D4\u30A2\u30CE\u306B\u306F\u7121\u3044\u97F3\uFF08\u5FAE\u5206\u97F3\uFF09\u304C\u4F7F\u3048\u307E\u3059\u3002</p>
|
|
14281
|
+
<p style="margin-top:8px;"><strong>31\u5E73\u5747\u5F8B\u306E\u7279\u5FB4</strong></p>
|
|
14282
|
+
<ul>
|
|
14283
|
+
<li>\u95773\u5EA6\u304C\u7D14\u6B63\u306B\u3054\u304F\u8FD1\u304F\uFF08\u8AA4\u5DEE0.8\u30BB\u30F3\u30C8\uFF09\u3001<strong>\u548C\u97F3\u304C12\u5E73\u5747\u5F8B\u3088\u308A\u7DBA\u9E97\u306B\u97FF\u304D\u307E\u3059</strong>\u3002</li>
|
|
14284
|
+
<li>\u30D4\u30A2\u30CE\u306B\u7121\u3044\u97F3\u304C\u4F7F\u3048\u307E\u3059\u3002\u4E2D\u7ACB3\u5EA6\uFF08\u9577\u77ED\u3069\u3061\u3089\u3067\u3082\u306A\u30443\u5EA6\uFF09\u3001\u81EA\u71367\u5EA6\uFF08\u30D0\u30FC\u30D0\u30FC\u30B7\u30E7\u30C3\u30D7\u306E\u97FF\u304D\uFF09\u306A\u3069\u3002</li>
|
|
14285
|
+
<li>C\u266F \u3068 D\u266D \u304C<strong>\u5225\u306E\u97F3</strong>\u306B\u306A\u308A\u307E\u3059\uFF0812\u5E73\u5747\u5F8B\u3067\u306F\u540C\u3058\u97F3\uFF09\u3002</li>
|
|
14286
|
+
</ul>
|
|
14287
|
+
<p style="margin-top:8px;"><strong>MML\u3067\u306E\u66F8\u304D\u65B9</strong></p>
|
|
14288
|
+
<p><code>#edo=31</code> \u3092\u5BA3\u8A00\u3057\u307E\u3059\u3002\u81E8\u6642\u8A18\u53F7\u306F4\u3064\u4F7F\u3044\u5206\u3051\u307E\u3059\u3002</p>
|
|
14289
|
+
<ul>
|
|
14290
|
+
<li><code>#</code> \u2026 \u534A\u97F3\u4E0A\u3052\uFF082\u5EA6\uFF09\u3000<code>-</code> \u2026 \u534A\u97F3\u4E0B\u3052\uFF082\u5EA6\uFF09</li>
|
|
14291
|
+
<li><code>+</code> \u2026 \u5FAE\u5206\u97F31\u3064\u4E0A\u3052\uFF081\u5EA6\uFF09\u3000<code>_</code> \u2026 \u5FAE\u5206\u97F31\u3064\u4E0B\u3052\uFF081\u5EA6\uFF09</li>
|
|
14292
|
+
</ul>
|
|
14293
|
+
<p>\u4F8B: <code>#edo=31 @0 o4 c c+ c# d- d_ d;</code></p>
|
|
14294
|
+
<p style="margin-top:8px;"><small>12\u5E73\u5747\u5F8B\u3067\u306F4\u8A18\u53F7\u3068\u3082\u5F93\u6765\u901A\u308A\u306E\u610F\u5473\uFF08\u30B7\u30E3\u30FC\u30D72\u3064\u30FB\u30D5\u30E9\u30C3\u30C82\u3064\uFF09\u306B\u306A\u308B\u306E\u3067\u3001\u65E2\u5B58\u306E\u66F2\u306E\u89E3\u91C8\u306F\u5909\u308F\u308A\u307E\u305B\u3093\u3002</small></p>
|
|
14295
|
+
<p style="margin-top:8px;"><small>\u5207\u308A\u66FF\u3048\u308B\u3068\u3001\u65E2\u5B58\u306E\u97F3\u7B26\u306F\u65B0\u3057\u3044\u97F3\u5F8B\u306E\u683C\u5B50\u3078\u4E38\u3081\u3089\u308C\u307E\u3059\u300212\u219231 \u306F\u6700\u592719\u30BB\u30F3\u30C8\u306E\u79FB\u52D5\u3067\u5143\u306B\u623B\u305B\u307E\u3059\u304C\u3001<strong>31\u219212 \u306F\u6700\u592748\u30BB\u30F3\u30C8\u52D5\u304D\u300112\u5E73\u5747\u5F8B\u306B\u7121\u3044\u97F3\u306F\u5931\u308F\u308C\u307E\u3059</strong>\u3002</small></p>
|
|
14296
|
+
</div>
|
|
14297
|
+
`;
|
|
13908
14298
|
var KOE_INFO_HTML = `
|
|
13909
14299
|
<div class="dtm-modal-body-content">
|
|
13910
14300
|
<h4>1. UTAU\u97F3\u6E90\u3092 .koe \u306B\u5909\u63DB\u3059\u308B</h4>
|
|
@@ -14122,7 +14512,7 @@ var AUTO_ROLE_BASE_VOLUME = {
|
|
|
14122
14512
|
chord: 72
|
|
14123
14513
|
};
|
|
14124
14514
|
var computeAutoRoleStats = (notes) => {
|
|
14125
|
-
const avgPitch = notes.reduce((s, n) => s + n.
|
|
14515
|
+
const avgPitch = notes.reduce((s, n) => s + n.pitchUnits, 0) / notes.length;
|
|
14126
14516
|
const avgDur = notes.reduce((s, n) => s + n.durationSteps, 0) / notes.length;
|
|
14127
14517
|
const byStart = /* @__PURE__ */ new Map();
|
|
14128
14518
|
for (const n of notes)
|
|
@@ -14136,8 +14526,9 @@ var computeAutoRoleStats = (notes) => {
|
|
|
14136
14526
|
const notesPerBeat = notes.length / spanBeats;
|
|
14137
14527
|
return { avgPitch, avgDur, avgPoly, maxPoly, notesPerBeat };
|
|
14138
14528
|
};
|
|
14529
|
+
var AUTO_ROLE_BASS_UNITS = 48 * UNITS_PER_SEMITONE;
|
|
14139
14530
|
var classifyTrackRole = (stats) => {
|
|
14140
|
-
if (stats.avgPitch <
|
|
14531
|
+
if (stats.avgPitch < AUTO_ROLE_BASS_UNITS) return "bass";
|
|
14141
14532
|
if (stats.maxPoly >= 2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT)
|
|
14142
14533
|
return "chord";
|
|
14143
14534
|
if (stats.notesPerBeat < 1.2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT * 1.5)
|
|
@@ -14149,7 +14540,10 @@ var computeAutoRoleVolume = (role, stats) => {
|
|
|
14149
14540
|
if (role === "chord" && stats.avgPoly > 1) {
|
|
14150
14541
|
vol /= Math.sqrt(stats.avgPoly);
|
|
14151
14542
|
} else if (role === "bass") {
|
|
14152
|
-
const depthSemitones = Math.max(
|
|
14543
|
+
const depthSemitones = Math.max(
|
|
14544
|
+
0,
|
|
14545
|
+
(AUTO_ROLE_BASS_UNITS - stats.avgPitch) / UNITS_PER_SEMITONE
|
|
14546
|
+
);
|
|
14153
14547
|
vol += Math.min(12, depthSemitones * 0.5);
|
|
14154
14548
|
}
|
|
14155
14549
|
return clamp5(Math.round(vol), 1, 127);
|
|
@@ -14241,17 +14635,48 @@ var mountDAW = (target, options = {}) => {
|
|
|
14241
14635
|
refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
|
|
14242
14636
|
const renderConfig = {
|
|
14243
14637
|
stepsPerBar: 192,
|
|
14244
|
-
keyCount:
|
|
14245
|
-
pitchRangeStart:
|
|
14638
|
+
keyCount: KEY_COUNT,
|
|
14639
|
+
pitchRangeStart: PITCH_RANGE_START,
|
|
14640
|
+
unitsPerRow: unitsPerRow(12),
|
|
14641
|
+
edo: 12,
|
|
14246
14642
|
keyHeight: BASE_KEY_HEIGHT,
|
|
14247
14643
|
stepWidth: BASE_STEP_WIDTH * 2
|
|
14248
14644
|
// zoom100% 相当
|
|
14249
14645
|
};
|
|
14646
|
+
let renderer;
|
|
14250
14647
|
let zoomX = 100;
|
|
14251
14648
|
let zoomY = 100;
|
|
14252
14649
|
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
14253
14650
|
let masterVolume = options.masterVolume ?? 50;
|
|
14254
|
-
|
|
14651
|
+
const applyMasterVolume = (volume) => {
|
|
14652
|
+
masterVolume = clamp5(Math.round(volume), 0, 100);
|
|
14653
|
+
refs.masterVolume.value = String(masterVolume);
|
|
14654
|
+
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
14655
|
+
options.singingVoices?.setVolume(masterVolume / 100);
|
|
14656
|
+
};
|
|
14657
|
+
applyMasterVolume(masterVolume);
|
|
14658
|
+
const snapToEdoGrid = (units) => {
|
|
14659
|
+
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
14660
|
+
return Math.round(units / upr) * upr;
|
|
14661
|
+
};
|
|
14662
|
+
const applyEdo = (edo, opts) => {
|
|
14663
|
+
const next = edo === 31 ? 31 : 12;
|
|
14664
|
+
if (renderConfig.edo === next) return;
|
|
14665
|
+
const upr = unitsPerRow(next);
|
|
14666
|
+
renderConfig.edo = next;
|
|
14667
|
+
renderConfig.unitsPerRow = upr;
|
|
14668
|
+
renderConfig.keyCount = keyCountFor(next);
|
|
14669
|
+
if (!opts?.snap) return;
|
|
14670
|
+
for (const t of trackStates) {
|
|
14671
|
+
t.core.beginBatch();
|
|
14672
|
+
for (const note of [...t.core.getNotes()]) {
|
|
14673
|
+
const snapped = Math.round(note.pitchUnits / upr) * upr;
|
|
14674
|
+
if (snapped !== note.pitchUnits)
|
|
14675
|
+
t.core.moveNote(note.id, note.startStep, snapped);
|
|
14676
|
+
}
|
|
14677
|
+
t.core.endBatch();
|
|
14678
|
+
}
|
|
14679
|
+
};
|
|
14255
14680
|
let reverbAmount = options.reverbAmount ?? 0;
|
|
14256
14681
|
let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
|
|
14257
14682
|
let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
|
|
@@ -14309,8 +14734,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14309
14734
|
let snapGridSteps = 12;
|
|
14310
14735
|
const gridLineSteps = 48;
|
|
14311
14736
|
let currentOffsetX = 0;
|
|
14312
|
-
const _initPitch = options.initialScrollPitch ?? 48;
|
|
14313
|
-
let currentOffsetY = (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215;
|
|
14737
|
+
const _initPitch = pitchV1ToUnits(options.initialScrollPitch ?? 48);
|
|
14738
|
+
let currentOffsetY = (renderConfig.keyCount - 1 - (_initPitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - 215;
|
|
14314
14739
|
let playStartStep = 0;
|
|
14315
14740
|
let isSolo = false;
|
|
14316
14741
|
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
@@ -14405,21 +14830,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
14405
14830
|
if (!ready) return;
|
|
14406
14831
|
if (!suppressPatch && options.onNotesPatch) {
|
|
14407
14832
|
const prevByKey = new Map(
|
|
14408
|
-
prevNotes.map((n) => [`${n.startStep}_${n.
|
|
14833
|
+
prevNotes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14409
14834
|
);
|
|
14410
14835
|
const currByKey = new Map(
|
|
14411
|
-
notes.map((n) => [`${n.startStep}_${n.
|
|
14836
|
+
notes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14412
14837
|
);
|
|
14413
14838
|
const added = notes.filter((n) => {
|
|
14414
|
-
const prev = prevByKey.get(
|
|
14839
|
+
const prev = prevByKey.get(
|
|
14840
|
+
`${n.startStep}_${n.pitchUnits}`
|
|
14841
|
+
);
|
|
14415
14842
|
return !prev || prev.durationSteps !== n.durationSteps || prev.velocity !== n.velocity;
|
|
14416
14843
|
}).map((n) => ({
|
|
14417
14844
|
startStep: n.startStep,
|
|
14418
|
-
|
|
14845
|
+
pitchUnits: n.pitchUnits,
|
|
14419
14846
|
durationSteps: n.durationSteps,
|
|
14420
14847
|
velocity: n.velocity
|
|
14421
14848
|
}));
|
|
14422
|
-
const removed = prevNotes.filter(
|
|
14849
|
+
const removed = prevNotes.filter(
|
|
14850
|
+
(n) => !currByKey.has(`${n.startStep}_${n.pitchUnits}`)
|
|
14851
|
+
).map((n) => ({
|
|
14852
|
+
startStep: n.startStep,
|
|
14853
|
+
pitchUnits: n.pitchUnits
|
|
14854
|
+
}));
|
|
14423
14855
|
if (added.length > 0 || removed.length > 0) {
|
|
14424
14856
|
options.onNotesPatch(config.id, added, removed);
|
|
14425
14857
|
}
|
|
@@ -14429,7 +14861,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14429
14861
|
updateUndoRedo();
|
|
14430
14862
|
}
|
|
14431
14863
|
},
|
|
14432
|
-
config.volume
|
|
14864
|
+
config.volume,
|
|
14865
|
+
() => renderConfig
|
|
14433
14866
|
),
|
|
14434
14867
|
volume: config.volume,
|
|
14435
14868
|
savedChordInput: "",
|
|
@@ -14524,18 +14957,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
14524
14957
|
return (lastNoteMeasure + 2) * renderConfig.stepsPerBar;
|
|
14525
14958
|
};
|
|
14526
14959
|
const getMaxOffsetX = () => {
|
|
14527
|
-
const canvas = getGridCanvas();
|
|
14960
|
+
const canvas = renderer.getGridCanvas();
|
|
14528
14961
|
const maxNoteStep = getMaxNoteStep();
|
|
14529
14962
|
const totalContentWidth = maxNoteStep * renderConfig.stepWidth;
|
|
14530
14963
|
return Math.max(0, totalContentWidth - canvas.width);
|
|
14531
14964
|
};
|
|
14532
14965
|
const getMaxOffsetY = () => {
|
|
14533
14966
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
14534
|
-
return Math.max(0, totalHeight - getGridCanvas().height);
|
|
14967
|
+
return Math.max(0, totalHeight - renderer.getGridCanvas().height);
|
|
14535
14968
|
};
|
|
14536
14969
|
const drawStartLine = () => {
|
|
14537
|
-
const ctx = getGridContext();
|
|
14538
|
-
const canvas = getGridCanvas();
|
|
14970
|
+
const ctx = renderer.getGridContext();
|
|
14971
|
+
const canvas = renderer.getGridCanvas();
|
|
14539
14972
|
if (!ctx) return;
|
|
14540
14973
|
const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
|
|
14541
14974
|
if (x2 < -10 || x2 > canvas.width + 10) return;
|
|
@@ -14550,8 +14983,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14550
14983
|
ctx.restore();
|
|
14551
14984
|
};
|
|
14552
14985
|
const drawPlayhead = () => {
|
|
14553
|
-
const ctx = getGridContext();
|
|
14554
|
-
const canvas = getGridCanvas();
|
|
14986
|
+
const ctx = renderer.getGridContext();
|
|
14987
|
+
const canvas = renderer.getGridCanvas();
|
|
14555
14988
|
if (!ctx) return;
|
|
14556
14989
|
const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
|
|
14557
14990
|
if (x2 < 0 || x2 > canvas.width) return;
|
|
@@ -14565,7 +14998,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14565
14998
|
ctx.restore();
|
|
14566
14999
|
};
|
|
14567
15000
|
const redrawAll = () => {
|
|
14568
|
-
drawGrid(gridLineSteps);
|
|
15001
|
+
renderer.drawGrid(gridLineSteps);
|
|
14569
15002
|
let lyricTargetNotes = null;
|
|
14570
15003
|
let activeTrackState = null;
|
|
14571
15004
|
for (const t of trackStates) {
|
|
@@ -14577,16 +15010,16 @@ var mountDAW = (target, options = {}) => {
|
|
|
14577
15010
|
}
|
|
14578
15011
|
const [r, g, b] = t.config.color;
|
|
14579
15012
|
const notes = t.core.getNotes();
|
|
14580
|
-
drawNotes(notes, [r, g, b, 1], false);
|
|
15013
|
+
renderer.drawNotes(notes, [r, g, b, 1], false);
|
|
14581
15014
|
}
|
|
14582
15015
|
if (activeTrackState) {
|
|
14583
15016
|
const [r, g, b] = activeTrackState.config.color;
|
|
14584
15017
|
const notes = activeTrackState.core.getNotes();
|
|
14585
|
-
drawNotes(notes, [r, g, b, 1], true);
|
|
15018
|
+
renderer.drawNotes(notes, [r, g, b, 1], true);
|
|
14586
15019
|
lyricTargetNotes = notes;
|
|
14587
15020
|
}
|
|
14588
15021
|
if (activeToolMode === "select" && selectionRect) {
|
|
14589
|
-
const ctx = getGridContext();
|
|
15022
|
+
const ctx = renderer.getGridContext();
|
|
14590
15023
|
ctx.save();
|
|
14591
15024
|
ctx.strokeStyle = "#ffec27";
|
|
14592
15025
|
ctx.lineWidth = 2;
|
|
@@ -14609,19 +15042,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
14609
15042
|
if (activeToolMode === "select" && selectedNotes.length > 0) {
|
|
14610
15043
|
const ids = new Set(selectedNotes.map((n) => n.id));
|
|
14611
15044
|
const active = getActive();
|
|
14612
|
-
drawSelectedNotes(active.core.getNotes(), ids, [
|
|
15045
|
+
renderer.drawSelectedNotes(active.core.getNotes(), ids, [
|
|
14613
15046
|
...active.config.color,
|
|
14614
15047
|
1
|
|
14615
15048
|
]);
|
|
14616
15049
|
}
|
|
14617
15050
|
if (lyricTargetNotes)
|
|
14618
|
-
drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
15051
|
+
renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
14619
15052
|
drawStartLine();
|
|
14620
15053
|
if (playbackState === "playing") drawPlayhead();
|
|
14621
15054
|
updateScrollbars();
|
|
14622
15055
|
};
|
|
14623
15056
|
const updateScrollbars = () => {
|
|
14624
|
-
const canvas = getGridCanvas();
|
|
15057
|
+
const canvas = renderer.getGridCanvas();
|
|
14625
15058
|
const maxOffsetX = getMaxOffsetX();
|
|
14626
15059
|
const sbW = refs.hScroll.clientWidth;
|
|
14627
15060
|
if (maxOffsetX <= 0) {
|
|
@@ -14705,7 +15138,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14705
15138
|
const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
14706
15139
|
const ratio = x2 / (rect.width - thumbW);
|
|
14707
15140
|
currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
|
|
14708
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15141
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14709
15142
|
redrawAll();
|
|
14710
15143
|
};
|
|
14711
15144
|
const moveV = (clientY) => {
|
|
@@ -14716,7 +15149,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14716
15149
|
const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
14717
15150
|
const ratio = y / (rect.height - thumbH);
|
|
14718
15151
|
currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
|
|
14719
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15152
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14720
15153
|
redrawAll();
|
|
14721
15154
|
};
|
|
14722
15155
|
};
|
|
@@ -14755,8 +15188,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14755
15188
|
const autoScrollTick = () => {
|
|
14756
15189
|
autoScrollRAF = null;
|
|
14757
15190
|
if (!isSelecting || !lastMoveEvent) return;
|
|
14758
|
-
const canvas = getGridCanvas();
|
|
14759
|
-
const { x: x2, y } = getGridPosition(lastMoveEvent);
|
|
15191
|
+
const canvas = renderer.getGridCanvas();
|
|
15192
|
+
const { x: x2, y } = renderer.getGridPosition(lastMoveEvent);
|
|
14760
15193
|
const dx = computeEdgeSpeed(x2, canvas.width);
|
|
14761
15194
|
const dy = computeEdgeSpeed(y, canvas.height);
|
|
14762
15195
|
if (dx !== 0 || dy !== 0) {
|
|
@@ -14764,7 +15197,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14764
15197
|
const maxOffsetY = getMaxOffsetY();
|
|
14765
15198
|
currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
|
|
14766
15199
|
currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
|
|
14767
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15200
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14768
15201
|
onPointerMove(lastMoveEvent);
|
|
14769
15202
|
}
|
|
14770
15203
|
if (isSelecting) {
|
|
@@ -14785,11 +15218,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14785
15218
|
};
|
|
14786
15219
|
const findActiveNoteAt = (x2, y, margin = 0) => {
|
|
14787
15220
|
const active = getActive();
|
|
14788
|
-
const {
|
|
14789
|
-
|
|
15221
|
+
const {
|
|
15222
|
+
stepWidth,
|
|
15223
|
+
keyHeight,
|
|
15224
|
+
keyCount,
|
|
15225
|
+
pitchRangeStart,
|
|
15226
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15227
|
+
} = renderConfig;
|
|
15228
|
+
const offset = renderer.getDrawOffset();
|
|
14790
15229
|
for (const note of active.core.getNotes()) {
|
|
14791
15230
|
const logicalX = note.startStep * stepWidth;
|
|
14792
|
-
const yIndex = keyCount - 1 - (note.
|
|
15231
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14793
15232
|
const logicalY = yIndex * keyHeight;
|
|
14794
15233
|
const w = note.durationSteps * stepWidth;
|
|
14795
15234
|
const renderX = logicalX - offset.x;
|
|
@@ -14802,7 +15241,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14802
15241
|
const hasNoteAt = (step, pitch, excludeId) => {
|
|
14803
15242
|
const active = getActive();
|
|
14804
15243
|
return active.core.getNotes().some(
|
|
14805
|
-
(n) => n.id !== excludeId && n.
|
|
15244
|
+
(n) => n.id !== excludeId && n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
14806
15245
|
);
|
|
14807
15246
|
};
|
|
14808
15247
|
const snapToGrid = (duration) => Math.max(
|
|
@@ -14813,7 +15252,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14813
15252
|
const onGridPointerDown = (event) => {
|
|
14814
15253
|
event.preventDefault();
|
|
14815
15254
|
options.onResumeAudio?.();
|
|
14816
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15255
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14817
15256
|
const active = getActive();
|
|
14818
15257
|
if (activeToolMode === "eraser") {
|
|
14819
15258
|
if (isActiveLocked()) return;
|
|
@@ -14828,7 +15267,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14828
15267
|
selectedOriginal = selectedNotes.map((n) => ({
|
|
14829
15268
|
id: n.id,
|
|
14830
15269
|
startStep: n.startStep,
|
|
14831
|
-
pitch: n.
|
|
15270
|
+
pitch: n.pitchUnits
|
|
14832
15271
|
}));
|
|
14833
15272
|
isSelecting = true;
|
|
14834
15273
|
dragMode = "move";
|
|
@@ -14847,7 +15286,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14847
15286
|
{
|
|
14848
15287
|
id: clicked.id,
|
|
14849
15288
|
startStep: clicked.startStep,
|
|
14850
|
-
pitch: clicked.
|
|
15289
|
+
pitch: clicked.pitchUnits
|
|
14851
15290
|
}
|
|
14852
15291
|
];
|
|
14853
15292
|
isSelecting = true;
|
|
@@ -14865,9 +15304,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
14865
15304
|
hasDragged = false;
|
|
14866
15305
|
const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
|
|
14867
15306
|
if (existing) {
|
|
14868
|
-
playPreview(existing.
|
|
15307
|
+
playPreview(existing.pitchUnits);
|
|
14869
15308
|
const { stepWidth } = renderConfig;
|
|
14870
|
-
const offset = getDrawOffset();
|
|
15309
|
+
const offset = renderer.getDrawOffset();
|
|
14871
15310
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
14872
15311
|
const w = existing.durationSteps * stepWidth;
|
|
14873
15312
|
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
@@ -14878,17 +15317,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14878
15317
|
dragOffsetPitch: 0,
|
|
14879
15318
|
startStep: existing.startStep,
|
|
14880
15319
|
durationSteps: existing.durationSteps,
|
|
14881
|
-
lastPreviewPitch: existing.
|
|
15320
|
+
lastPreviewPitch: existing.pitchUnits
|
|
14882
15321
|
};
|
|
14883
15322
|
} else {
|
|
14884
15323
|
dragState = {
|
|
14885
15324
|
noteId: existing.id,
|
|
14886
15325
|
mode: "move",
|
|
14887
15326
|
dragOffsetStep: step - existing.startStep,
|
|
14888
|
-
dragOffsetPitch: pitch - existing.
|
|
15327
|
+
dragOffsetPitch: pitch - existing.pitchUnits,
|
|
14889
15328
|
startStep: existing.startStep,
|
|
14890
15329
|
durationSteps: existing.durationSteps,
|
|
14891
|
-
lastPreviewPitch: existing.
|
|
15330
|
+
lastPreviewPitch: existing.pitchUnits
|
|
14892
15331
|
};
|
|
14893
15332
|
}
|
|
14894
15333
|
suppressClick = true;
|
|
@@ -14899,14 +15338,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
14899
15338
|
const newStart = snappedStep;
|
|
14900
15339
|
const newEnd = newStart + currentInsertLength;
|
|
14901
15340
|
const overlapping = active.core.getNotes().some(
|
|
14902
|
-
(n) => n.
|
|
15341
|
+
(n) => n.pitchUnits === pitch && newStart < n.startStep + n.durationSteps && newEnd > n.startStep
|
|
14903
15342
|
);
|
|
14904
15343
|
if (!overlapping) {
|
|
14905
15344
|
active.core.addNote(snappedStep, pitch, {
|
|
14906
15345
|
noteLengthSteps: currentInsertLength
|
|
14907
15346
|
});
|
|
14908
15347
|
playPreview(pitch);
|
|
14909
|
-
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.
|
|
15348
|
+
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.pitchUnits === pitch);
|
|
14910
15349
|
if (newNote) {
|
|
14911
15350
|
dragState = {
|
|
14912
15351
|
noteId: newNote.id,
|
|
@@ -14915,7 +15354,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14915
15354
|
dragOffsetPitch: 0,
|
|
14916
15355
|
startStep: newNote.startStep,
|
|
14917
15356
|
durationSteps: newNote.durationSteps,
|
|
14918
|
-
lastPreviewPitch: newNote.
|
|
15357
|
+
lastPreviewPitch: newNote.pitchUnits
|
|
14919
15358
|
};
|
|
14920
15359
|
hasDragged = true;
|
|
14921
15360
|
}
|
|
@@ -14926,7 +15365,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14926
15365
|
const active = getActive();
|
|
14927
15366
|
if (activeToolMode === "pen") {
|
|
14928
15367
|
if (!dragState) return;
|
|
14929
|
-
const { step, pitch } = getGridPosition(event);
|
|
15368
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
14930
15369
|
hasDragged = true;
|
|
14931
15370
|
if (dragState.mode === "move") {
|
|
14932
15371
|
const nextStart = step - dragState.dragOffsetStep;
|
|
@@ -14950,7 +15389,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14950
15389
|
}
|
|
14951
15390
|
if (activeToolMode === "select" && isSelecting && selectionStart) {
|
|
14952
15391
|
ensureAutoScroll(event);
|
|
14953
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15392
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14954
15393
|
if (dragMode === "rect") {
|
|
14955
15394
|
const rect = {
|
|
14956
15395
|
x: Math.min(x2, selectionStart.x),
|
|
@@ -14959,11 +15398,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14959
15398
|
height: Math.abs(y - selectionStart.y)
|
|
14960
15399
|
};
|
|
14961
15400
|
selectionRect = rect;
|
|
14962
|
-
const {
|
|
14963
|
-
|
|
15401
|
+
const {
|
|
15402
|
+
stepWidth,
|
|
15403
|
+
keyHeight,
|
|
15404
|
+
keyCount,
|
|
15405
|
+
pitchRangeStart,
|
|
15406
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15407
|
+
} = renderConfig;
|
|
15408
|
+
const offset = renderer.getDrawOffset();
|
|
14964
15409
|
selectedNotes = active.core.getNotes().filter((note) => {
|
|
14965
15410
|
const logicalX = note.startStep * stepWidth;
|
|
14966
|
-
const yIndex = keyCount - 1 - (note.
|
|
15411
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14967
15412
|
const logicalY = yIndex * keyHeight;
|
|
14968
15413
|
const nx = logicalX - offset.x;
|
|
14969
15414
|
const ny = logicalY - offset.y;
|
|
@@ -14982,7 +15427,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14982
15427
|
const orig = selectedOriginal.find((o) => o.id === note.id);
|
|
14983
15428
|
if (!orig) continue;
|
|
14984
15429
|
const newPitch = orig.pitch + deltaPitch;
|
|
14985
|
-
if (newPitch >=
|
|
15430
|
+
if (newPitch >= PITCH_RANGE_START && newPitch <= PITCH_RANGE_END)
|
|
14986
15431
|
active.core.moveNote(
|
|
14987
15432
|
note.id,
|
|
14988
15433
|
orig.startStep + snappedDelta,
|
|
@@ -15130,22 +15575,23 @@ var mountDAW = (target, options = {}) => {
|
|
|
15130
15575
|
refs.bgOpacityRow.classList.add("dtm-hidden");
|
|
15131
15576
|
}
|
|
15132
15577
|
refs.bgRemoveBtn.classList.toggle("dtm-hidden", !blob2);
|
|
15133
|
-
setBackgroundActive(!!blob2);
|
|
15578
|
+
renderer.setBackgroundActive(!!blob2);
|
|
15134
15579
|
redrawAll();
|
|
15135
15580
|
};
|
|
15136
15581
|
const setupCanvas = () => {
|
|
15137
15582
|
const w = refs.rollContainer.clientWidth || 800;
|
|
15138
15583
|
const h = refs.rollContainer.clientHeight || 450;
|
|
15139
|
-
|
|
15140
|
-
|
|
15584
|
+
renderer?.destroy();
|
|
15585
|
+
renderer = createRenderer(refs.wrapper, w, h, renderConfig);
|
|
15586
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
15141
15587
|
gridCanvas.addEventListener("pointerdown", onGridPointerDown);
|
|
15142
15588
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
15143
15589
|
event.preventDefault();
|
|
15144
15590
|
if (isActiveLocked()) return;
|
|
15145
|
-
const { step, pitch } = getGridPosition(event);
|
|
15591
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15146
15592
|
const active = getActive();
|
|
15147
15593
|
const note = active.core.getNotes().find(
|
|
15148
|
-
(n) => n.
|
|
15594
|
+
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
15149
15595
|
);
|
|
15150
15596
|
if (note) active.core.deleteNoteById(note.id);
|
|
15151
15597
|
});
|
|
@@ -15163,7 +15609,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15163
15609
|
0,
|
|
15164
15610
|
getMaxOffsetX()
|
|
15165
15611
|
);
|
|
15166
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15612
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15167
15613
|
redrawAll();
|
|
15168
15614
|
},
|
|
15169
15615
|
{ passive: false }
|
|
@@ -15173,7 +15619,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15173
15619
|
suppressClick = false;
|
|
15174
15620
|
}
|
|
15175
15621
|
});
|
|
15176
|
-
const headerCanvas = getHeaderCanvas();
|
|
15622
|
+
const headerCanvas = renderer.getHeaderCanvas();
|
|
15177
15623
|
headerCanvas.addEventListener("click", (event) => {
|
|
15178
15624
|
if (playbackState === "playing") return;
|
|
15179
15625
|
const rect = headerCanvas.getBoundingClientRect();
|
|
@@ -15189,11 +15635,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15189
15635
|
}
|
|
15190
15636
|
redrawAll();
|
|
15191
15637
|
});
|
|
15192
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15638
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15193
15639
|
redrawAll();
|
|
15194
15640
|
};
|
|
15195
15641
|
const applyZoomX = () => {
|
|
15196
|
-
const canvas = getGridCanvas();
|
|
15642
|
+
const canvas = renderer.getGridCanvas();
|
|
15197
15643
|
const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
|
|
15198
15644
|
renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
|
|
15199
15645
|
refs.zoomXLabel.textContent = `${zoomX}%`;
|
|
@@ -15202,11 +15648,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15202
15648
|
0,
|
|
15203
15649
|
getMaxOffsetX()
|
|
15204
15650
|
);
|
|
15205
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15651
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15206
15652
|
redrawAll();
|
|
15207
15653
|
};
|
|
15208
15654
|
const applyZoomY = () => {
|
|
15209
|
-
const canvas = getGridCanvas();
|
|
15655
|
+
const canvas = renderer.getGridCanvas();
|
|
15210
15656
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
15211
15657
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
15212
15658
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
@@ -15215,7 +15661,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15215
15661
|
0,
|
|
15216
15662
|
getMaxOffsetY()
|
|
15217
15663
|
);
|
|
15218
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15664
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15219
15665
|
redrawAll();
|
|
15220
15666
|
};
|
|
15221
15667
|
const getViewState = () => ({
|
|
@@ -15227,7 +15673,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15227
15673
|
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
15228
15674
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
15229
15675
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
15230
|
-
options.onPlayNote?.({
|
|
15676
|
+
options.onPlayNote?.({
|
|
15677
|
+
trackId,
|
|
15678
|
+
pitchUnits: pitch,
|
|
15679
|
+
velocity,
|
|
15680
|
+
volume,
|
|
15681
|
+
when,
|
|
15682
|
+
duration
|
|
15683
|
+
});
|
|
15231
15684
|
};
|
|
15232
15685
|
const sequencer = createSequencer({
|
|
15233
15686
|
getTracks: () => trackStates.map((t) => ({
|
|
@@ -15255,7 +15708,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15255
15708
|
},
|
|
15256
15709
|
onTick: (step) => {
|
|
15257
15710
|
currentPlayStep = step;
|
|
15258
|
-
const canvas = getGridCanvas();
|
|
15711
|
+
const canvas = renderer.getGridCanvas();
|
|
15259
15712
|
const visibleSteps = canvas.width / renderConfig.stepWidth;
|
|
15260
15713
|
const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
|
|
15261
15714
|
if (currentPlayStep > threshold) {
|
|
@@ -15265,7 +15718,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15265
15718
|
0,
|
|
15266
15719
|
getMaxOffsetX()
|
|
15267
15720
|
);
|
|
15268
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15721
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15269
15722
|
}
|
|
15270
15723
|
redrawAll();
|
|
15271
15724
|
},
|
|
@@ -15296,7 +15749,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15296
15749
|
(a, b) => a.startStep - b.startStep
|
|
15297
15750
|
);
|
|
15298
15751
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
15299
|
-
const semis = (lt.octave ?? 0) *
|
|
15752
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
15300
15753
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
15301
15754
|
const notes = [];
|
|
15302
15755
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -15304,7 +15757,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15304
15757
|
if (n.startStep < fromStep) continue;
|
|
15305
15758
|
notes.push({
|
|
15306
15759
|
syllable: lt.syllables[i2],
|
|
15307
|
-
pitch: n.
|
|
15760
|
+
pitch: n.pitchUnits + semis,
|
|
15308
15761
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
15309
15762
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
15310
15763
|
});
|
|
@@ -15347,13 +15800,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
15347
15800
|
}
|
|
15348
15801
|
}
|
|
15349
15802
|
if (playbackState !== "paused") {
|
|
15350
|
-
const canvas = getGridCanvas();
|
|
15803
|
+
const canvas = renderer.getGridCanvas();
|
|
15351
15804
|
currentOffsetX = clamp5(
|
|
15352
15805
|
playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
|
|
15353
15806
|
0,
|
|
15354
15807
|
getMaxOffsetX()
|
|
15355
15808
|
);
|
|
15356
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15809
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15357
15810
|
}
|
|
15358
15811
|
playbackState = "playing";
|
|
15359
15812
|
sequencer.start(fromStep);
|
|
@@ -16340,6 +16793,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16340
16793
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16341
16794
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16342
16795
|
mode,
|
|
16796
|
+
edo: renderConfig.edo,
|
|
16343
16797
|
trackInstruments: trackInstMeta,
|
|
16344
16798
|
trackCompression: trackCompMeta,
|
|
16345
16799
|
trackWidth: trackWidthMeta,
|
|
@@ -16368,6 +16822,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16368
16822
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16369
16823
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16370
16824
|
mode,
|
|
16825
|
+
edo: renderConfig.edo,
|
|
16371
16826
|
trackInstruments: trackInstMeta,
|
|
16372
16827
|
trackCompression: trackCompMeta,
|
|
16373
16828
|
trackWidth: trackWidthMeta,
|
|
@@ -16507,19 +16962,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
16507
16962
|
}
|
|
16508
16963
|
}
|
|
16509
16964
|
if (candidateNotes.length === 0) return null;
|
|
16510
|
-
const sum = candidateNotes.reduce((acc, note) => acc + note.
|
|
16965
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitchUnits, 0);
|
|
16511
16966
|
return Math.round(sum / candidateNotes.length);
|
|
16512
16967
|
};
|
|
16513
16968
|
const centerPitch = (pitch) => {
|
|
16514
|
-
const canvas = getGridCanvas();
|
|
16515
|
-
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
16969
|
+
const canvas = renderer.getGridCanvas();
|
|
16970
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE);
|
|
16516
16971
|
const logicalY = yIndex * renderConfig.keyHeight;
|
|
16517
16972
|
currentOffsetY = clamp5(
|
|
16518
16973
|
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
16519
16974
|
0,
|
|
16520
16975
|
getMaxOffsetY()
|
|
16521
16976
|
);
|
|
16522
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16977
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16523
16978
|
};
|
|
16524
16979
|
const clearAll = () => {
|
|
16525
16980
|
for (const t of trackStates) {
|
|
@@ -16577,10 +17032,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
16577
17032
|
if (!meta.drumFont) applyDrumPatternFont(meta.drum);
|
|
16578
17033
|
}
|
|
16579
17034
|
if (meta.volume !== void 0) {
|
|
16580
|
-
|
|
16581
|
-
refs.masterVolume.value = String(meta.volume);
|
|
16582
|
-
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
17035
|
+
applyMasterVolume(meta.volume);
|
|
16583
17036
|
}
|
|
17037
|
+
applyEdo(meta.edo ?? 12);
|
|
17038
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16584
17039
|
if (meta.drumVolume !== void 0) {
|
|
16585
17040
|
drumVolume = meta.drumVolume;
|
|
16586
17041
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -16745,7 +17200,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16745
17200
|
if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
|
|
16746
17201
|
const t = trackStates[p.trackIndex];
|
|
16747
17202
|
if (!t) continue;
|
|
16748
|
-
t.core.addNote(p.startStep, p.
|
|
17203
|
+
t.core.addNote(p.startStep, p.pitchUnits, {
|
|
16749
17204
|
noteLengthSteps: p.durationSteps,
|
|
16750
17205
|
velocity: DEFAULT_VELOCITY
|
|
16751
17206
|
});
|
|
@@ -16762,7 +17217,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16762
17217
|
if (firstPitch !== null) {
|
|
16763
17218
|
centerPitch(firstPitch);
|
|
16764
17219
|
} else {
|
|
16765
|
-
centerPitch(48);
|
|
17220
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16766
17221
|
}
|
|
16767
17222
|
redrawAll();
|
|
16768
17223
|
updateTrackPanel();
|
|
@@ -16780,6 +17235,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16780
17235
|
const chordTrack = trackStates.find((t) => t.config.id === "chord");
|
|
16781
17236
|
if (!chordTrack) return;
|
|
16782
17237
|
const placements = buildChordPlacements({
|
|
17238
|
+
// 和音は五度連鎖経由で音律の格子へ写す(31平均律では長3度が10度になる)
|
|
17239
|
+
edo: renderConfig.edo,
|
|
16783
17240
|
chordStr: active.savedChordInput,
|
|
16784
17241
|
patternType: active.savedChordPattern,
|
|
16785
17242
|
rootShift: active.savedChordRoot,
|
|
@@ -16789,7 +17246,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16789
17246
|
chordTrack.core.clearNotesWithoutHistory();
|
|
16790
17247
|
chordTrack.core.beginBatch();
|
|
16791
17248
|
for (const p of placements) {
|
|
16792
|
-
chordTrack.core.addNote(p.startStep, p.
|
|
17249
|
+
chordTrack.core.addNote(p.startStep, p.pitchUnits, {
|
|
16793
17250
|
noteLengthSteps: Math.max(1, p.durationSteps),
|
|
16794
17251
|
velocity: p.velocity
|
|
16795
17252
|
});
|
|
@@ -16827,6 +17284,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16827
17284
|
active.vocalOctaveUnison = "none";
|
|
16828
17285
|
} else {
|
|
16829
17286
|
clearAll();
|
|
17287
|
+
applyEdo(12);
|
|
17288
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16830
17289
|
for (const t of trackStates) t.core.setLoadMode(true);
|
|
16831
17290
|
for (const t of trackStates) {
|
|
16832
17291
|
t.lyrics = "";
|
|
@@ -16853,7 +17312,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16853
17312
|
if (applyActiveOnly && p.trackId !== activeTrackId) continue;
|
|
16854
17313
|
const t = trackStates.find((ts) => ts.config.id === p.trackId);
|
|
16855
17314
|
if (!t) continue;
|
|
16856
|
-
t.core.addNote(p.startStep, p.pitch, {
|
|
17315
|
+
t.core.addNote(p.startStep, snapToEdoGrid(pitchV1ToUnits(p.pitch)), {
|
|
16857
17316
|
noteLengthSteps: p.durationSteps,
|
|
16858
17317
|
velocity: p.velocity
|
|
16859
17318
|
});
|
|
@@ -16870,7 +17329,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16870
17329
|
if (firstPitch !== null) {
|
|
16871
17330
|
centerPitch(firstPitch);
|
|
16872
17331
|
} else {
|
|
16873
|
-
centerPitch(48);
|
|
17332
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16874
17333
|
}
|
|
16875
17334
|
redrawAll();
|
|
16876
17335
|
updateTrackPanel();
|
|
@@ -16934,18 +17393,21 @@ var mountDAW = (target, options = {}) => {
|
|
|
16934
17393
|
}
|
|
16935
17394
|
}, 30);
|
|
16936
17395
|
};
|
|
16937
|
-
const showConfirmModal = (message) => new Promise((resolve) => {
|
|
17396
|
+
const showConfirmModal = (message, opts) => new Promise((resolve) => {
|
|
17397
|
+
const title = opts?.title ?? "\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D";
|
|
17398
|
+
const yes = opts?.yes ?? "\u306F\u3044\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u308B\uFF09";
|
|
17399
|
+
const no = opts?.no ?? "\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09";
|
|
16938
17400
|
const overlay = document.createElement("div");
|
|
16939
17401
|
overlay.className = "dtm-modal-overlay";
|
|
16940
17402
|
overlay.innerHTML = `
|
|
16941
17403
|
<div class="dtm-modal">
|
|
16942
17404
|
<div class="dtm-modal-header">
|
|
16943
|
-
<span class="dtm-modal-title"
|
|
17405
|
+
<span class="dtm-modal-title">${title}</span>
|
|
16944
17406
|
</div>
|
|
16945
17407
|
<div class="dtm-modal-body"><p>${message}</p></div>
|
|
16946
17408
|
<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"
|
|
17409
|
+
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no">${no}</button>
|
|
17410
|
+
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes">${yes}</button>
|
|
16949
17411
|
</div>
|
|
16950
17412
|
</div>`;
|
|
16951
17413
|
const close = (result) => {
|
|
@@ -17053,10 +17515,36 @@ var mountDAW = (target, options = {}) => {
|
|
|
17053
17515
|
});
|
|
17054
17516
|
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
17055
17517
|
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
17518
|
+
refs.edoSelect.addEventListener("change", async () => {
|
|
17519
|
+
const next = Number.parseInt(refs.edoSelect.value, 10) === 31 ? 31 : 12;
|
|
17520
|
+
const prev = renderConfig.edo ?? 12;
|
|
17521
|
+
if (next === prev) return;
|
|
17522
|
+
const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
|
|
17523
|
+
if (next === 12 && prev === 31 && hasNotes) {
|
|
17524
|
+
const okToDrop = await showConfirmModal(
|
|
17525
|
+
"31\u5E73\u5747\u5F8B\u304B\u308912\u5E73\u5747\u5F8B\u3078\u623B\u3059\u3068\u3001\u97F3\u7B26\u304C\u6700\u592748\u30BB\u30F3\u30C8\uFF08\u534A\u97F3\u306E\u7D04\u534A\u5206\uFF09\u52D5\u304D\u307E\u3059\u3002<br>12\u5E73\u5747\u5F8B\u306B\u7121\u3044\u97F3\uFF08\u4E2D\u7ACB3\u5EA6\u306A\u3069\uFF09\u306F\u8FD1\u3044\u97F3\u306B\u6F70\u308C\u3001<strong>\u5143\u306B\u306F\u623B\u305B\u307E\u305B\u3093</strong>\u3002<br>\u5207\u308A\u66FF\u3048\u307E\u3059\u304B\uFF1F",
|
|
17526
|
+
{
|
|
17527
|
+
title: "\u97F3\u5F8B\u306E\u78BA\u8A8D",
|
|
17528
|
+
yes: "\u5207\u308A\u66FF\u3048\u308B",
|
|
17529
|
+
no: "\u3084\u3081\u308B"
|
|
17530
|
+
}
|
|
17531
|
+
);
|
|
17532
|
+
if (!okToDrop) {
|
|
17533
|
+
refs.edoSelect.value = String(prev);
|
|
17534
|
+
return;
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
applyEdo(next, { snap: true });
|
|
17538
|
+
const first = getFirstDetectedPitch();
|
|
17539
|
+
centerPitch(first ?? pitchV1ToUnits(48));
|
|
17540
|
+
redrawAll();
|
|
17541
|
+
updateUndoRedo();
|
|
17542
|
+
});
|
|
17543
|
+
refs.edoInfoBtn.addEventListener("click", () => {
|
|
17544
|
+
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
17545
|
+
});
|
|
17056
17546
|
refs.masterVolume.addEventListener("input", () => {
|
|
17057
|
-
|
|
17058
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17059
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17547
|
+
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
17060
17548
|
});
|
|
17061
17549
|
refs.masterComp.addEventListener("input", () => {
|
|
17062
17550
|
masterCompression = Number.parseInt(refs.masterComp.value, 10) || 0;
|
|
@@ -17120,10 +17608,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17120
17608
|
const scale = targetPeak / observedPeakMax;
|
|
17121
17609
|
const suggested = clamp5(Math.round(masterVolume * scale), 10, 100);
|
|
17122
17610
|
if (suggested !== masterVolume) {
|
|
17123
|
-
|
|
17124
|
-
refs.masterVolume.value = String(masterVolume);
|
|
17125
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17126
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17611
|
+
applyMasterVolume(suggested);
|
|
17127
17612
|
}
|
|
17128
17613
|
observedPeakMax = 0;
|
|
17129
17614
|
observedPlayMs = 0;
|
|
@@ -17283,7 +17768,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
17283
17768
|
generateRandomPattern(getActive().core, {
|
|
17284
17769
|
stepsPerBar: renderConfig.stepsPerBar,
|
|
17285
17770
|
startStep: playStartStep,
|
|
17286
|
-
pitchRangeStart: renderConfig.pitchRangeStart
|
|
17771
|
+
pitchRangeStart: renderConfig.pitchRangeStart,
|
|
17772
|
+
edo: renderConfig.edo
|
|
17287
17773
|
});
|
|
17288
17774
|
redrawAll();
|
|
17289
17775
|
});
|
|
@@ -17620,7 +18106,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17620
18106
|
id: 0,
|
|
17621
18107
|
startStep: p.startStep,
|
|
17622
18108
|
durationSteps: p.durationSteps,
|
|
17623
|
-
|
|
18109
|
+
pitchUnits: snapToEdoGrid(pitchV1ToUnits(p.pitch)),
|
|
17624
18110
|
velocity: p.velocity
|
|
17625
18111
|
}));
|
|
17626
18112
|
const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
|
|
@@ -18075,10 +18561,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18075
18561
|
const newStart = playStartStep + (note.startStep - minStart);
|
|
18076
18562
|
const newEnd = newStart + note.durationSteps;
|
|
18077
18563
|
const overlap = notes.some(
|
|
18078
|
-
(ex) => ex.
|
|
18564
|
+
(ex) => ex.pitchUnits === note.pitchUnits && newStart < ex.startStep + ex.durationSteps && newEnd > ex.startStep
|
|
18079
18565
|
);
|
|
18080
18566
|
if (!overlap)
|
|
18081
|
-
core.addNote(newStart, note.
|
|
18567
|
+
core.addNote(newStart, note.pitchUnits, {
|
|
18082
18568
|
noteLengthSteps: note.durationSteps,
|
|
18083
18569
|
velocity: note.velocity
|
|
18084
18570
|
});
|
|
@@ -18151,13 +18637,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
18151
18637
|
pausedPlayStep = step;
|
|
18152
18638
|
currentPlayStep = step;
|
|
18153
18639
|
playbackState = "paused";
|
|
18154
|
-
const canvas = getGridCanvas();
|
|
18640
|
+
const canvas = renderer.getGridCanvas();
|
|
18155
18641
|
currentOffsetX = clamp5(
|
|
18156
18642
|
step * renderConfig.stepWidth - canvas.width * 0.5,
|
|
18157
18643
|
0,
|
|
18158
18644
|
getMaxOffsetX()
|
|
18159
18645
|
);
|
|
18160
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18646
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18161
18647
|
updateTransport();
|
|
18162
18648
|
redrawAll();
|
|
18163
18649
|
};
|
|
@@ -18225,16 +18711,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18225
18711
|
forcePauseAt,
|
|
18226
18712
|
setLoading,
|
|
18227
18713
|
setMasterVolume: (volume) => {
|
|
18228
|
-
|
|
18229
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18230
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18231
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18714
|
+
applyMasterVolume(volume);
|
|
18232
18715
|
},
|
|
18233
18716
|
setVolume: (volume) => {
|
|
18234
|
-
|
|
18235
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18236
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18237
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18717
|
+
applyMasterVolume(volume);
|
|
18238
18718
|
},
|
|
18239
18719
|
setDrumVolume: (volume) => {
|
|
18240
18720
|
drumVolume = clamp5(volume, 0, 100);
|
|
@@ -18277,15 +18757,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
18277
18757
|
suppressPatch = true;
|
|
18278
18758
|
track.core.beginBatch();
|
|
18279
18759
|
for (const n of added) {
|
|
18280
|
-
const existing = track.core.getNotes().find(
|
|
18760
|
+
const existing = track.core.getNotes().find(
|
|
18761
|
+
(e) => e.startStep === n.startStep && e.pitchUnits === n.pitchUnits
|
|
18762
|
+
);
|
|
18281
18763
|
if (existing) track.core.deleteNoteById(existing.id);
|
|
18282
|
-
track.core.addNote(n.startStep, n.
|
|
18764
|
+
track.core.addNote(n.startStep, n.pitchUnits, {
|
|
18283
18765
|
noteLengthSteps: n.durationSteps,
|
|
18284
18766
|
velocity: n.velocity
|
|
18285
18767
|
});
|
|
18286
18768
|
}
|
|
18287
18769
|
for (const r of removed) {
|
|
18288
|
-
const note = track.core.getNotes().find(
|
|
18770
|
+
const note = track.core.getNotes().find(
|
|
18771
|
+
(n) => n.startStep === r.startStep && n.pitchUnits === r.pitchUnits
|
|
18772
|
+
);
|
|
18289
18773
|
if (note) track.core.deleteNoteById(note.id);
|
|
18290
18774
|
}
|
|
18291
18775
|
track.core.endBatch();
|
|
@@ -18327,9 +18811,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
18327
18811
|
if (t.config.id === activeTrackId) updateTrackPanel();
|
|
18328
18812
|
},
|
|
18329
18813
|
noteToCanvas: (step, pitch) => {
|
|
18330
|
-
const canvas = getGridCanvas();
|
|
18814
|
+
const canvas = renderer.getGridCanvas();
|
|
18331
18815
|
const x2 = step * renderConfig.stepWidth - currentOffsetX;
|
|
18332
|
-
const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
|
|
18816
|
+
const y = (renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - currentOffsetY;
|
|
18333
18817
|
const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
|
|
18334
18818
|
let side = null;
|
|
18335
18819
|
if (!onScreen) {
|
|
@@ -18393,7 +18877,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18393
18877
|
id: id++,
|
|
18394
18878
|
startStep: p.startStep,
|
|
18395
18879
|
durationSteps: p.durationSteps,
|
|
18396
|
-
|
|
18880
|
+
pitchUnits: p.pitchUnits,
|
|
18397
18881
|
velocity: p.velocity
|
|
18398
18882
|
}));
|
|
18399
18883
|
return {
|
|
@@ -18419,7 +18903,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18419
18903
|
(a, b) => a.startStep - b.startStep
|
|
18420
18904
|
);
|
|
18421
18905
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
18422
|
-
const semis = (lt.octave ?? 0) *
|
|
18906
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
18423
18907
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
18424
18908
|
const notes = [];
|
|
18425
18909
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -18427,7 +18911,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18427
18911
|
if (n.startStep < fromStep) continue;
|
|
18428
18912
|
notes.push({
|
|
18429
18913
|
syllable: lt.syllables[i2],
|
|
18430
|
-
pitch: n.
|
|
18914
|
+
pitch: n.pitchUnits + semis,
|
|
18431
18915
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
18432
18916
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
18433
18917
|
});
|
|
@@ -18615,24 +19099,28 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18615
19099
|
config,
|
|
18616
19100
|
noteLengthSteps = 1
|
|
18617
19101
|
} = options;
|
|
18618
|
-
|
|
19102
|
+
const renderer = createRenderer(mountTarget, width, height, config);
|
|
18619
19103
|
let currentNoteLengthSteps = noteLengthSteps;
|
|
18620
19104
|
let selectionRect = null;
|
|
18621
19105
|
let isSelecting = false;
|
|
18622
19106
|
let selectionStart = null;
|
|
18623
19107
|
let selectedNotes = [];
|
|
18624
19108
|
let copiedNotes = [];
|
|
18625
|
-
const core = new MMLCore(
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
19109
|
+
const core = new MMLCore(
|
|
19110
|
+
{
|
|
19111
|
+
onMMLGenerated: handlers.onMMLGenerated,
|
|
19112
|
+
onNotesChanged: (notes) => {
|
|
19113
|
+
handlers.onNotesChanged(notes);
|
|
19114
|
+
}
|
|
19115
|
+
},
|
|
19116
|
+
80,
|
|
19117
|
+
() => config
|
|
19118
|
+
);
|
|
18631
19119
|
const getAddNoteOptions = () => ({
|
|
18632
19120
|
noteLengthSteps: currentNoteLengthSteps
|
|
18633
19121
|
});
|
|
18634
19122
|
let suppressClick = false;
|
|
18635
|
-
onClick((step, pitch) => {
|
|
19123
|
+
renderer.onClick((step, pitch) => {
|
|
18636
19124
|
if (suppressClick) {
|
|
18637
19125
|
suppressClick = false;
|
|
18638
19126
|
return;
|
|
@@ -18644,7 +19132,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18644
19132
|
} else if (mode === "eraser") {
|
|
18645
19133
|
const notes = core.getNotes();
|
|
18646
19134
|
const note = notes.find(
|
|
18647
|
-
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.
|
|
19135
|
+
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.pitchUnits === pitch
|
|
18648
19136
|
);
|
|
18649
19137
|
if (note) {
|
|
18650
19138
|
core.deleteNoteById(note.id);
|
|
@@ -18652,17 +19140,23 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18652
19140
|
}
|
|
18653
19141
|
}
|
|
18654
19142
|
});
|
|
18655
|
-
const gridCanvas = getGridCanvas();
|
|
19143
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
18656
19144
|
const resizeHandleWidth = 6;
|
|
18657
19145
|
let dragState = null;
|
|
18658
19146
|
let hasDragged = false;
|
|
18659
19147
|
let lastPreviewPitch = null;
|
|
18660
19148
|
const findNoteAtPosition = (x2, y) => {
|
|
18661
|
-
const {
|
|
18662
|
-
|
|
19149
|
+
const {
|
|
19150
|
+
stepWidth,
|
|
19151
|
+
keyHeight,
|
|
19152
|
+
keyCount,
|
|
19153
|
+
pitchRangeStart,
|
|
19154
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19155
|
+
} = renderer.getRenderConfig();
|
|
19156
|
+
const offset = renderer.getDrawOffset();
|
|
18663
19157
|
for (const note of core.getNotes()) {
|
|
18664
19158
|
const logicalX = note.startStep * stepWidth;
|
|
18665
|
-
const yIndex = keyCount - 1 - (note.
|
|
19159
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18666
19160
|
const logicalY = yIndex * keyHeight;
|
|
18667
19161
|
const w = note.durationSteps * stepWidth;
|
|
18668
19162
|
const h = keyHeight;
|
|
@@ -18676,7 +19170,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18676
19170
|
};
|
|
18677
19171
|
const handlePointerMove = (e) => {
|
|
18678
19172
|
if (core.getToolMode() === "select" && isSelecting && selectionStart) {
|
|
18679
|
-
const { x: x2, y } = getGridPosition(e);
|
|
19173
|
+
const { x: x2, y } = renderer.getGridPosition(e);
|
|
18680
19174
|
const minX = Math.min(x2, selectionStart.x);
|
|
18681
19175
|
const minY = Math.min(y, selectionStart.y);
|
|
18682
19176
|
const width2 = Math.abs(x2 - selectionStart.x);
|
|
@@ -18688,7 +19182,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18688
19182
|
}
|
|
18689
19183
|
if (!dragState) return;
|
|
18690
19184
|
hasDragged = true;
|
|
18691
|
-
const { step, pitch } = getGridPosition(e);
|
|
19185
|
+
const { step, pitch } = renderer.getGridPosition(e);
|
|
18692
19186
|
if (dragState.mode === "move") {
|
|
18693
19187
|
if (dragState.selectedNotes && dragState.selectedNotes.length > 0) {
|
|
18694
19188
|
const noteId = dragState.noteId;
|
|
@@ -18697,10 +19191,10 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18697
19191
|
const nextStart2 = step - dragState.dragOffsetStep;
|
|
18698
19192
|
const nextPitch2 = pitch - dragState.dragOffsetPitch;
|
|
18699
19193
|
const stepDelta = nextStart2 - baseNote.startStep;
|
|
18700
|
-
const pitchDelta = nextPitch2 - baseNote.
|
|
19194
|
+
const pitchDelta = nextPitch2 - baseNote.pitchUnits;
|
|
18701
19195
|
for (const note of dragState.selectedNotes) {
|
|
18702
19196
|
const newStart = note.startStep + stepDelta;
|
|
18703
|
-
const newPitch = note.
|
|
19197
|
+
const newPitch = note.pitchUnits + pitchDelta;
|
|
18704
19198
|
core.moveNote(note.id, newStart, newPitch);
|
|
18705
19199
|
}
|
|
18706
19200
|
if (options.onPreviewSound && pitch !== lastPreviewPitch) {
|
|
@@ -18741,7 +19235,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18741
19235
|
}
|
|
18742
19236
|
};
|
|
18743
19237
|
gridCanvas.addEventListener("pointerdown", (e) => {
|
|
18744
|
-
const { x: x2, y, step, pitch } = getGridPosition(e);
|
|
19238
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(e);
|
|
18745
19239
|
const currentMode = core.getToolMode();
|
|
18746
19240
|
if (currentMode === "select") {
|
|
18747
19241
|
const clickedNote = findNoteAtPosition(x2, y);
|
|
@@ -18752,7 +19246,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18752
19246
|
noteId: clickedNote.id,
|
|
18753
19247
|
mode: "move",
|
|
18754
19248
|
dragOffsetStep: step - clickedNote.startStep,
|
|
18755
|
-
dragOffsetPitch: pitch - clickedNote.
|
|
19249
|
+
dragOffsetPitch: pitch - clickedNote.pitchUnits,
|
|
18756
19250
|
startStep: clickedNote.startStep,
|
|
18757
19251
|
selectedNotes: notesInRect
|
|
18758
19252
|
// 複数選択ノートを保存
|
|
@@ -18770,10 +19264,16 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18770
19264
|
}
|
|
18771
19265
|
const note = findNoteAtPosition(x2, y);
|
|
18772
19266
|
if (!note) return;
|
|
18773
|
-
const {
|
|
18774
|
-
|
|
19267
|
+
const {
|
|
19268
|
+
stepWidth,
|
|
19269
|
+
keyHeight,
|
|
19270
|
+
keyCount,
|
|
19271
|
+
pitchRangeStart,
|
|
19272
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19273
|
+
} = renderer.getRenderConfig();
|
|
19274
|
+
const offset = renderer.getDrawOffset();
|
|
18775
19275
|
const logicalX = note.startStep * stepWidth;
|
|
18776
|
-
const yIndex = keyCount - 1 - (note.
|
|
19276
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18777
19277
|
const logicalY = yIndex * keyHeight;
|
|
18778
19278
|
const renderX = logicalX - offset.x;
|
|
18779
19279
|
const renderY = logicalY - offset.y;
|
|
@@ -18792,7 +19292,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18792
19292
|
noteId: note.id,
|
|
18793
19293
|
mode: "move",
|
|
18794
19294
|
dragOffsetStep: step - note.startStep,
|
|
18795
|
-
dragOffsetPitch: pitch - note.
|
|
19295
|
+
dragOffsetPitch: pitch - note.pitchUnits,
|
|
18796
19296
|
startStep: note.startStep
|
|
18797
19297
|
};
|
|
18798
19298
|
});
|
|
@@ -18803,41 +19303,47 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18803
19303
|
"wheel",
|
|
18804
19304
|
(e) => {
|
|
18805
19305
|
e.preventDefault();
|
|
18806
|
-
const configValues = getRenderConfig();
|
|
19306
|
+
const configValues = renderer.getRenderConfig();
|
|
18807
19307
|
const gridHeight = gridCanvas.height;
|
|
18808
19308
|
const maxOffsetY = Math.max(
|
|
18809
19309
|
0,
|
|
18810
19310
|
configValues.keyCount * configValues.keyHeight - gridHeight
|
|
18811
19311
|
);
|
|
18812
|
-
const currentOffset = getDrawOffset();
|
|
19312
|
+
const currentOffset = renderer.getDrawOffset();
|
|
18813
19313
|
const nextOffsetY = Math.min(
|
|
18814
19314
|
Math.max(currentOffset.y + e.deltaY, 0),
|
|
18815
19315
|
maxOffsetY
|
|
18816
19316
|
);
|
|
18817
|
-
setDrawOffset(currentOffset.x, nextOffsetY);
|
|
18818
|
-
drawGrid();
|
|
18819
|
-
drawNotes(core.getNotes());
|
|
19317
|
+
renderer.setDrawOffset(currentOffset.x, nextOffsetY);
|
|
19318
|
+
renderer.drawGrid();
|
|
19319
|
+
renderer.drawNotes(core.getNotes());
|
|
18820
19320
|
},
|
|
18821
19321
|
{ passive: false }
|
|
18822
19322
|
);
|
|
18823
19323
|
const redraw = () => {
|
|
18824
|
-
drawGrid();
|
|
18825
|
-
drawNotes(core.getNotes());
|
|
19324
|
+
renderer.drawGrid();
|
|
19325
|
+
renderer.drawNotes(core.getNotes());
|
|
18826
19326
|
if (core.getToolMode() === "select") {
|
|
18827
|
-
drawSelectionRect(selectionRect);
|
|
19327
|
+
renderer.drawSelectionRect(selectionRect);
|
|
18828
19328
|
if (selectedNotes.length > 0) {
|
|
18829
19329
|
const selectedIds = new Set(selectedNotes.map((n) => n.id));
|
|
18830
|
-
drawSelectedNotes(core.getNotes(), selectedIds);
|
|
19330
|
+
renderer.drawSelectedNotes(core.getNotes(), selectedIds);
|
|
18831
19331
|
}
|
|
18832
19332
|
}
|
|
18833
19333
|
};
|
|
18834
19334
|
const getNotesInRect = (rect) => {
|
|
18835
|
-
const {
|
|
18836
|
-
|
|
19335
|
+
const {
|
|
19336
|
+
stepWidth,
|
|
19337
|
+
keyHeight,
|
|
19338
|
+
keyCount,
|
|
19339
|
+
pitchRangeStart,
|
|
19340
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19341
|
+
} = renderer.getRenderConfig();
|
|
19342
|
+
const offset = renderer.getDrawOffset();
|
|
18837
19343
|
const notes = [];
|
|
18838
19344
|
for (const note of core.getNotes()) {
|
|
18839
19345
|
const logicalX = note.startStep * stepWidth;
|
|
18840
|
-
const yIndex = keyCount - 1 - (note.
|
|
19346
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18841
19347
|
const logicalY = yIndex * keyHeight;
|
|
18842
19348
|
const noteRect = {
|
|
18843
19349
|
x: logicalX - offset.x,
|
|
@@ -18884,7 +19390,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18884
19390
|
const minStart = Math.min(...copiedNotes.map((n) => n.startStep));
|
|
18885
19391
|
copiedNotes.forEach((note) => {
|
|
18886
19392
|
const newStep = startStep + (note.startStep - minStart);
|
|
18887
|
-
core.addNote(newStep, note.
|
|
19393
|
+
core.addNote(newStep, note.pitchUnits, {
|
|
18888
19394
|
noteLengthSteps: note.durationSteps,
|
|
18889
19395
|
velocity: note.velocity
|
|
18890
19396
|
});
|
|
@@ -19725,10 +20231,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
19725
20231
|
);
|
|
19726
20232
|
}
|
|
19727
20233
|
if (!sfInst) return;
|
|
20234
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
19728
20235
|
sfInst.play({
|
|
19729
20236
|
ctx: audioCtx,
|
|
19730
20237
|
destination: getChannelStrip(e.trackId).input,
|
|
19731
|
-
pitch:
|
|
20238
|
+
pitch: midi,
|
|
20239
|
+
detuneCents,
|
|
19732
20240
|
volume: e.volume,
|
|
19733
20241
|
velocity: e.velocity,
|
|
19734
20242
|
when: e.when,
|
|
@@ -20044,10 +20552,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20044
20552
|
);
|
|
20045
20553
|
}
|
|
20046
20554
|
if (!sfInst) return;
|
|
20555
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20047
20556
|
sfInst.play({
|
|
20048
20557
|
ctx: audioCtx,
|
|
20049
20558
|
destination: getChannelStrip(e.trackId).input,
|
|
20050
|
-
pitch:
|
|
20559
|
+
pitch: midi,
|
|
20560
|
+
detuneCents,
|
|
20051
20561
|
volume: e.volume,
|
|
20052
20562
|
velocity: e.velocity,
|
|
20053
20563
|
when: e.when,
|
|
@@ -20133,10 +20643,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20133
20643
|
);
|
|
20134
20644
|
}
|
|
20135
20645
|
if (!sfInst) return;
|
|
20646
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20136
20647
|
sfInst.play({
|
|
20137
20648
|
ctx: audioCtx,
|
|
20138
20649
|
destination: getChannelStrip(e.trackId).input,
|
|
20139
|
-
pitch:
|
|
20650
|
+
pitch: midi,
|
|
20651
|
+
detuneCents,
|
|
20140
20652
|
volume: e.volume,
|
|
20141
20653
|
velocity: e.velocity,
|
|
20142
20654
|
when: e.when,
|
|
@@ -20215,10 +20727,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20215
20727
|
);
|
|
20216
20728
|
}
|
|
20217
20729
|
if (!sfInst) return;
|
|
20730
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20218
20731
|
sfInst.play({
|
|
20219
20732
|
ctx: audioCtx,
|
|
20220
20733
|
destination: getChannelStrip(e.trackId).input,
|
|
20221
|
-
pitch:
|
|
20734
|
+
pitch: midi,
|
|
20735
|
+
detuneCents,
|
|
20222
20736
|
volume: e.volume,
|
|
20223
20737
|
velocity: e.velocity,
|
|
20224
20738
|
when: e.when,
|
|
@@ -20255,10 +20769,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20255
20769
|
sfInst = resolveSoundFont(defaultPreset, role, "simple");
|
|
20256
20770
|
}
|
|
20257
20771
|
if (!sfInst) return;
|
|
20772
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20258
20773
|
sfInst.play({
|
|
20259
20774
|
ctx: audioCtx,
|
|
20260
20775
|
destination: getChannelStrip(e.trackId).input,
|
|
20261
|
-
pitch:
|
|
20776
|
+
pitch: midi,
|
|
20777
|
+
detuneCents,
|
|
20262
20778
|
volume: e.volume,
|
|
20263
20779
|
velocity: e.velocity,
|
|
20264
20780
|
when: e.when,
|
|
@@ -20281,10 +20797,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20281
20797
|
if (audioCtx.state === "suspended") {
|
|
20282
20798
|
await audioCtx.resume();
|
|
20283
20799
|
}
|
|
20800
|
+
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
20284
20801
|
sfInst.play({
|
|
20285
20802
|
ctx: audioCtx,
|
|
20286
20803
|
destination: masterGain,
|
|
20287
|
-
pitch:
|
|
20804
|
+
pitch: midi,
|
|
20805
|
+
detuneCents,
|
|
20288
20806
|
volume: vol / 100,
|
|
20289
20807
|
when: 0,
|
|
20290
20808
|
duration: dur
|
|
@@ -20304,7 +20822,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20304
20822
|
// 伴奏トラック
|
|
20305
20823
|
startStep: p.startStep,
|
|
20306
20824
|
durationSteps: p.durationSteps,
|
|
20307
|
-
|
|
20825
|
+
pitchUnits: p.pitchUnits,
|
|
20308
20826
|
velocity: p.velocity
|
|
20309
20827
|
}));
|
|
20310
20828
|
const playerPreset = defaultPreset;
|
|
@@ -20312,10 +20830,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20312
20830
|
const playPlayerNote = (e) => {
|
|
20313
20831
|
const sfInst = resolveSoundFont(playerPreset, "chord");
|
|
20314
20832
|
if (!sfInst) return;
|
|
20833
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20315
20834
|
sfInst.play({
|
|
20316
20835
|
ctx: audioCtx,
|
|
20317
20836
|
destination: getChannelStrip("chord").input,
|
|
20318
|
-
pitch:
|
|
20837
|
+
pitch: midi,
|
|
20838
|
+
detuneCents,
|
|
20319
20839
|
volume: e.volume,
|
|
20320
20840
|
velocity: e.velocity,
|
|
20321
20841
|
when: e.when,
|
|
@@ -20453,8 +20973,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20453
20973
|
};
|
|
20454
20974
|
};
|
|
20455
20975
|
export {
|
|
20976
|
+
A4_HZ,
|
|
20977
|
+
A4_UNITS,
|
|
20978
|
+
CENTS_PER_UNIT,
|
|
20456
20979
|
DAW_CSS,
|
|
20457
20980
|
DEFAULT_BPM,
|
|
20981
|
+
DEFAULT_EDO,
|
|
20458
20982
|
DEFAULT_GATE,
|
|
20459
20983
|
DEFAULT_PAN,
|
|
20460
20984
|
DEFAULT_PLAYBACK_VELOCITY,
|
|
@@ -20464,21 +20988,30 @@ export {
|
|
|
20464
20988
|
DRUM_FONT,
|
|
20465
20989
|
DRUM_KEYS,
|
|
20466
20990
|
DRUM_PATTERNS,
|
|
20991
|
+
EDO31_NAMES,
|
|
20467
20992
|
GM_INSTRUMENT_NAMES,
|
|
20468
20993
|
INSTRUMENT_PRESETS,
|
|
20994
|
+
KEY_COUNT,
|
|
20469
20995
|
KOE_BASE_URL,
|
|
20470
20996
|
KOE_VOICEBANKS,
|
|
20471
20997
|
KOE_VOICEBANK_LABELS,
|
|
20472
20998
|
KOE_VOICEBANK_TERMS,
|
|
20473
20999
|
LinkedList,
|
|
20474
21000
|
MAX_VOCAL_VOLUME,
|
|
21001
|
+
MICRO_STEP,
|
|
20475
21002
|
MMLCore,
|
|
20476
21003
|
MML_END_MARKER,
|
|
20477
21004
|
MidiSearchClient,
|
|
20478
|
-
|
|
21005
|
+
PITCH_ENCODING_VERSION,
|
|
21006
|
+
PITCH_MAP,
|
|
21007
|
+
PITCH_RANGE_END,
|
|
21008
|
+
PITCH_RANGE_START,
|
|
20479
21009
|
PREWARM_NOTES,
|
|
20480
21010
|
TRACKS_ADVANCED,
|
|
20481
21011
|
TRACKS_SIMPLE,
|
|
21012
|
+
UNITS_PER_EDO31_DEGREE,
|
|
21013
|
+
UNITS_PER_OCTAVE,
|
|
21014
|
+
UNITS_PER_SEMITONE,
|
|
20482
21015
|
VIBRATO_MIN_SEC,
|
|
20483
21016
|
VOICE_IMAGES,
|
|
20484
21017
|
VOICE_IMAGE_KEY,
|
|
@@ -20488,6 +21021,7 @@ export {
|
|
|
20488
21021
|
buildChordPlacements,
|
|
20489
21022
|
buildDrumPatternJson,
|
|
20490
21023
|
buildNameToKeyMapping,
|
|
21024
|
+
chromaticStep,
|
|
20491
21025
|
collectPitchTokens,
|
|
20492
21026
|
concatFloat32,
|
|
20493
21027
|
createAudioContext,
|
|
@@ -20496,19 +21030,13 @@ export {
|
|
|
20496
21030
|
createKoeVoice,
|
|
20497
21031
|
createLyricsConductor,
|
|
20498
21032
|
createPianoRoll,
|
|
21033
|
+
createRenderer,
|
|
20499
21034
|
createSequencer,
|
|
20500
21035
|
createSingingVoices,
|
|
20501
21036
|
createSynth,
|
|
20502
21037
|
createVoiceRegistry,
|
|
20503
21038
|
decodeMml,
|
|
20504
21039
|
decomposeToMonophonic,
|
|
20505
|
-
drawGrid,
|
|
20506
|
-
drawHeader,
|
|
20507
|
-
drawKeyboard,
|
|
20508
|
-
drawNoteLyrics,
|
|
20509
|
-
drawNotes,
|
|
20510
|
-
drawSelectedNotes,
|
|
20511
|
-
drawSelectionRect,
|
|
20512
21040
|
encodeMml,
|
|
20513
21041
|
encodeWavPCM16,
|
|
20514
21042
|
exportMIDI,
|
|
@@ -20516,36 +21044,34 @@ export {
|
|
|
20516
21044
|
extractMidiDrumPattern,
|
|
20517
21045
|
extractMidiPlacements,
|
|
20518
21046
|
extractMidiPlacementsByTrack,
|
|
21047
|
+
fifthToStep,
|
|
21048
|
+
fifthToUnits,
|
|
20519
21049
|
formatMmlMeta,
|
|
20520
21050
|
freqFromPitch,
|
|
20521
21051
|
generateRandomPattern,
|
|
20522
|
-
getDrawOffset,
|
|
20523
21052
|
getDrumPatternKeys,
|
|
20524
|
-
getGridCanvas,
|
|
20525
|
-
getGridContext,
|
|
20526
|
-
getGridPosition,
|
|
20527
|
-
getHeaderCanvas,
|
|
20528
21053
|
getMidiBPM,
|
|
20529
|
-
getRenderConfig,
|
|
20530
|
-
getXY,
|
|
20531
21054
|
icon,
|
|
20532
|
-
init,
|
|
20533
21055
|
injectStyles,
|
|
20534
21056
|
isChordHeavyTrack,
|
|
21057
|
+
isNaturalLetter,
|
|
20535
21058
|
isPlausibleMidiTranscription,
|
|
20536
21059
|
isValidHttpUrl,
|
|
21060
|
+
keyCountFor,
|
|
20537
21061
|
koeUrl,
|
|
21062
|
+
midiToUnits,
|
|
20538
21063
|
mountChordPlayer,
|
|
20539
21064
|
mountDAW,
|
|
20540
21065
|
mountMmlPlayer,
|
|
21066
|
+
naturalStep,
|
|
20541
21067
|
normalizeDrumPatterns,
|
|
20542
21068
|
normalizeLyrics,
|
|
20543
|
-
onClick,
|
|
20544
21069
|
panToStereo,
|
|
20545
21070
|
parseCustomVocals,
|
|
20546
21071
|
parseLyrics,
|
|
20547
21072
|
parseMML,
|
|
20548
21073
|
parseMmlMeta,
|
|
21074
|
+
pitchV1ToUnits,
|
|
20549
21075
|
playChords,
|
|
20550
21076
|
playMML,
|
|
20551
21077
|
playNote,
|
|
@@ -20553,13 +21079,18 @@ export {
|
|
|
20553
21079
|
playSingingMML,
|
|
20554
21080
|
resolveDrumPattern,
|
|
20555
21081
|
resolveLoopPoint,
|
|
20556
|
-
setBackgroundActive,
|
|
20557
|
-
setDrawOffset,
|
|
20558
21082
|
shiftNotes,
|
|
20559
21083
|
showLoadingOverlay,
|
|
21084
|
+
spellingToUnits,
|
|
20560
21085
|
stripCustomVocals,
|
|
20561
21086
|
stripLyrics,
|
|
20562
21087
|
stripMmlMeta,
|
|
20563
21088
|
transposeNotes,
|
|
21089
|
+
unitsPerRow,
|
|
21090
|
+
unitsPerStep,
|
|
21091
|
+
unitsToHz,
|
|
21092
|
+
unitsToMidi,
|
|
21093
|
+
unitsToMidiDetune,
|
|
21094
|
+
unitsToPitchV1,
|
|
20564
21095
|
vocalVolumeToGain
|
|
20565
21096
|
};
|