@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.js
CHANGED
|
@@ -20,8 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
A4_HZ: () => A4_HZ,
|
|
24
|
+
A4_UNITS: () => A4_UNITS,
|
|
25
|
+
CENTS_PER_UNIT: () => CENTS_PER_UNIT,
|
|
23
26
|
DAW_CSS: () => DAW_CSS,
|
|
24
27
|
DEFAULT_BPM: () => DEFAULT_BPM,
|
|
28
|
+
DEFAULT_EDO: () => DEFAULT_EDO,
|
|
25
29
|
DEFAULT_GATE: () => DEFAULT_GATE,
|
|
26
30
|
DEFAULT_PAN: () => DEFAULT_PAN,
|
|
27
31
|
DEFAULT_PLAYBACK_VELOCITY: () => DEFAULT_PLAYBACK_VELOCITY,
|
|
@@ -31,21 +35,30 @@ __export(index_exports, {
|
|
|
31
35
|
DRUM_FONT: () => DRUM_FONT,
|
|
32
36
|
DRUM_KEYS: () => DRUM_KEYS,
|
|
33
37
|
DRUM_PATTERNS: () => DRUM_PATTERNS,
|
|
38
|
+
EDO31_NAMES: () => EDO31_NAMES,
|
|
34
39
|
GM_INSTRUMENT_NAMES: () => GM_INSTRUMENT_NAMES,
|
|
35
40
|
INSTRUMENT_PRESETS: () => INSTRUMENT_PRESETS,
|
|
41
|
+
KEY_COUNT: () => KEY_COUNT,
|
|
36
42
|
KOE_BASE_URL: () => KOE_BASE_URL,
|
|
37
43
|
KOE_VOICEBANKS: () => KOE_VOICEBANKS,
|
|
38
44
|
KOE_VOICEBANK_LABELS: () => KOE_VOICEBANK_LABELS,
|
|
39
45
|
KOE_VOICEBANK_TERMS: () => KOE_VOICEBANK_TERMS,
|
|
40
46
|
LinkedList: () => LinkedList,
|
|
41
47
|
MAX_VOCAL_VOLUME: () => MAX_VOCAL_VOLUME,
|
|
48
|
+
MICRO_STEP: () => MICRO_STEP,
|
|
42
49
|
MMLCore: () => MMLCore,
|
|
43
50
|
MML_END_MARKER: () => MML_END_MARKER,
|
|
44
51
|
MidiSearchClient: () => MidiSearchClient,
|
|
45
|
-
|
|
52
|
+
PITCH_ENCODING_VERSION: () => PITCH_ENCODING_VERSION,
|
|
53
|
+
PITCH_MAP: () => PITCH_MAP,
|
|
54
|
+
PITCH_RANGE_END: () => PITCH_RANGE_END,
|
|
55
|
+
PITCH_RANGE_START: () => PITCH_RANGE_START,
|
|
46
56
|
PREWARM_NOTES: () => PREWARM_NOTES,
|
|
47
57
|
TRACKS_ADVANCED: () => TRACKS_ADVANCED,
|
|
48
58
|
TRACKS_SIMPLE: () => TRACKS_SIMPLE,
|
|
59
|
+
UNITS_PER_EDO31_DEGREE: () => UNITS_PER_EDO31_DEGREE,
|
|
60
|
+
UNITS_PER_OCTAVE: () => UNITS_PER_OCTAVE,
|
|
61
|
+
UNITS_PER_SEMITONE: () => UNITS_PER_SEMITONE,
|
|
49
62
|
VIBRATO_MIN_SEC: () => VIBRATO_MIN_SEC,
|
|
50
63
|
VOICE_IMAGES: () => VOICE_IMAGES,
|
|
51
64
|
VOICE_IMAGE_KEY: () => VOICE_IMAGE_KEY,
|
|
@@ -55,6 +68,7 @@ __export(index_exports, {
|
|
|
55
68
|
buildChordPlacements: () => buildChordPlacements,
|
|
56
69
|
buildDrumPatternJson: () => buildDrumPatternJson,
|
|
57
70
|
buildNameToKeyMapping: () => buildNameToKeyMapping,
|
|
71
|
+
chromaticStep: () => chromaticStep,
|
|
58
72
|
collectPitchTokens: () => collectPitchTokens,
|
|
59
73
|
concatFloat32: () => concatFloat32,
|
|
60
74
|
createAudioContext: () => createAudioContext,
|
|
@@ -63,19 +77,13 @@ __export(index_exports, {
|
|
|
63
77
|
createKoeVoice: () => createKoeVoice,
|
|
64
78
|
createLyricsConductor: () => createLyricsConductor,
|
|
65
79
|
createPianoRoll: () => createPianoRoll,
|
|
80
|
+
createRenderer: () => createRenderer,
|
|
66
81
|
createSequencer: () => createSequencer,
|
|
67
82
|
createSingingVoices: () => createSingingVoices,
|
|
68
83
|
createSynth: () => createSynth,
|
|
69
84
|
createVoiceRegistry: () => createVoiceRegistry,
|
|
70
85
|
decodeMml: () => decodeMml,
|
|
71
86
|
decomposeToMonophonic: () => decomposeToMonophonic,
|
|
72
|
-
drawGrid: () => drawGrid,
|
|
73
|
-
drawHeader: () => drawHeader,
|
|
74
|
-
drawKeyboard: () => drawKeyboard,
|
|
75
|
-
drawNoteLyrics: () => drawNoteLyrics,
|
|
76
|
-
drawNotes: () => drawNotes,
|
|
77
|
-
drawSelectedNotes: () => drawSelectedNotes,
|
|
78
|
-
drawSelectionRect: () => drawSelectionRect,
|
|
79
87
|
encodeMml: () => encodeMml,
|
|
80
88
|
encodeWavPCM16: () => encodeWavPCM16,
|
|
81
89
|
exportMIDI: () => exportMIDI,
|
|
@@ -83,36 +91,34 @@ __export(index_exports, {
|
|
|
83
91
|
extractMidiDrumPattern: () => extractMidiDrumPattern,
|
|
84
92
|
extractMidiPlacements: () => extractMidiPlacements,
|
|
85
93
|
extractMidiPlacementsByTrack: () => extractMidiPlacementsByTrack,
|
|
94
|
+
fifthToStep: () => fifthToStep,
|
|
95
|
+
fifthToUnits: () => fifthToUnits,
|
|
86
96
|
formatMmlMeta: () => formatMmlMeta,
|
|
87
97
|
freqFromPitch: () => freqFromPitch,
|
|
88
98
|
generateRandomPattern: () => generateRandomPattern,
|
|
89
|
-
getDrawOffset: () => getDrawOffset,
|
|
90
99
|
getDrumPatternKeys: () => getDrumPatternKeys,
|
|
91
|
-
getGridCanvas: () => getGridCanvas,
|
|
92
|
-
getGridContext: () => getGridContext,
|
|
93
|
-
getGridPosition: () => getGridPosition,
|
|
94
|
-
getHeaderCanvas: () => getHeaderCanvas,
|
|
95
100
|
getMidiBPM: () => getMidiBPM,
|
|
96
|
-
getRenderConfig: () => getRenderConfig,
|
|
97
|
-
getXY: () => getXY,
|
|
98
101
|
icon: () => icon,
|
|
99
|
-
init: () => init,
|
|
100
102
|
injectStyles: () => injectStyles,
|
|
101
103
|
isChordHeavyTrack: () => isChordHeavyTrack,
|
|
104
|
+
isNaturalLetter: () => isNaturalLetter,
|
|
102
105
|
isPlausibleMidiTranscription: () => isPlausibleMidiTranscription,
|
|
103
106
|
isValidHttpUrl: () => isValidHttpUrl,
|
|
107
|
+
keyCountFor: () => keyCountFor,
|
|
104
108
|
koeUrl: () => koeUrl,
|
|
109
|
+
midiToUnits: () => midiToUnits,
|
|
105
110
|
mountChordPlayer: () => mountChordPlayer,
|
|
106
111
|
mountDAW: () => mountDAW,
|
|
107
112
|
mountMmlPlayer: () => mountMmlPlayer,
|
|
113
|
+
naturalStep: () => naturalStep,
|
|
108
114
|
normalizeDrumPatterns: () => normalizeDrumPatterns,
|
|
109
115
|
normalizeLyrics: () => normalizeLyrics,
|
|
110
|
-
onClick: () => onClick,
|
|
111
116
|
panToStereo: () => panToStereo,
|
|
112
117
|
parseCustomVocals: () => parseCustomVocals,
|
|
113
118
|
parseLyrics: () => parseLyrics,
|
|
114
119
|
parseMML: () => parseMML,
|
|
115
120
|
parseMmlMeta: () => parseMmlMeta,
|
|
121
|
+
pitchV1ToUnits: () => pitchV1ToUnits,
|
|
116
122
|
playChords: () => playChords,
|
|
117
123
|
playMML: () => playMML,
|
|
118
124
|
playNote: () => playNote,
|
|
@@ -120,14 +126,19 @@ __export(index_exports, {
|
|
|
120
126
|
playSingingMML: () => playSingingMML,
|
|
121
127
|
resolveDrumPattern: () => resolveDrumPattern,
|
|
122
128
|
resolveLoopPoint: () => resolveLoopPoint,
|
|
123
|
-
setBackgroundActive: () => setBackgroundActive,
|
|
124
|
-
setDrawOffset: () => setDrawOffset,
|
|
125
129
|
shiftNotes: () => shiftNotes,
|
|
126
130
|
showLoadingOverlay: () => showLoadingOverlay,
|
|
131
|
+
spellingToUnits: () => spellingToUnits,
|
|
127
132
|
stripCustomVocals: () => stripCustomVocals,
|
|
128
133
|
stripLyrics: () => stripLyrics,
|
|
129
134
|
stripMmlMeta: () => stripMmlMeta,
|
|
130
135
|
transposeNotes: () => transposeNotes,
|
|
136
|
+
unitsPerRow: () => unitsPerRow,
|
|
137
|
+
unitsPerStep: () => unitsPerStep,
|
|
138
|
+
unitsToHz: () => unitsToHz,
|
|
139
|
+
unitsToMidi: () => unitsToMidi,
|
|
140
|
+
unitsToMidiDetune: () => unitsToMidiDetune,
|
|
141
|
+
unitsToPitchV1: () => unitsToPitchV1,
|
|
131
142
|
vocalVolumeToGain: () => vocalVolumeToGain
|
|
132
143
|
});
|
|
133
144
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -282,7 +293,7 @@ async function buildNameToKeyMapping() {
|
|
|
282
293
|
}
|
|
283
294
|
var GM_INSTRUMENT_NAMES = FONT_NAME_SURIKOV.trim().split("\n").map((line) => line.slice(line.indexOf(" ") + 1));
|
|
284
295
|
|
|
285
|
-
// node_modules/.pnpm/@onjmin+chord-parser@1.
|
|
296
|
+
// node_modules/.pnpm/@onjmin+chord-parser@1.1.1/node_modules/@onjmin/chord-parser/dist/index.mjs
|
|
286
297
|
var SHARP_NAMES = [
|
|
287
298
|
"C",
|
|
288
299
|
"C#",
|
|
@@ -313,6 +324,10 @@ var FLAT_NAMES = [
|
|
|
313
324
|
];
|
|
314
325
|
var toPitchClass = (n) => (n % 12 + 12) % 12;
|
|
315
326
|
var noteName = (pc, flat = false) => (flat ? FLAT_NAMES : SHARP_NAMES)[toPitchClass(pc)];
|
|
327
|
+
var LETTER_FIFTH = [0, 2, 4, -1, 1, 3, 5];
|
|
328
|
+
var DEGREE_FIFTH = [0, 2, 4, -1, 1, 3, 5];
|
|
329
|
+
var deg2fifth = (deg) => DEGREE_FIFTH[(deg - 1) % 7];
|
|
330
|
+
var HALF_FIFTH = 7;
|
|
316
331
|
var SyntaxErrorWithPos = class extends Error {
|
|
317
332
|
constructor(input, msg) {
|
|
318
333
|
super(
|
|
@@ -359,6 +374,16 @@ var Input = class _Input {
|
|
|
359
374
|
};
|
|
360
375
|
var Output = class {
|
|
361
376
|
pitch = null;
|
|
377
|
+
/** ルート音の五度圏インデックス。`pitch` と対で持つ。 */
|
|
378
|
+
rootFifth = 0;
|
|
379
|
+
/**
|
|
380
|
+
* 構成音の五度圏オフセット。キーは「ルートからの相対半音 mod 12」。
|
|
381
|
+
*
|
|
382
|
+
* 五度圏インデックスはオクターブ不変なので、分数コードのオクターブシフト
|
|
383
|
+
* (`value` の配列を ±12 する処理)の影響を受けない。そのため構成音の集合とは
|
|
384
|
+
* 別にこの対応表を持っておけば、配列操作側に手を入れずに綴りを保てる。
|
|
385
|
+
*/
|
|
386
|
+
relFifths = /* @__PURE__ */ new Map();
|
|
362
387
|
chord = null;
|
|
363
388
|
isChord = false;
|
|
364
389
|
pending = null;
|
|
@@ -442,9 +467,17 @@ var parseFormula = (input, output = new Output(), nest = 0) => {
|
|
|
442
467
|
case DIVIDE: {
|
|
443
468
|
const o = parseFormula(input, new Output(), nest);
|
|
444
469
|
const v = [...output.value];
|
|
470
|
+
const rebase = (semi, fifth) => noteFifth(
|
|
471
|
+
output,
|
|
472
|
+
semi - output.pitch,
|
|
473
|
+
fifth - output.rootFifth
|
|
474
|
+
);
|
|
445
475
|
if (o.isChord) {
|
|
476
|
+
for (const [rel, f] of o.relFifths)
|
|
477
|
+
rebase(o.pitch + rel, o.rootFifth + f);
|
|
446
478
|
output.value = [...o.value].concat(v);
|
|
447
479
|
} else {
|
|
480
|
+
rebase(o.pitch, o.rootFifth);
|
|
448
481
|
const a = v.sort((x2, y) => x2 - y);
|
|
449
482
|
const pitch = (o.pitch + 3) % 12 - 3;
|
|
450
483
|
if (a[0] < pitch) {
|
|
@@ -491,63 +524,91 @@ var parsePitch = (input, output) => {
|
|
|
491
524
|
const pitch = pitchMatcher.parse(input);
|
|
492
525
|
if (pitch === null) err(input, "Not found pitch");
|
|
493
526
|
output.pitch = pitch;
|
|
527
|
+
output.rootFifth = LETTER_FIFTH[idx2pitch.slice(0, 7).indexOf(pitch)];
|
|
494
528
|
for (let i2 = 0; i2 < 2; i2++) {
|
|
495
529
|
const half = parseHalf(input, true);
|
|
496
530
|
if (half === null) break;
|
|
497
531
|
output.pitch += half;
|
|
532
|
+
output.rootFifth += half * HALF_FIFTH;
|
|
498
533
|
}
|
|
499
534
|
return parseBase(input, output);
|
|
500
535
|
};
|
|
536
|
+
var noteFifth = (out, semi, fifth) => {
|
|
537
|
+
out.relFifths.set((semi % 12 + 12) % 12, fifth);
|
|
538
|
+
};
|
|
501
539
|
var MAJOR = [0, 4, 7];
|
|
502
540
|
var DIM = [0, 3, 6];
|
|
541
|
+
var BASE_FIFTHS = /* @__PURE__ */ new Map();
|
|
542
|
+
var MINOR = [0, 3, 7];
|
|
543
|
+
var AUG = [0, 4, 8];
|
|
544
|
+
var HALF_DIM = [0, 3, 6, 10];
|
|
545
|
+
BASE_FIFTHS.set(MAJOR, [0, 4, 1]);
|
|
546
|
+
BASE_FIFTHS.set(MINOR, [0, -3, 1]);
|
|
547
|
+
BASE_FIFTHS.set(DIM, [0, -3, -6]);
|
|
548
|
+
BASE_FIFTHS.set(AUG, [0, 4, 8]);
|
|
549
|
+
BASE_FIFTHS.set(HALF_DIM, [0, -3, -6, -2]);
|
|
503
550
|
var baseMatcher = new Matcher();
|
|
504
|
-
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"],
|
|
551
|
+
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"], MINOR);
|
|
505
552
|
baseMatcher.set(["dim", "\u3007"], DIM);
|
|
506
|
-
baseMatcher.set("+",
|
|
507
|
-
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"],
|
|
553
|
+
baseMatcher.set("+", AUG);
|
|
554
|
+
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"], HALF_DIM);
|
|
508
555
|
var parseBase = (input, output) => {
|
|
509
556
|
const isMajMarker = /^maj/i.test(input.str.slice(input.idx));
|
|
510
557
|
const res = isMajMarker ? null : baseMatcher.parse(input);
|
|
511
558
|
if (res !== null) output.isChord = true;
|
|
512
|
-
|
|
559
|
+
const base = res || MAJOR;
|
|
560
|
+
output.chord = new Set(base);
|
|
561
|
+
const baseFifths = BASE_FIFTHS.get(base);
|
|
562
|
+
if (baseFifths)
|
|
563
|
+
for (const [i2, semi] of base.entries())
|
|
564
|
+
noteFifth(output, semi, baseFifths[i2]);
|
|
513
565
|
if (res === DIM) {
|
|
514
566
|
const { num } = input;
|
|
515
567
|
const chord = output.chord;
|
|
516
|
-
if (num !== null)
|
|
568
|
+
if (num !== null) {
|
|
569
|
+
chord.add(deg2pitch(num) - 2);
|
|
570
|
+
noteFifth(output, deg2pitch(num) - 2, deg2fifth(num) - 2 * HALF_FIFTH);
|
|
571
|
+
}
|
|
517
572
|
}
|
|
518
573
|
output.nest = input.nest;
|
|
519
574
|
return parseTerm(input, output);
|
|
520
575
|
};
|
|
521
|
-
var
|
|
522
|
-
chord.add(deg2pitch(
|
|
576
|
+
var put = (chord, out, deg, half = 0) => {
|
|
577
|
+
chord.add(deg2pitch(deg) + half);
|
|
578
|
+
noteFifth(out, deg2pitch(deg) + half, deg2fifth(deg) + half * HALF_FIFTH);
|
|
579
|
+
};
|
|
580
|
+
var add = (chord, n, half, out) => {
|
|
581
|
+
put(chord, out, n, half);
|
|
523
582
|
};
|
|
524
|
-
var aug = (chord) => {
|
|
583
|
+
var aug = (chord, out) => {
|
|
525
584
|
chord.delete(deg2pitch(5));
|
|
526
|
-
chord
|
|
585
|
+
put(chord, out, 5, 1);
|
|
527
586
|
};
|
|
528
|
-
var _7th = (chord, n, _half2, isFlat = false) => {
|
|
587
|
+
var _7th = (chord, n, _half2, out, isFlat = false) => {
|
|
529
588
|
if (n === 5) chord.delete(deg2pitch(3));
|
|
530
|
-
else if (n === 6) chord
|
|
531
|
-
else if (n === 69)
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (n >=
|
|
536
|
-
if (n >=
|
|
589
|
+
else if (n === 6) put(chord, out, 6);
|
|
590
|
+
else if (n === 69) {
|
|
591
|
+
put(chord, out, 6);
|
|
592
|
+
put(chord, out, 9);
|
|
593
|
+
} else {
|
|
594
|
+
if (n >= 7) put(chord, out, 7, isFlat ? -1 : 0);
|
|
595
|
+
if (n >= 9) put(chord, out, 9);
|
|
596
|
+
if (n >= 11) put(chord, out, 11);
|
|
597
|
+
if (n >= 13) put(chord, out, 13);
|
|
537
598
|
}
|
|
538
599
|
};
|
|
539
|
-
var _half = (chord, n, half) => {
|
|
600
|
+
var _half = (chord, n, half, out) => {
|
|
540
601
|
chord.delete(deg2pitch(n));
|
|
541
|
-
chord
|
|
602
|
+
put(chord, out, n, half);
|
|
542
603
|
};
|
|
543
604
|
var funcMatcher = new Matcher();
|
|
544
605
|
funcMatcher.set("add", add);
|
|
545
606
|
funcMatcher.set(["omit", "no"], (chord, n, half) => {
|
|
546
607
|
chord.delete(deg2pitch(n) + half);
|
|
547
608
|
});
|
|
548
|
-
funcMatcher.set("sus", (chord, n, half) => {
|
|
609
|
+
funcMatcher.set("sus", (chord, n, half, out) => {
|
|
549
610
|
chord.delete(deg2pitch(3));
|
|
550
|
-
chord
|
|
611
|
+
put(chord, out, n, half);
|
|
551
612
|
});
|
|
552
613
|
funcMatcher.set(
|
|
553
614
|
["M", "maj", "Maj", "major", "Major", "\u25B3", "\u0394"],
|
|
@@ -563,17 +624,18 @@ var parseFunc = (input, output) => {
|
|
|
563
624
|
const half = parseHalf(input);
|
|
564
625
|
const { num } = input;
|
|
565
626
|
if (num === null) {
|
|
566
|
-
if (isAug) aug(chord);
|
|
627
|
+
if (isAug) aug(chord, output);
|
|
567
628
|
else err(input, "Not found number");
|
|
568
629
|
}
|
|
569
630
|
if (half === null) {
|
|
570
|
-
if (input.nest === output.nest)
|
|
571
|
-
|
|
631
|
+
if (input.nest === output.nest)
|
|
632
|
+
_7th(chord, num, 0, output, true);
|
|
633
|
+
else add(chord, num, 0, output);
|
|
572
634
|
} else {
|
|
573
|
-
_half(chord, num, half);
|
|
635
|
+
_half(chord, num, half, output);
|
|
574
636
|
}
|
|
575
637
|
} else if (func === aug) {
|
|
576
|
-
aug(chord);
|
|
638
|
+
aug(chord, output);
|
|
577
639
|
} else {
|
|
578
640
|
output.pending = func;
|
|
579
641
|
}
|
|
@@ -587,7 +649,8 @@ var parsePending = (input, output) => {
|
|
|
587
649
|
pending(
|
|
588
650
|
chord,
|
|
589
651
|
num,
|
|
590
|
-
half === null ? 0 : half
|
|
652
|
+
half === null ? 0 : half,
|
|
653
|
+
output
|
|
591
654
|
);
|
|
592
655
|
output.pending = null;
|
|
593
656
|
return parseTerm(input, output);
|
|
@@ -599,12 +662,29 @@ var parseChord = (symbol) => {
|
|
|
599
662
|
const pitchClasses = [...new Set(notes.map(toPitchClass))].sort(
|
|
600
663
|
(a, b) => a - b
|
|
601
664
|
);
|
|
665
|
+
const rootPitch = output.pitch;
|
|
666
|
+
const rootFifth = output.rootFifth;
|
|
667
|
+
const noteFifths = notes.map((n) => {
|
|
668
|
+
const rel = ((n - rootPitch) % 12 + 12) % 12;
|
|
669
|
+
const f = output.relFifths.get(rel);
|
|
670
|
+
if (f !== void 0) return rootFifth + f;
|
|
671
|
+
let best = 0;
|
|
672
|
+
for (let k = -6; k <= 6; k++) {
|
|
673
|
+
if ((k * 7 % 12 + 12) % 12 === rel) {
|
|
674
|
+
best = k;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return rootFifth + best;
|
|
679
|
+
});
|
|
602
680
|
return {
|
|
603
681
|
symbol,
|
|
604
|
-
root: toPitchClass(
|
|
682
|
+
root: toPitchClass(rootPitch),
|
|
605
683
|
notes,
|
|
606
684
|
pitchClasses,
|
|
607
|
-
intervals
|
|
685
|
+
intervals,
|
|
686
|
+
rootFifth,
|
|
687
|
+
noteFifths
|
|
608
688
|
};
|
|
609
689
|
};
|
|
610
690
|
var QUALITY_SOURCE = [
|
|
@@ -1633,10 +1713,72 @@ var createChannelStrip = (ctx, destination, options = {}) => {
|
|
|
1633
1713
|
};
|
|
1634
1714
|
};
|
|
1635
1715
|
|
|
1716
|
+
// src/tuning.ts
|
|
1717
|
+
var UNITS_PER_OCTAVE = 372;
|
|
1718
|
+
var UNITS_PER_SEMITONE = 372 / 12;
|
|
1719
|
+
var UNITS_PER_EDO31_DEGREE = 372 / 31;
|
|
1720
|
+
var CENTS_PER_UNIT = 1200 / UNITS_PER_OCTAVE;
|
|
1721
|
+
var A4_UNITS = 69 * UNITS_PER_SEMITONE;
|
|
1722
|
+
var A4_HZ = 440;
|
|
1723
|
+
var DEFAULT_EDO = 12;
|
|
1724
|
+
var unitsPerStep = (edo) => UNITS_PER_OCTAVE / edo;
|
|
1725
|
+
var midiToUnits = (midi) => midi * UNITS_PER_SEMITONE;
|
|
1726
|
+
var unitsToMidi = (units) => units / UNITS_PER_SEMITONE;
|
|
1727
|
+
var unitsToHz = (units) => A4_HZ * 2 ** ((units - A4_UNITS) / UNITS_PER_OCTAVE);
|
|
1728
|
+
var unitsToMidiDetune = (units) => {
|
|
1729
|
+
const midi = Math.round(units / UNITS_PER_SEMITONE);
|
|
1730
|
+
return {
|
|
1731
|
+
midi,
|
|
1732
|
+
detuneCents: (units - midi * UNITS_PER_SEMITONE) * CENTS_PER_UNIT
|
|
1733
|
+
};
|
|
1734
|
+
};
|
|
1735
|
+
var NATURAL_STEPS = {
|
|
1736
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
1737
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
1738
|
+
};
|
|
1739
|
+
var chromaticStep = (edo) => edo === 31 ? 2 : 1;
|
|
1740
|
+
var MICRO_STEP = 1;
|
|
1741
|
+
var isNaturalLetter = (ch) => Object.hasOwn(NATURAL_STEPS[12], ch);
|
|
1742
|
+
var naturalStep = (ch, edo) => NATURAL_STEPS[edo][ch] ?? null;
|
|
1743
|
+
var spellingToUnits = (letter, octave, chromatic, micro, edo) => {
|
|
1744
|
+
const step = naturalStep(letter, edo);
|
|
1745
|
+
if (step === null) return null;
|
|
1746
|
+
const perStep = unitsPerStep(edo);
|
|
1747
|
+
const stepsFromC = step + chromatic * chromaticStep(edo) + micro * MICRO_STEP;
|
|
1748
|
+
return (octave + 1) * UNITS_PER_OCTAVE + stepsFromC * perStep;
|
|
1749
|
+
};
|
|
1750
|
+
var FIFTH_STEPS = { 12: 7, 31: 18 };
|
|
1751
|
+
var fifthToStep = (fifthIndex, edo) => {
|
|
1752
|
+
const n = fifthIndex * FIFTH_STEPS[edo];
|
|
1753
|
+
return (n % edo + edo) % edo;
|
|
1754
|
+
};
|
|
1755
|
+
var fifthToUnits = (fifthIndex, edo) => fifthToStep(fifthIndex, edo) * unitsPerStep(edo);
|
|
1756
|
+
var PITCH_ENCODING_VERSION = 2;
|
|
1757
|
+
var pitchV1ToUnits = (pitch) => pitch * UNITS_PER_SEMITONE;
|
|
1758
|
+
var unitsToPitchV1 = (units) => Math.round(units / UNITS_PER_SEMITONE);
|
|
1759
|
+
|
|
1636
1760
|
// src/chords.ts
|
|
1637
1761
|
var C3 = 48;
|
|
1762
|
+
var MEANTONE_STEP_BY_SEMITONE = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
1763
|
+
var chordToneToUnits = (relSemitone, relFifth, rootSemitone, rootFifth, edo) => {
|
|
1764
|
+
const perStep = UNITS_PER_OCTAVE / edo;
|
|
1765
|
+
const rootOct = Math.floor(rootSemitone / 12);
|
|
1766
|
+
const rootWithin = (rootSemitone % 12 + 12) % 12;
|
|
1767
|
+
const rootStep = edo === 31 ? (fifthToStep(rootFifth, 31) + MEANTONE_STEP_BY_SEMITONE[rootWithin] - fifthToStep(rootFifth, 31) + 31) % 31 : rootWithin;
|
|
1768
|
+
const relWithin12 = (relSemitone % 12 + 12) % 12;
|
|
1769
|
+
const relOct = Math.round((relSemitone - relWithin12) / 12);
|
|
1770
|
+
const relStep = (fifthToStep(relFifth, edo) - fifthToStep(rootFifth, edo) + edo) % edo;
|
|
1771
|
+
return (rootOct + relOct) * UNITS_PER_OCTAVE + (rootStep + relStep) * perStep;
|
|
1772
|
+
};
|
|
1638
1773
|
var buildChordPlacements = (options) => {
|
|
1639
|
-
const {
|
|
1774
|
+
const {
|
|
1775
|
+
chordStr,
|
|
1776
|
+
patternType,
|
|
1777
|
+
rootShift,
|
|
1778
|
+
bpm,
|
|
1779
|
+
stepsPerBar,
|
|
1780
|
+
edo = 12
|
|
1781
|
+
} = options;
|
|
1640
1782
|
const placements = [];
|
|
1641
1783
|
if (!chordStr.trim()) return placements;
|
|
1642
1784
|
const offset = rootShift;
|
|
@@ -1665,8 +1807,20 @@ var buildChordPlacements = (options) => {
|
|
|
1665
1807
|
for (const group of Object.values(chordGroups)) {
|
|
1666
1808
|
for (const chord of group) {
|
|
1667
1809
|
let notes;
|
|
1810
|
+
let toUnits;
|
|
1668
1811
|
try {
|
|
1669
|
-
|
|
1812
|
+
const parsed = parseChord(`${chord.key}${chord.chord}`);
|
|
1813
|
+
notes = [...parsed.notes];
|
|
1814
|
+
const fifthOf = new Map(
|
|
1815
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1816
|
+
);
|
|
1817
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1818
|
+
rel,
|
|
1819
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1820
|
+
C3 + offset,
|
|
1821
|
+
parsed.rootFifth,
|
|
1822
|
+
edo === 31 ? 31 : 12
|
|
1823
|
+
);
|
|
1670
1824
|
} catch {
|
|
1671
1825
|
continue;
|
|
1672
1826
|
}
|
|
@@ -1675,7 +1829,7 @@ var buildChordPlacements = (options) => {
|
|
|
1675
1829
|
for (const noteOffset of notes) {
|
|
1676
1830
|
placements.push({
|
|
1677
1831
|
startStep: chord.whenStep,
|
|
1678
|
-
|
|
1832
|
+
pitchUnits: toUnits(noteOffset),
|
|
1679
1833
|
durationSteps: noteLength,
|
|
1680
1834
|
velocity: 100
|
|
1681
1835
|
});
|
|
@@ -1685,7 +1839,7 @@ var buildChordPlacements = (options) => {
|
|
|
1685
1839
|
notes.forEach((noteOffset, i2) => {
|
|
1686
1840
|
placements.push({
|
|
1687
1841
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1688
|
-
|
|
1842
|
+
pitchUnits: toUnits(noteOffset),
|
|
1689
1843
|
durationSteps: noteLength - i2 * arpInterval,
|
|
1690
1844
|
velocity: 100
|
|
1691
1845
|
});
|
|
@@ -1695,7 +1849,7 @@ var buildChordPlacements = (options) => {
|
|
|
1695
1849
|
notes.forEach((noteOffset, i2) => {
|
|
1696
1850
|
placements.push({
|
|
1697
1851
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1698
|
-
|
|
1852
|
+
pitchUnits: toUnits(noteOffset),
|
|
1699
1853
|
durationSteps: Math.max(12, noteLength - i2 * arpInterval),
|
|
1700
1854
|
velocity: 100
|
|
1701
1855
|
});
|
|
@@ -1709,7 +1863,7 @@ var buildChordPlacements = (options) => {
|
|
|
1709
1863
|
for (const noteOffset of notes) {
|
|
1710
1864
|
placements.push({
|
|
1711
1865
|
startStep: syncopatedStep,
|
|
1712
|
-
|
|
1866
|
+
pitchUnits: toUnits(noteOffset),
|
|
1713
1867
|
durationSteps: Math.min(halfBeat, 12),
|
|
1714
1868
|
velocity: 100
|
|
1715
1869
|
});
|
|
@@ -1728,7 +1882,7 @@ var buildChordPlacements = (options) => {
|
|
|
1728
1882
|
for (const noteOffset of notes) {
|
|
1729
1883
|
placements.push({
|
|
1730
1884
|
startStep: noteStart,
|
|
1731
|
-
|
|
1885
|
+
pitchUnits: toUnits(noteOffset),
|
|
1732
1886
|
durationSteps: yatsumeLengthSteps,
|
|
1733
1887
|
velocity: 100
|
|
1734
1888
|
});
|
|
@@ -1740,7 +1894,7 @@ var buildChordPlacements = (options) => {
|
|
|
1740
1894
|
const stepOffset = i2 * Math.floor(stepsPerBar / 4);
|
|
1741
1895
|
placements.push({
|
|
1742
1896
|
startStep: chord.whenStep + stepOffset,
|
|
1743
|
-
|
|
1897
|
+
pitchUnits: toUnits(noteOffset),
|
|
1744
1898
|
durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
|
|
1745
1899
|
velocity: 100
|
|
1746
1900
|
});
|
|
@@ -1752,8 +1906,20 @@ var buildChordPlacements = (options) => {
|
|
|
1752
1906
|
const chordNames = chordStr.split(/[\s,]+/).filter((c) => c);
|
|
1753
1907
|
chordNames.forEach((chordName, barIndex) => {
|
|
1754
1908
|
let notes;
|
|
1909
|
+
let toUnits;
|
|
1755
1910
|
try {
|
|
1756
|
-
|
|
1911
|
+
const parsed = parseChord(chordName);
|
|
1912
|
+
notes = [...parsed.notes];
|
|
1913
|
+
const fifthOf = new Map(
|
|
1914
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1915
|
+
);
|
|
1916
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1917
|
+
rel,
|
|
1918
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1919
|
+
C3 + offset,
|
|
1920
|
+
parsed.rootFifth,
|
|
1921
|
+
edo === 31 ? 31 : 12
|
|
1922
|
+
);
|
|
1757
1923
|
} catch {
|
|
1758
1924
|
return;
|
|
1759
1925
|
}
|
|
@@ -1763,7 +1929,7 @@ var buildChordPlacements = (options) => {
|
|
|
1763
1929
|
const stepOffset = i2 * 3;
|
|
1764
1930
|
placements.push({
|
|
1765
1931
|
startStep: startStep + stepOffset,
|
|
1766
|
-
|
|
1932
|
+
pitchUnits: toUnits(noteOffset),
|
|
1767
1933
|
durationSteps: chordLength - stepOffset,
|
|
1768
1934
|
velocity: 100
|
|
1769
1935
|
});
|
|
@@ -2295,6 +2461,11 @@ var DEFAULT_VELOCITY = 100;
|
|
|
2295
2461
|
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
2296
2462
|
var DEFAULT_STEPS_PER_BAR = 192;
|
|
2297
2463
|
var MML_END_MARKER = "#end;";
|
|
2464
|
+
var PITCH_RANGE_START = 0;
|
|
2465
|
+
var PITCH_RANGE_END = 3937;
|
|
2466
|
+
var unitsPerRow = (edo) => 372 / edo;
|
|
2467
|
+
var keyCountFor = (edo) => Math.floor((PITCH_RANGE_END - PITCH_RANGE_START) / unitsPerRow(edo)) + 1;
|
|
2468
|
+
var KEY_COUNT = keyCountFor(12);
|
|
2298
2469
|
|
|
2299
2470
|
// src/vibrato.ts
|
|
2300
2471
|
var VIBRATO_MIN_SEC = 0.35;
|
|
@@ -2694,7 +2865,8 @@ var FORMANTS = {
|
|
|
2694
2865
|
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
2695
2866
|
N: [250, 1e3]
|
|
2696
2867
|
};
|
|
2697
|
-
var
|
|
2868
|
+
var unitsToFreq = (units) => 440 * 2 ** ((units - 2139) / 372);
|
|
2869
|
+
var unitsToMidiFloat = (units) => units / 31;
|
|
2698
2870
|
var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
2699
2871
|
const active = /* @__PURE__ */ new Set();
|
|
2700
2872
|
const voice = (syllable, e) => {
|
|
@@ -2728,7 +2900,7 @@ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
|
2728
2900
|
}
|
|
2729
2901
|
const osc = ctx.createOscillator();
|
|
2730
2902
|
osc.type = "sawtooth";
|
|
2731
|
-
osc.frequency.value =
|
|
2903
|
+
osc.frequency.value = unitsToFreq(e.pitchUnits);
|
|
2732
2904
|
const makeFormant = (freq, q2, gainScale) => {
|
|
2733
2905
|
const filter = ctx.createBiquadFilter();
|
|
2734
2906
|
filter.type = "bandpass";
|
|
@@ -2980,7 +3152,7 @@ var createLocalBackend = async (options) => {
|
|
|
2980
3152
|
if (!consonantPcm || !vowelPcm) return null;
|
|
2981
3153
|
const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
|
|
2982
3154
|
if (!spliced) return null;
|
|
2983
|
-
const targetHz =
|
|
3155
|
+
const targetHz = unitsToFreq(pitch);
|
|
2984
3156
|
const audio = worldline.renderNote({
|
|
2985
3157
|
pcm: spliced.pcm,
|
|
2986
3158
|
pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
|
|
@@ -3009,7 +3181,7 @@ var createLocalBackend = async (options) => {
|
|
|
3009
3181
|
if (!pcm || pcm.length === 0) return null;
|
|
3010
3182
|
const entry = bank.manifest.phonemes[alias];
|
|
3011
3183
|
const lead = leadInFromEntry(entry);
|
|
3012
|
-
const targetHz =
|
|
3184
|
+
const targetHz = unitsToFreq(pitch);
|
|
3013
3185
|
if (worldline) {
|
|
3014
3186
|
const audio = worldline.renderNote({
|
|
3015
3187
|
pcm,
|
|
@@ -3216,7 +3388,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3216
3388
|
backend.pitchTokens,
|
|
3217
3389
|
syllable,
|
|
3218
3390
|
prevVowel,
|
|
3219
|
-
e.
|
|
3391
|
+
unitsToMidiFloat(e.pitchUnits)
|
|
3220
3392
|
);
|
|
3221
3393
|
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
3222
3394
|
if (!alias) return;
|
|
@@ -3224,7 +3396,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3224
3396
|
const peak = Math.max(1e-4, e.volume);
|
|
3225
3397
|
const pan = e.pan ?? 0;
|
|
3226
3398
|
const durationMs = Math.max(60, e.duration * 1e3);
|
|
3227
|
-
void renderInto(alias, e.
|
|
3399
|
+
void renderInto(alias, e.pitchUnits, durationMs).then((r) => {
|
|
3228
3400
|
if (r)
|
|
3229
3401
|
schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
|
|
3230
3402
|
});
|
|
@@ -3236,7 +3408,10 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3236
3408
|
backend.pitchTokens,
|
|
3237
3409
|
syllable,
|
|
3238
3410
|
prevVowelArg,
|
|
3239
|
-
|
|
3411
|
+
// 多音階バンクのピッチトークン("_G4" 等)は録音の音名なので、
|
|
3412
|
+
// 最寄り選択はMIDIノート番号の尺度で行う。units のまま渡すと常に
|
|
3413
|
+
// 最高音のトークンが選ばれてしまう。
|
|
3414
|
+
unitsToMidiFloat(pitch)
|
|
3240
3415
|
);
|
|
3241
3416
|
if (!alias) return null;
|
|
3242
3417
|
const dMs = Math.max(60, durationMs);
|
|
@@ -3268,13 +3443,14 @@ var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
|
3268
3443
|
var STREAM_POLL_MS = 100;
|
|
3269
3444
|
var OCTAVE_UNISON_PEAK_SCALE = 0.6;
|
|
3270
3445
|
var octaveUnisonOffsets = (mode) => {
|
|
3446
|
+
const OCT = 372;
|
|
3271
3447
|
switch (mode) {
|
|
3272
3448
|
case "down":
|
|
3273
|
-
return [-
|
|
3449
|
+
return [-OCT];
|
|
3274
3450
|
case "up":
|
|
3275
|
-
return [
|
|
3451
|
+
return [OCT];
|
|
3276
3452
|
case "both":
|
|
3277
|
-
return [-
|
|
3453
|
+
return [-OCT, OCT];
|
|
3278
3454
|
default:
|
|
3279
3455
|
return [];
|
|
3280
3456
|
}
|
|
@@ -3484,7 +3660,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3484
3660
|
const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
|
|
3485
3661
|
model(note.syllable, {
|
|
3486
3662
|
trackId: track.id ?? "",
|
|
3487
|
-
pitch,
|
|
3663
|
+
pitchUnits: pitch,
|
|
3488
3664
|
velocity: 100,
|
|
3489
3665
|
volume: effPeak,
|
|
3490
3666
|
when,
|
|
@@ -3550,17 +3726,13 @@ var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
|
3550
3726
|
};
|
|
3551
3727
|
|
|
3552
3728
|
// src/mml-parser.ts
|
|
3553
|
-
var
|
|
3554
|
-
c: 0,
|
|
3555
|
-
d:
|
|
3556
|
-
e: 4,
|
|
3557
|
-
f: 5,
|
|
3558
|
-
g: 7,
|
|
3559
|
-
a: 9,
|
|
3560
|
-
b: 11
|
|
3729
|
+
var NATURAL_STEPS2 = {
|
|
3730
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
3731
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
3561
3732
|
};
|
|
3733
|
+
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
3562
3734
|
var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
3563
|
-
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode)=([\w-]+)/gi;
|
|
3735
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
|
|
3564
3736
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
3565
3737
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
3566
3738
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -3610,6 +3782,9 @@ var parseMmlMeta = (mml) => {
|
|
|
3610
3782
|
if (m[2] === "simple" || m[2] === "advanced") {
|
|
3611
3783
|
meta.mode = m[2];
|
|
3612
3784
|
}
|
|
3785
|
+
} else if (key === "edo") {
|
|
3786
|
+
const e = Number.parseInt(m[2], 10);
|
|
3787
|
+
if (e === 12 || e === 31) meta.edo = e;
|
|
3613
3788
|
}
|
|
3614
3789
|
}
|
|
3615
3790
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -3712,6 +3887,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
3712
3887
|
if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
|
|
3713
3888
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
3714
3889
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
3890
|
+
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
3715
3891
|
if (meta.trackInstruments) {
|
|
3716
3892
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
3717
3893
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -3786,6 +3962,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
3786
3962
|
const endMarkerBase = MML_END_MARKER.replace(/;+$/, "");
|
|
3787
3963
|
const endRegex = new RegExp(`(?<![cdafgCDAFG])${endMarkerBase}\\b;?`, "gi");
|
|
3788
3964
|
const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
|
|
3965
|
+
const edo = meta.edo === 31 ? 31 : 12;
|
|
3966
|
+
const naturals = NATURAL_STEPS2[edo];
|
|
3967
|
+
const unitsPerStep2 = 372 / edo;
|
|
3968
|
+
const chromaticStep2 = edo === 31 ? 2 : 1;
|
|
3789
3969
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
3790
3970
|
let trackIndex = 0;
|
|
3791
3971
|
let sourceTrackIndex = 0;
|
|
@@ -3833,6 +4013,19 @@ var parseMML = (mml, options = {}) => {
|
|
|
3833
4013
|
type
|
|
3834
4014
|
});
|
|
3835
4015
|
};
|
|
4016
|
+
const readAccidentals = () => {
|
|
4017
|
+
let delta = 0;
|
|
4018
|
+
while (j < body.length) {
|
|
4019
|
+
const a = body[j];
|
|
4020
|
+
if (a === "#") delta += chromaticStep2;
|
|
4021
|
+
else if (a === "-") delta -= chromaticStep2;
|
|
4022
|
+
else if (a === "+") delta += edo === 31 ? 1 : chromaticStep2;
|
|
4023
|
+
else if (a === "_") delta -= edo === 31 ? 1 : chromaticStep2;
|
|
4024
|
+
else break;
|
|
4025
|
+
j++;
|
|
4026
|
+
}
|
|
4027
|
+
return delta;
|
|
4028
|
+
};
|
|
3836
4029
|
const parseLength = () => {
|
|
3837
4030
|
let numStr = "";
|
|
3838
4031
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -3904,17 +4097,11 @@ var parseMML = (mml, options = {}) => {
|
|
|
3904
4097
|
const savedOctave = octave;
|
|
3905
4098
|
while (j < body.length && body[j] !== "]") {
|
|
3906
4099
|
const c = body[j];
|
|
3907
|
-
if (
|
|
3908
|
-
let
|
|
4100
|
+
if (isNatural(c)) {
|
|
4101
|
+
let pitchSteps = naturals[c];
|
|
3909
4102
|
j++;
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
j++;
|
|
3913
|
-
} else if (j < body.length && body[j] === "-") {
|
|
3914
|
-
pitch--;
|
|
3915
|
-
j++;
|
|
3916
|
-
}
|
|
3917
|
-
chordNotes.push((octave + 1) * 12 + pitch);
|
|
4103
|
+
pitchSteps += readAccidentals();
|
|
4104
|
+
chordNotes.push((octave + 1) * 372 + pitchSteps * unitsPerStep2);
|
|
3918
4105
|
} else if (c === ">") {
|
|
3919
4106
|
octave = Math.min(8, octave + 1);
|
|
3920
4107
|
j++;
|
|
@@ -3940,7 +4127,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
3940
4127
|
placements.push({
|
|
3941
4128
|
trackIndex,
|
|
3942
4129
|
startStep: currentStep,
|
|
3943
|
-
|
|
4130
|
+
pitchUnits: p,
|
|
3944
4131
|
durationSteps: Math.max(1, steps),
|
|
3945
4132
|
velocity
|
|
3946
4133
|
});
|
|
@@ -3948,23 +4135,17 @@ var parseMML = (mml, options = {}) => {
|
|
|
3948
4135
|
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
3949
4136
|
currentStep += steps;
|
|
3950
4137
|
octave = savedOctave;
|
|
3951
|
-
} else if (
|
|
3952
|
-
let
|
|
4138
|
+
} else if (isNatural(ch)) {
|
|
4139
|
+
let pitchSteps = naturals[ch];
|
|
3953
4140
|
j++;
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
j++;
|
|
3957
|
-
} else if (j < body.length && body[j] === "-") {
|
|
3958
|
-
pitch--;
|
|
3959
|
-
j++;
|
|
3960
|
-
}
|
|
3961
|
-
const midiPitch = (octave + 1) * 12 + pitch;
|
|
4141
|
+
pitchSteps += readAccidentals();
|
|
4142
|
+
const midiPitch = (octave + 1) * 372 + pitchSteps * unitsPerStep2;
|
|
3962
4143
|
const steps = parseLength();
|
|
3963
4144
|
recordContributor();
|
|
3964
4145
|
placements.push({
|
|
3965
4146
|
trackIndex,
|
|
3966
4147
|
startStep: currentStep,
|
|
3967
|
-
|
|
4148
|
+
pitchUnits: midiPitch,
|
|
3968
4149
|
durationSteps: Math.max(1, steps),
|
|
3969
4150
|
velocity
|
|
3970
4151
|
});
|
|
@@ -4096,7 +4277,7 @@ var createSequencer = (options) => {
|
|
|
4096
4277
|
maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
|
|
4097
4278
|
timeline.push({
|
|
4098
4279
|
trackId: track.id,
|
|
4099
|
-
pitch: note.
|
|
4280
|
+
pitch: note.pitchUnits,
|
|
4100
4281
|
volume: track.volume / 100,
|
|
4101
4282
|
velocity: note.velocity ?? DEFAULT_PLAYBACK_VELOCITY,
|
|
4102
4283
|
when,
|
|
@@ -4159,7 +4340,7 @@ var createSequencer = (options) => {
|
|
|
4159
4340
|
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
4160
4341
|
options.onPlayNote({
|
|
4161
4342
|
trackId: ev.trackId,
|
|
4162
|
-
|
|
4343
|
+
pitchUnits: ev.pitch,
|
|
4163
4344
|
velocity: ev.velocity,
|
|
4164
4345
|
volume: currentVolume * velocityVolume,
|
|
4165
4346
|
when: Math.max(0, _when),
|
|
@@ -6479,7 +6660,7 @@ var SONG_DRUM_PATTERNS = {
|
|
|
6479
6660
|
};
|
|
6480
6661
|
|
|
6481
6662
|
// src/synth.ts
|
|
6482
|
-
var freqFromPitch = (
|
|
6663
|
+
var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
|
|
6483
6664
|
var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
6484
6665
|
const wave = tone.wave ?? "square";
|
|
6485
6666
|
const attack = tone.attack ?? 0;
|
|
@@ -6495,7 +6676,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
|
6495
6676
|
const osc = ctx.createOscillator();
|
|
6496
6677
|
const gain = ctx.createGain();
|
|
6497
6678
|
osc.type = wave;
|
|
6498
|
-
osc.frequency.value = freqFromPitch(e.
|
|
6679
|
+
osc.frequency.value = freqFromPitch(e.pitchUnits);
|
|
6499
6680
|
const t0 = ctx.currentTime + e.when;
|
|
6500
6681
|
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
|
|
6501
6682
|
if (tone.decay) {
|
|
@@ -6600,7 +6781,7 @@ var playPlacements = (placements, options) => {
|
|
|
6600
6781
|
id: id++,
|
|
6601
6782
|
startStep: p.startStep,
|
|
6602
6783
|
durationSteps: p.durationSteps,
|
|
6603
|
-
|
|
6784
|
+
pitchUnits: p.pitchUnits,
|
|
6604
6785
|
velocity: p.velocity
|
|
6605
6786
|
}));
|
|
6606
6787
|
return {
|
|
@@ -6771,7 +6952,7 @@ var playNote = (options) => {
|
|
|
6771
6952
|
const dur = options.duration ?? 1;
|
|
6772
6953
|
synth.playNote({
|
|
6773
6954
|
trackId: "melody",
|
|
6774
|
-
|
|
6955
|
+
pitchUnits: options.pitchUnits,
|
|
6775
6956
|
velocity: 100,
|
|
6776
6957
|
volume: vol / 100,
|
|
6777
6958
|
when: 0,
|
|
@@ -6792,7 +6973,7 @@ var playChords = (chordStr, options = {}) => {
|
|
|
6792
6973
|
// 伴奏トラック
|
|
6793
6974
|
startStep: p.startStep,
|
|
6794
6975
|
durationSteps: p.durationSteps,
|
|
6795
|
-
|
|
6976
|
+
pitchUnits: p.pitchUnits,
|
|
6796
6977
|
velocity: p.velocity
|
|
6797
6978
|
}));
|
|
6798
6979
|
return playPlacements(placements, {
|
|
@@ -8949,7 +9130,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8949
9130
|
0
|
|
8950
9131
|
);
|
|
8951
9132
|
const timedNotes = placements.map((p) => ({
|
|
8952
|
-
pitch: p.
|
|
9133
|
+
pitch: unitsToPitchV1(p.pitchUnits),
|
|
8953
9134
|
when: p.startStep * secondsPerStep,
|
|
8954
9135
|
duration: p.durationSteps * secondsPerStep
|
|
8955
9136
|
}));
|
|
@@ -8982,7 +9163,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8982
9163
|
id: id++,
|
|
8983
9164
|
startStep: p.startStep,
|
|
8984
9165
|
durationSteps: p.durationSteps,
|
|
8985
|
-
|
|
9166
|
+
pitchUnits: p.pitchUnits,
|
|
8986
9167
|
velocity: DEFAULT_VELOCITY
|
|
8987
9168
|
}));
|
|
8988
9169
|
return {
|
|
@@ -9807,7 +9988,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9807
9988
|
(a, b) => a.startStep - b.startStep
|
|
9808
9989
|
);
|
|
9809
9990
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
9810
|
-
const semis = (lt.octave ?? 0) *
|
|
9991
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
9811
9992
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
9812
9993
|
const notes = [];
|
|
9813
9994
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -9815,7 +9996,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9815
9996
|
if (n.startStep < fromStep) continue;
|
|
9816
9997
|
notes.push({
|
|
9817
9998
|
syllable: lt.syllables[i2],
|
|
9818
|
-
pitch: n.
|
|
9999
|
+
pitch: n.pitchUnits + semis,
|
|
9819
10000
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
9820
10001
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
9821
10002
|
});
|
|
@@ -10090,6 +10271,7 @@ var SoundFont = class _SoundFont {
|
|
|
10090
10271
|
pitch = 60,
|
|
10091
10272
|
volume = 1,
|
|
10092
10273
|
velocity,
|
|
10274
|
+
detuneCents = 0,
|
|
10093
10275
|
when = 0,
|
|
10094
10276
|
duration = 1
|
|
10095
10277
|
} = {}) {
|
|
@@ -10109,7 +10291,7 @@ var SoundFont = class _SoundFont {
|
|
|
10109
10291
|
Object.assign(src, _param.src);
|
|
10110
10292
|
const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
|
|
10111
10293
|
const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
|
|
10112
|
-
src.detune.setValueAtTime(humanizeCents, 0);
|
|
10294
|
+
src.detune.setValueAtTime(humanizeCents + detuneCents, 0);
|
|
10113
10295
|
const effectiveVolume = volume * humanizeGainMul;
|
|
10114
10296
|
const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
|
|
10115
10297
|
const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
|
|
@@ -11248,7 +11430,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11248
11430
|
scheduleMetronomeBeats(ctx, cutGain);
|
|
11249
11431
|
};
|
|
11250
11432
|
const wafPlayDynamic = ({
|
|
11251
|
-
|
|
11433
|
+
pitchUnits,
|
|
11252
11434
|
velocity,
|
|
11253
11435
|
volume,
|
|
11254
11436
|
when,
|
|
@@ -11258,10 +11440,12 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11258
11440
|
}) => {
|
|
11259
11441
|
const waf = activeGmName ? _wafCache.get(activeGmName) : null;
|
|
11260
11442
|
if (waf) {
|
|
11443
|
+
const { midi, detuneCents } = unitsToMidiDetune(pitchUnits);
|
|
11261
11444
|
waf.play({
|
|
11262
11445
|
ctx,
|
|
11263
11446
|
destination,
|
|
11264
|
-
pitch,
|
|
11447
|
+
pitch: midi,
|
|
11448
|
+
detuneCents,
|
|
11265
11449
|
volume: volume * 0.85,
|
|
11266
11450
|
velocity,
|
|
11267
11451
|
when,
|
|
@@ -11271,7 +11455,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11271
11455
|
const synth = createSynth(ctx, destination);
|
|
11272
11456
|
synth.playNote({
|
|
11273
11457
|
trackId: "chord",
|
|
11274
|
-
|
|
11458
|
+
pitchUnits,
|
|
11275
11459
|
velocity: 100,
|
|
11276
11460
|
volume,
|
|
11277
11461
|
when,
|
|
@@ -11300,7 +11484,8 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11300
11484
|
trackIndex: 3,
|
|
11301
11485
|
startStep: whenStep,
|
|
11302
11486
|
durationSteps,
|
|
11303
|
-
|
|
11487
|
+
// chord-parser の構成音は12平均律の半音。内部表現の units へ写す。
|
|
11488
|
+
pitchUnits: pitchV1ToUnits(C32 + noteOffset),
|
|
11304
11489
|
velocity: 100
|
|
11305
11490
|
});
|
|
11306
11491
|
}
|
|
@@ -11313,7 +11498,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11313
11498
|
synth: false,
|
|
11314
11499
|
onPlayNote: (e) => {
|
|
11315
11500
|
wafPlayDynamic({
|
|
11316
|
-
|
|
11501
|
+
pitchUnits: e.pitchUnits,
|
|
11317
11502
|
velocity: e.velocity,
|
|
11318
11503
|
volume: e.volume,
|
|
11319
11504
|
when: e.when,
|
|
@@ -11755,6 +11940,14 @@ var buildUI = (target, options) => {
|
|
|
11755
11940
|
<summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
|
|
11756
11941
|
<div class="dtm-panel-body">
|
|
11757
11942
|
<div data-dtm="preset-select-slot"></div>
|
|
11943
|
+
<div class="dtm-row">
|
|
11944
|
+
<span class="dtm-label">\u97F3\u5F8B</span>
|
|
11945
|
+
<select class="dtm-select dtm-grow" data-dtm="edo-select">
|
|
11946
|
+
<option value="12">12\u5E73\u5747\u5F8B\uFF08\u901A\u5E38\uFF09</option>
|
|
11947
|
+
<option value="31">31\u5E73\u5747\u5F8B\uFF08\u5FAE\u5206\u97F3\uFF09</option>
|
|
11948
|
+
</select>
|
|
11949
|
+
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
11950
|
+
</div>
|
|
11758
11951
|
<div class="dtm-row">
|
|
11759
11952
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
11760
11953
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -12087,6 +12280,8 @@ var buildUI = (target, options) => {
|
|
|
12087
12280
|
copyMiniBtn: sel("copy-mini"),
|
|
12088
12281
|
overlay: sel("overlay"),
|
|
12089
12282
|
mmlInfoBtn: sel("mml-info"),
|
|
12283
|
+
edoSelect: sel("edo-select"),
|
|
12284
|
+
edoInfoBtn: sel("edo-info"),
|
|
12090
12285
|
modalOverlay: sel("modal-overlay"),
|
|
12091
12286
|
modalTitle: sel("modal-title"),
|
|
12092
12287
|
modalBody: sel("modal-body"),
|
|
@@ -12207,17 +12402,21 @@ var SCALES = [
|
|
|
12207
12402
|
[0, 2, 4, 7, 9]
|
|
12208
12403
|
// Pentatonic Major
|
|
12209
12404
|
];
|
|
12405
|
+
var MEANTONE_STEP_BY_SEMITONE2 = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
12210
12406
|
var generateRandomPattern = (core, options) => {
|
|
12211
|
-
const { stepsPerBar, startStep, pitchRangeStart } = options;
|
|
12407
|
+
const { stepsPerBar, startStep, pitchRangeStart, edo = 12 } = options;
|
|
12212
12408
|
const numBars = 8;
|
|
12213
12409
|
const noteLength = 24;
|
|
12214
|
-
const
|
|
12410
|
+
const semitoneToStep = (semi) => edo === 31 ? MEANTONE_STEP_BY_SEMITONE2[semi] : semi;
|
|
12411
|
+
const upr = UNITS_PER_OCTAVE / edo;
|
|
12412
|
+
const basePitch = pitchRangeStart + 60 * UNITS_PER_SEMITONE;
|
|
12215
12413
|
const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
|
|
12216
12414
|
const rootOffset = Math.floor(Math.random() * 12);
|
|
12217
12415
|
const availablePitches = [];
|
|
12218
12416
|
for (let i2 = 0; i2 < 12; i2++) {
|
|
12219
12417
|
const noteInOctave = (i2 - rootOffset + 12) % 12;
|
|
12220
|
-
if (scale.includes(noteInOctave))
|
|
12418
|
+
if (scale.includes(noteInOctave))
|
|
12419
|
+
availablePitches.push(basePitch + semitoneToStep(i2) * upr);
|
|
12221
12420
|
}
|
|
12222
12421
|
core.beginBatch();
|
|
12223
12422
|
for (let bar = 0; bar < numBars; bar++) {
|
|
@@ -12252,14 +12451,17 @@ var applyHarmonicFilter = (targetCore, chordCore, options) => {
|
|
|
12252
12451
|
const isNewBar = halfBar % 2 === 0;
|
|
12253
12452
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12254
12453
|
if (chordHere.length > 0) {
|
|
12255
|
-
currentClasses = new Set(
|
|
12454
|
+
currentClasses = new Set(
|
|
12455
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12456
|
+
);
|
|
12256
12457
|
} else if (isNewBar) {
|
|
12257
12458
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12258
12459
|
}
|
|
12259
12460
|
if (currentClasses.size === 0) continue;
|
|
12260
12461
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12261
12462
|
for (const n of activeHere) {
|
|
12262
|
-
if (!currentClasses.has(n.
|
|
12463
|
+
if (!currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE))
|
|
12464
|
+
targetCore.deleteNoteById(n.id);
|
|
12263
12465
|
}
|
|
12264
12466
|
}
|
|
12265
12467
|
targetCore.endBatch();
|
|
@@ -12281,13 +12483,17 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12281
12483
|
const isNewBar = halfBar % 2 === 0;
|
|
12282
12484
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12283
12485
|
if (chordHere.length > 0) {
|
|
12284
|
-
currentClasses = new Set(
|
|
12486
|
+
currentClasses = new Set(
|
|
12487
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12488
|
+
);
|
|
12285
12489
|
} else if (isNewBar) {
|
|
12286
12490
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12287
12491
|
}
|
|
12288
12492
|
if (currentClasses.size === 0) continue;
|
|
12289
12493
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12290
|
-
const filtered = activeHere.filter(
|
|
12494
|
+
const filtered = activeHere.filter(
|
|
12495
|
+
(n) => currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12496
|
+
);
|
|
12291
12497
|
const filteredIds = new Set(filtered.map((n) => n.id));
|
|
12292
12498
|
for (const n of activeHere) {
|
|
12293
12499
|
if (!filteredIds.has(n.id)) targetCore.deleteNoteById(n.id);
|
|
@@ -12299,7 +12505,7 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12299
12505
|
}
|
|
12300
12506
|
for (const notesAtTime of timeMap.values()) {
|
|
12301
12507
|
if (notesAtTime.length > 1) {
|
|
12302
|
-
notesAtTime.sort((a, b) => b.
|
|
12508
|
+
notesAtTime.sort((a, b) => b.pitchUnits - a.pitchUnits);
|
|
12303
12509
|
const [, ...others] = notesAtTime;
|
|
12304
12510
|
for (const on of others) targetCore.deleteNoteById(on.id);
|
|
12305
12511
|
}
|
|
@@ -12315,18 +12521,21 @@ var shiftNotes = (cores, shiftSteps) => {
|
|
|
12315
12521
|
for (const note of notes) {
|
|
12316
12522
|
const newStart = note.startStep + shiftSteps;
|
|
12317
12523
|
if (newStart < 0) core.deleteNoteById(note.id);
|
|
12318
|
-
else core.moveNote(note.id, newStart, note.
|
|
12524
|
+
else core.moveNote(note.id, newStart, note.pitchUnits);
|
|
12319
12525
|
}
|
|
12320
12526
|
core.saveHistory();
|
|
12321
12527
|
}
|
|
12322
12528
|
};
|
|
12323
|
-
var transposeNotes = (cores,
|
|
12324
|
-
if (
|
|
12529
|
+
var transposeNotes = (cores, steps) => {
|
|
12530
|
+
if (steps === 0) return;
|
|
12325
12531
|
for (const core of cores) {
|
|
12326
12532
|
const notes = [...core.getNotes()];
|
|
12327
12533
|
for (const note of notes) {
|
|
12328
|
-
const newPitch = Math.max(
|
|
12329
|
-
|
|
12534
|
+
const newPitch = Math.max(
|
|
12535
|
+
PITCH_RANGE_START,
|
|
12536
|
+
Math.min(PITCH_RANGE_END, note.pitchUnits + steps)
|
|
12537
|
+
);
|
|
12538
|
+
if (newPitch !== note.pitchUnits) {
|
|
12330
12539
|
core.moveNote(note.id, note.startStep, newPitch);
|
|
12331
12540
|
}
|
|
12332
12541
|
}
|
|
@@ -12651,11 +12860,31 @@ var exportMIDI = (options) => {
|
|
|
12651
12860
|
const div = 480;
|
|
12652
12861
|
const tickPerStep = div / STEPS_PER_BEAT3;
|
|
12653
12862
|
const midiTracks = [];
|
|
12654
|
-
|
|
12863
|
+
const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
|
|
12864
|
+
const BEND_RANGE_SEMITONES = 2;
|
|
12865
|
+
const bendKey = (cents) => Math.round(cents * 100);
|
|
12866
|
+
const bendUse = /* @__PURE__ */ new Map();
|
|
12867
|
+
for (const track of tracks) {
|
|
12868
|
+
for (const n of track.notes) {
|
|
12869
|
+
const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12870
|
+
const k = bendKey(detuneCents);
|
|
12871
|
+
bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
|
|
12872
|
+
}
|
|
12873
|
+
}
|
|
12874
|
+
const bendChannel = /* @__PURE__ */ new Map();
|
|
12875
|
+
const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
|
|
12876
|
+
for (const [k] of ordered) {
|
|
12877
|
+
if (bendChannel.size >= NOTE_CHANNELS.length) break;
|
|
12878
|
+
bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
|
|
12879
|
+
}
|
|
12880
|
+
tracks.forEach((track) => {
|
|
12655
12881
|
if (track.notes.length === 0) return;
|
|
12656
|
-
const channel = ch < 9 ? ch : ch + 1 & 15;
|
|
12657
12882
|
const events = [];
|
|
12658
12883
|
for (const n of track.notes) {
|
|
12884
|
+
const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12885
|
+
const k = bendKey(detuneCents);
|
|
12886
|
+
const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
|
|
12887
|
+
const note = Math.max(0, Math.min(127, midi));
|
|
12659
12888
|
const startTick = Math.round(n.startStep * tickPerStep);
|
|
12660
12889
|
const endTick = Math.round(
|
|
12661
12890
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
@@ -12663,12 +12892,25 @@ var exportMIDI = (options) => {
|
|
|
12663
12892
|
const vel = Math.round(
|
|
12664
12893
|
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
12665
12894
|
);
|
|
12666
|
-
events.push({ t: startTick, m: [144 | channel,
|
|
12667
|
-
events.push({ t: endTick, m: [144 | channel,
|
|
12895
|
+
events.push({ t: startTick, m: [144 | channel, note, vel] });
|
|
12896
|
+
events.push({ t: endTick, m: [144 | channel, note, 0] });
|
|
12668
12897
|
}
|
|
12669
12898
|
events.sort((a, b) => a.t - b.t);
|
|
12670
12899
|
midiTracks.push(events);
|
|
12671
12900
|
});
|
|
12901
|
+
const bendSetup = [];
|
|
12902
|
+
for (const [k, channel] of bendChannel) {
|
|
12903
|
+
const cents = k / 100;
|
|
12904
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
|
|
12905
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
|
|
12906
|
+
bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
|
|
12907
|
+
bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
|
|
12908
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
|
|
12909
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
|
|
12910
|
+
const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
|
|
12911
|
+
const v = Math.max(0, Math.min(16383, raw));
|
|
12912
|
+
bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
|
|
12913
|
+
}
|
|
12672
12914
|
const maxStep = Math.max(
|
|
12673
12915
|
...tracks.filter((t) => t.notes.length > 0).map(
|
|
12674
12916
|
(t) => Math.max(...t.notes.map((n) => n.startStep + n.durationSteps))
|
|
@@ -12705,6 +12947,7 @@ var exportMIDI = (options) => {
|
|
|
12705
12947
|
headerChunks(arr, midiTracks.length + 1, div);
|
|
12706
12948
|
trackChunks(arr, (a) => {
|
|
12707
12949
|
a.push(0, 255, 81, 3, ...to3byte(Math.round(6e7 / bpm)));
|
|
12950
|
+
for (const ev of bendSetup) a.push(0, ...ev.m);
|
|
12708
12951
|
});
|
|
12709
12952
|
for (const events of midiTracks) {
|
|
12710
12953
|
trackChunks(arr, (a) => {
|
|
@@ -12951,403 +13194,8 @@ var LinkedList = class {
|
|
|
12951
13194
|
}
|
|
12952
13195
|
};
|
|
12953
13196
|
|
|
12954
|
-
// src/renderer.ts
|
|
12955
|
-
var g_header_canvas;
|
|
12956
|
-
var g_key_canvas;
|
|
12957
|
-
var g_grid_canvas;
|
|
12958
|
-
var g_header_ctx;
|
|
12959
|
-
var g_key_ctx;
|
|
12960
|
-
var g_grid_ctx;
|
|
12961
|
-
var g_config;
|
|
12962
|
-
var KEYBOARD_WIDTH = 60;
|
|
12963
|
-
var HEADER_HEIGHT = 20;
|
|
12964
|
-
var getRenderConfig = () => g_config;
|
|
12965
|
-
var g_draw_offset_x = 0;
|
|
12966
|
-
var g_draw_offset_y = 0;
|
|
12967
|
-
var g_bg_active = false;
|
|
12968
|
-
var setBackgroundActive = (active) => {
|
|
12969
|
-
g_bg_active = active;
|
|
12970
|
-
};
|
|
12971
|
-
var getDrawOffset = () => ({
|
|
12972
|
-
x: g_draw_offset_x,
|
|
12973
|
-
y: g_draw_offset_y
|
|
12974
|
-
});
|
|
12975
|
-
var getGridCanvas = () => g_grid_canvas;
|
|
12976
|
-
var getGridContext = () => g_grid_ctx;
|
|
12977
|
-
var getHeaderCanvas = () => g_header_canvas;
|
|
12978
|
-
var init = (mountTarget, width = 800, height = 450, config) => {
|
|
12979
|
-
g_config = config;
|
|
12980
|
-
const headerCanvas = document.createElement("canvas");
|
|
12981
|
-
g_header_canvas = headerCanvas;
|
|
12982
|
-
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
12983
|
-
headerCanvas.height = HEADER_HEIGHT;
|
|
12984
|
-
headerCanvas.style.position = "absolute";
|
|
12985
|
-
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
12986
|
-
headerCanvas.style.top = "0px";
|
|
12987
|
-
const headerCtx = headerCanvas.getContext("2d");
|
|
12988
|
-
if (!headerCtx)
|
|
12989
|
-
throw new Error("Failed to get 2D rendering context for header.");
|
|
12990
|
-
g_header_ctx = headerCtx;
|
|
12991
|
-
const keyCanvas = document.createElement("canvas");
|
|
12992
|
-
g_key_canvas = keyCanvas;
|
|
12993
|
-
keyCanvas.width = KEYBOARD_WIDTH;
|
|
12994
|
-
keyCanvas.height = height - HEADER_HEIGHT;
|
|
12995
|
-
keyCanvas.style.position = "absolute";
|
|
12996
|
-
keyCanvas.style.left = "0px";
|
|
12997
|
-
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
12998
|
-
const keyCtx = keyCanvas.getContext("2d");
|
|
12999
|
-
if (!keyCtx)
|
|
13000
|
-
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
13001
|
-
g_key_ctx = keyCtx;
|
|
13002
|
-
const gridCanvas = document.createElement("canvas");
|
|
13003
|
-
g_grid_canvas = gridCanvas;
|
|
13004
|
-
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
13005
|
-
gridCanvas.height = height - HEADER_HEIGHT;
|
|
13006
|
-
gridCanvas.style.position = "absolute";
|
|
13007
|
-
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13008
|
-
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13009
|
-
gridCanvas.style.touchAction = "none";
|
|
13010
|
-
gridCanvas.style.userSelect = "none";
|
|
13011
|
-
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
13012
|
-
if (!gridCtx) throw new Error("Failed to get 2D rendering context for grid.");
|
|
13013
|
-
g_grid_ctx = gridCtx;
|
|
13014
|
-
mountTarget.innerHTML = "";
|
|
13015
|
-
mountTarget.style.position = "relative";
|
|
13016
|
-
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
13017
|
-
mountTarget.style.height = `${height}px`;
|
|
13018
|
-
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
13019
|
-
drawHeaderCorner();
|
|
13020
|
-
};
|
|
13021
|
-
var blackKeyPitches = /* @__PURE__ */ new Set([1, 3, 6, 8, 10]);
|
|
13022
|
-
var KEY_NAMES = [
|
|
13023
|
-
"C",
|
|
13024
|
-
"C#",
|
|
13025
|
-
"D",
|
|
13026
|
-
"D#",
|
|
13027
|
-
"E",
|
|
13028
|
-
"F",
|
|
13029
|
-
"F#",
|
|
13030
|
-
"G",
|
|
13031
|
-
"G#",
|
|
13032
|
-
"A",
|
|
13033
|
-
"A#",
|
|
13034
|
-
"B"
|
|
13035
|
-
];
|
|
13036
|
-
var drawHeaderCorner = () => {
|
|
13037
|
-
const mountTarget = g_key_canvas.parentElement;
|
|
13038
|
-
if (!mountTarget) return;
|
|
13039
|
-
let cornerDiv = mountTarget.querySelector("#header-corner");
|
|
13040
|
-
if (!cornerDiv) {
|
|
13041
|
-
cornerDiv = document.createElement("div");
|
|
13042
|
-
cornerDiv.id = "header-corner";
|
|
13043
|
-
cornerDiv.style.position = "absolute";
|
|
13044
|
-
cornerDiv.style.left = "0px";
|
|
13045
|
-
cornerDiv.style.top = "0px";
|
|
13046
|
-
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
13047
|
-
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
13048
|
-
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
13049
|
-
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
13050
|
-
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
13051
|
-
mountTarget.insertBefore(cornerDiv, g_header_canvas);
|
|
13052
|
-
}
|
|
13053
|
-
};
|
|
13054
|
-
var drawKeyboard = () => {
|
|
13055
|
-
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
13056
|
-
const { keyHeight, keyCount, pitchRangeStart } = g_config;
|
|
13057
|
-
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13058
|
-
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
13059
|
-
const WHITE_KEY = "#ccc8b4";
|
|
13060
|
-
const BLACK_KEY = "#111111";
|
|
13061
|
-
const BK_EDGE = "#383838";
|
|
13062
|
-
const WW_SEP = "#807a6a";
|
|
13063
|
-
const BK_RATIO = 0.62;
|
|
13064
|
-
for (let y = startY; y < endY; y += keyHeight) {
|
|
13065
|
-
const pitchIndex = keyCount - 1 - y / keyHeight;
|
|
13066
|
-
const totalPitch = pitchIndex + pitchRangeStart;
|
|
13067
|
-
const pitchMod12 = totalPitch % 12;
|
|
13068
|
-
const isBlackKey = blackKeyPitches.has(pitchMod12);
|
|
13069
|
-
const octave = Math.floor(totalPitch / 12) - 1;
|
|
13070
|
-
const isC4Range = octave === 4;
|
|
13071
|
-
const screenY = y - g_draw_offset_y;
|
|
13072
|
-
const bkW = Math.floor(KEYBOARD_WIDTH * BK_RATIO);
|
|
13073
|
-
if (isBlackKey) {
|
|
13074
|
-
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
13075
|
-
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13076
|
-
g_key_ctx.fillStyle = isC4Range ? "#1a1408" : BLACK_KEY;
|
|
13077
|
-
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
13078
|
-
g_key_ctx.strokeStyle = BK_EDGE;
|
|
13079
|
-
g_key_ctx.lineWidth = 1;
|
|
13080
|
-
g_key_ctx.beginPath();
|
|
13081
|
-
g_key_ctx.moveTo(bkW, screenY);
|
|
13082
|
-
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
13083
|
-
g_key_ctx.stroke();
|
|
13084
|
-
} else {
|
|
13085
|
-
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
13086
|
-
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13087
|
-
if (pitchMod12 === 5 || pitchMod12 === 0) {
|
|
13088
|
-
g_key_ctx.strokeStyle = WW_SEP;
|
|
13089
|
-
g_key_ctx.lineWidth = 1;
|
|
13090
|
-
g_key_ctx.beginPath();
|
|
13091
|
-
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
13092
|
-
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
13093
|
-
g_key_ctx.stroke();
|
|
13094
|
-
}
|
|
13095
|
-
}
|
|
13096
|
-
if (pitchMod12 === 0) {
|
|
13097
|
-
const octave2 = Math.floor(totalPitch / 12) - 1;
|
|
13098
|
-
g_key_ctx.fillStyle = "#555040";
|
|
13099
|
-
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
13100
|
-
g_key_ctx.textAlign = "right";
|
|
13101
|
-
g_key_ctx.textBaseline = "bottom";
|
|
13102
|
-
g_key_ctx.fillText(
|
|
13103
|
-
`${KEY_NAMES[pitchMod12]}${octave2}`,
|
|
13104
|
-
KEYBOARD_WIDTH - 4,
|
|
13105
|
-
screenY + keyHeight - 2
|
|
13106
|
-
);
|
|
13107
|
-
}
|
|
13108
|
-
}
|
|
13109
|
-
g_key_ctx.beginPath();
|
|
13110
|
-
g_key_ctx.strokeStyle = "#29adff";
|
|
13111
|
-
g_key_ctx.lineWidth = 2;
|
|
13112
|
-
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
13113
|
-
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
13114
|
-
g_key_ctx.stroke();
|
|
13115
|
-
};
|
|
13116
|
-
var drawHeader = () => {
|
|
13117
|
-
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
13118
|
-
const { stepWidth, stepsPerBar } = g_config;
|
|
13119
|
-
g_header_ctx.save();
|
|
13120
|
-
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
13121
|
-
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
13122
|
-
g_header_ctx.fillRect(
|
|
13123
|
-
g_draw_offset_x,
|
|
13124
|
-
0,
|
|
13125
|
-
g_header_canvas.width,
|
|
13126
|
-
HEADER_HEIGHT
|
|
13127
|
-
);
|
|
13128
|
-
g_header_ctx.strokeStyle = "#3d405b";
|
|
13129
|
-
g_header_ctx.lineWidth = 1;
|
|
13130
|
-
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
13131
|
-
g_header_ctx.fillStyle = "#83769c";
|
|
13132
|
-
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
13133
|
-
const endBar = Math.ceil(
|
|
13134
|
-
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13135
|
-
);
|
|
13136
|
-
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13137
|
-
const x2 = bar * stepsPerBar * stepWidth;
|
|
13138
|
-
const screenX = x2;
|
|
13139
|
-
g_header_ctx.beginPath();
|
|
13140
|
-
g_header_ctx.moveTo(screenX, 0);
|
|
13141
|
-
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13142
|
-
g_header_ctx.stroke();
|
|
13143
|
-
if (bar >= 0) {
|
|
13144
|
-
g_header_ctx.textAlign = "left";
|
|
13145
|
-
g_header_ctx.textBaseline = "middle";
|
|
13146
|
-
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13147
|
-
}
|
|
13148
|
-
}
|
|
13149
|
-
g_header_ctx.restore();
|
|
13150
|
-
};
|
|
13151
|
-
var drawGrid = (noteLengthSteps = 1) => {
|
|
13152
|
-
drawKeyboard();
|
|
13153
|
-
drawHeader();
|
|
13154
|
-
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13155
|
-
const { keyHeight, keyCount, stepWidth, stepsPerBar, pitchRangeStart } = g_config;
|
|
13156
|
-
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13157
|
-
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13158
|
-
for (let y = startY; y < endY; y += keyHeight) {
|
|
13159
|
-
const pitchIndex = keyCount - 1 - y / keyHeight;
|
|
13160
|
-
const totalPitch = pitchIndex + pitchRangeStart;
|
|
13161
|
-
const pitchMod12 = totalPitch % 12;
|
|
13162
|
-
const isBlackKey = blackKeyPitches.has(pitchMod12);
|
|
13163
|
-
const isC = pitchMod12 === 0;
|
|
13164
|
-
const octave = Math.floor(totalPitch / 12) - 1;
|
|
13165
|
-
const isC4Range = octave === 4;
|
|
13166
|
-
const screenY = y - g_draw_offset_y;
|
|
13167
|
-
g_grid_ctx.fillStyle = g_bg_active ? isBlackKey ? "rgba(8,11,22,0.55)" : "rgba(17,22,40,0.45)" : isBlackKey ? "#080b16" : "#111628";
|
|
13168
|
-
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13169
|
-
if (isC4Range) {
|
|
13170
|
-
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13171
|
-
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13172
|
-
}
|
|
13173
|
-
g_grid_ctx.beginPath();
|
|
13174
|
-
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13175
|
-
g_grid_ctx.lineWidth = 1;
|
|
13176
|
-
const lineY = screenY + keyHeight;
|
|
13177
|
-
g_grid_ctx.moveTo(0, lineY);
|
|
13178
|
-
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13179
|
-
g_grid_ctx.stroke();
|
|
13180
|
-
}
|
|
13181
|
-
const gridStep = noteLengthSteps || 48;
|
|
13182
|
-
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13183
|
-
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13184
|
-
const lineStep = stepWidth * gridStep;
|
|
13185
|
-
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13186
|
-
const step = x2 / stepWidth;
|
|
13187
|
-
const isBarLine = step % stepsPerBar === 0;
|
|
13188
|
-
const isNoteLine = step % gridStep === 0;
|
|
13189
|
-
const screenX = x2 - g_draw_offset_x;
|
|
13190
|
-
g_grid_ctx.beginPath();
|
|
13191
|
-
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13192
|
-
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13193
|
-
g_grid_ctx.moveTo(screenX, 0);
|
|
13194
|
-
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13195
|
-
g_grid_ctx.stroke();
|
|
13196
|
-
}
|
|
13197
|
-
};
|
|
13198
|
-
var drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13199
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13200
|
-
const canvasWidth = g_grid_canvas.width;
|
|
13201
|
-
const canvasHeight = g_grid_canvas.height;
|
|
13202
|
-
const [r, g, b, a] = color;
|
|
13203
|
-
for (const note of notes) {
|
|
13204
|
-
const logicalX = note.startStep * stepWidth;
|
|
13205
|
-
const yIndex = keyCount - 1 - (note.pitch - pitchRangeStart);
|
|
13206
|
-
const logicalY = yIndex * keyHeight;
|
|
13207
|
-
const w = note.durationSteps * stepWidth;
|
|
13208
|
-
const h = keyHeight;
|
|
13209
|
-
const renderX = logicalX - g_draw_offset_x;
|
|
13210
|
-
const renderY = logicalY - g_draw_offset_y;
|
|
13211
|
-
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13212
|
-
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13213
|
-
if (isActive) {
|
|
13214
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13215
|
-
const finalOpacity = a * velocityOpacity;
|
|
13216
|
-
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13217
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13218
|
-
if (w >= 4 && h >= 4) {
|
|
13219
|
-
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13220
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13221
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13222
|
-
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13223
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13224
|
-
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13225
|
-
}
|
|
13226
|
-
} else {
|
|
13227
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13228
|
-
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13229
|
-
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13230
|
-
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13231
|
-
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13232
|
-
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13233
|
-
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13234
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13235
|
-
}
|
|
13236
|
-
}
|
|
13237
|
-
};
|
|
13238
|
-
var drawNoteLyrics = (notes, syllables) => {
|
|
13239
|
-
if (syllables.length === 0) return;
|
|
13240
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13241
|
-
if (keyHeight < 10) return;
|
|
13242
|
-
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13243
|
-
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13244
|
-
const count = Math.min(sorted.length, syllables.length);
|
|
13245
|
-
g_grid_ctx.save();
|
|
13246
|
-
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13247
|
-
g_grid_ctx.textAlign = "left";
|
|
13248
|
-
g_grid_ctx.textBaseline = "middle";
|
|
13249
|
-
g_grid_ctx.lineWidth = 3;
|
|
13250
|
-
g_grid_ctx.lineJoin = "round";
|
|
13251
|
-
for (let i2 = 0; i2 < count; i2++) {
|
|
13252
|
-
const kana = syllables[i2];
|
|
13253
|
-
if (!kana) continue;
|
|
13254
|
-
const note = sorted[i2];
|
|
13255
|
-
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
13256
|
-
const renderY = (keyCount - 1 - (note.pitch - pitchRangeStart)) * keyHeight - g_draw_offset_y;
|
|
13257
|
-
const w = note.durationSteps * stepWidth;
|
|
13258
|
-
if (w < 6) continue;
|
|
13259
|
-
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
13260
|
-
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
13261
|
-
const textX = renderX + 2;
|
|
13262
|
-
const textY = renderY + keyHeight / 2;
|
|
13263
|
-
g_grid_ctx.save();
|
|
13264
|
-
g_grid_ctx.beginPath();
|
|
13265
|
-
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
13266
|
-
g_grid_ctx.clip();
|
|
13267
|
-
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
13268
|
-
g_grid_ctx.strokeText(kana, textX, textY);
|
|
13269
|
-
g_grid_ctx.fillStyle = "#fff1e8";
|
|
13270
|
-
g_grid_ctx.fillText(kana, textX, textY);
|
|
13271
|
-
g_grid_ctx.restore();
|
|
13272
|
-
}
|
|
13273
|
-
g_grid_ctx.restore();
|
|
13274
|
-
};
|
|
13275
|
-
var drawSelectionRect = (rect) => {
|
|
13276
|
-
if (!rect) return;
|
|
13277
|
-
g_grid_ctx.save();
|
|
13278
|
-
g_grid_ctx.strokeStyle = "#ffec27";
|
|
13279
|
-
g_grid_ctx.lineWidth = 2;
|
|
13280
|
-
g_grid_ctx.setLineDash([4, 4]);
|
|
13281
|
-
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
13282
|
-
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
13283
|
-
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
13284
|
-
g_grid_ctx.restore();
|
|
13285
|
-
};
|
|
13286
|
-
var drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
13287
|
-
const { keyHeight, stepWidth, keyCount, pitchRangeStart } = g_config;
|
|
13288
|
-
for (const note of notes) {
|
|
13289
|
-
if (!selectedIds.has(note.id)) continue;
|
|
13290
|
-
const logicalX = note.startStep * stepWidth;
|
|
13291
|
-
const yIndex = keyCount - 1 - (note.pitch - pitchRangeStart);
|
|
13292
|
-
const logicalY = yIndex * keyHeight;
|
|
13293
|
-
const w = note.durationSteps * stepWidth;
|
|
13294
|
-
const h = keyHeight;
|
|
13295
|
-
const renderX = logicalX - g_draw_offset_x;
|
|
13296
|
-
const renderY = logicalY - g_draw_offset_y;
|
|
13297
|
-
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
13298
|
-
const [r, g, b, a] = baseColor;
|
|
13299
|
-
const darkenFactor = 1.3;
|
|
13300
|
-
const darkerR = Math.min(255, r * darkenFactor);
|
|
13301
|
-
const darkerG = Math.min(255, g * darkenFactor);
|
|
13302
|
-
const darkerB = Math.min(255, b * darkenFactor);
|
|
13303
|
-
const finalOpacity = a * velocityOpacity;
|
|
13304
|
-
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
13305
|
-
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13306
|
-
}
|
|
13307
|
-
};
|
|
13308
|
-
var getXY = (e) => {
|
|
13309
|
-
const { clientX, clientY } = e;
|
|
13310
|
-
const rect = g_grid_canvas.getBoundingClientRect();
|
|
13311
|
-
const x2 = Math.floor(clientX - rect.left);
|
|
13312
|
-
const y = Math.floor(clientY - rect.top);
|
|
13313
|
-
return [x2, y, e.buttons];
|
|
13314
|
-
};
|
|
13315
|
-
var getGridPosition = (e) => {
|
|
13316
|
-
const [x2, y] = getXY(e);
|
|
13317
|
-
const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
|
|
13318
|
-
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13319
|
-
const absoluteY = y + g_draw_offset_y;
|
|
13320
|
-
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13321
|
-
const pitch = keyCount - 1 - yIndex + pitchRangeStart;
|
|
13322
|
-
return { step, pitch, x: x2, y };
|
|
13323
|
-
};
|
|
13324
|
-
var onClick = (callback) => {
|
|
13325
|
-
g_grid_canvas.addEventListener(
|
|
13326
|
-
"click",
|
|
13327
|
-
(e) => {
|
|
13328
|
-
const [x2, y] = getXY(e);
|
|
13329
|
-
const { keyCount, pitchRangeStart, keyHeight, stepWidth } = g_config;
|
|
13330
|
-
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
13331
|
-
const absoluteY = y + g_draw_offset_y;
|
|
13332
|
-
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
13333
|
-
const pitch = keyCount - 1 - yIndex + pitchRangeStart;
|
|
13334
|
-
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
13335
|
-
requestAnimationFrame(() => callback(step, pitch));
|
|
13336
|
-
}
|
|
13337
|
-
},
|
|
13338
|
-
{ passive: true }
|
|
13339
|
-
);
|
|
13340
|
-
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
13341
|
-
};
|
|
13342
|
-
var setDrawOffset = (x2, y) => {
|
|
13343
|
-
g_draw_offset_x = x2;
|
|
13344
|
-
g_draw_offset_y = y;
|
|
13345
|
-
drawKeyboard();
|
|
13346
|
-
drawHeader();
|
|
13347
|
-
};
|
|
13348
|
-
|
|
13349
13197
|
// src/mml-core.ts
|
|
13350
|
-
var
|
|
13198
|
+
var PITCH_MAP = [
|
|
13351
13199
|
"c",
|
|
13352
13200
|
"c+",
|
|
13353
13201
|
"d",
|
|
@@ -13361,6 +13209,39 @@ var PITCH_MAP2 = [
|
|
|
13361
13209
|
"a+",
|
|
13362
13210
|
"b"
|
|
13363
13211
|
];
|
|
13212
|
+
var EDO31_NAMES = [
|
|
13213
|
+
"c",
|
|
13214
|
+
"c+",
|
|
13215
|
+
"c#",
|
|
13216
|
+
"d-",
|
|
13217
|
+
"d_",
|
|
13218
|
+
"d",
|
|
13219
|
+
"d+",
|
|
13220
|
+
"d#",
|
|
13221
|
+
"e-",
|
|
13222
|
+
"e_",
|
|
13223
|
+
"e",
|
|
13224
|
+
"f-",
|
|
13225
|
+
"e#",
|
|
13226
|
+
"f",
|
|
13227
|
+
"f+",
|
|
13228
|
+
"f#",
|
|
13229
|
+
"g-",
|
|
13230
|
+
"g_",
|
|
13231
|
+
"g",
|
|
13232
|
+
"g+",
|
|
13233
|
+
"g#",
|
|
13234
|
+
"a-",
|
|
13235
|
+
"a_",
|
|
13236
|
+
"a",
|
|
13237
|
+
"a+",
|
|
13238
|
+
"a#",
|
|
13239
|
+
"b-",
|
|
13240
|
+
"b_",
|
|
13241
|
+
"b",
|
|
13242
|
+
"b+",
|
|
13243
|
+
"b#"
|
|
13244
|
+
];
|
|
13364
13245
|
var MMLCore = class _MMLCore {
|
|
13365
13246
|
notes = [];
|
|
13366
13247
|
nextNoteId = 0;
|
|
@@ -13374,9 +13255,12 @@ var MMLCore = class _MMLCore {
|
|
|
13374
13255
|
lastUndoTime = 0;
|
|
13375
13256
|
static UNDO_DEBOUNCE_MS = 100;
|
|
13376
13257
|
toolMode = "pen";
|
|
13377
|
-
|
|
13258
|
+
/** グリッド寸法の供給元(描画器ではなく呼び出し側が持つ設定を読む) */
|
|
13259
|
+
getConfig;
|
|
13260
|
+
constructor(handlers, volume = 80, getConfig) {
|
|
13378
13261
|
this.handlers = handlers;
|
|
13379
13262
|
this.volume = volume;
|
|
13263
|
+
this.getConfig = getConfig;
|
|
13380
13264
|
this.lastHistorySnapshot = JSON.stringify(this.notes);
|
|
13381
13265
|
this.history.add([]);
|
|
13382
13266
|
this.generateAndNotify();
|
|
@@ -13463,14 +13347,14 @@ var MMLCore = class _MMLCore {
|
|
|
13463
13347
|
*/
|
|
13464
13348
|
addNote(step, pitch, options) {
|
|
13465
13349
|
const existingIndex = this.notes.findIndex(
|
|
13466
|
-
(n) => n.startStep === step && n.
|
|
13350
|
+
(n) => n.startStep === step && n.pitchUnits === pitch
|
|
13467
13351
|
);
|
|
13468
13352
|
if (existingIndex === -1) {
|
|
13469
13353
|
const newNote = {
|
|
13470
13354
|
id: this.nextNoteId++,
|
|
13471
13355
|
startStep: step,
|
|
13472
13356
|
durationSteps: options.noteLengthSteps,
|
|
13473
|
-
pitch,
|
|
13357
|
+
pitchUnits: pitch,
|
|
13474
13358
|
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
13475
13359
|
};
|
|
13476
13360
|
this.notes.push(newNote);
|
|
@@ -13498,9 +13382,11 @@ var MMLCore = class _MMLCore {
|
|
|
13498
13382
|
moveNote(noteId, startStep, pitch) {
|
|
13499
13383
|
const note = this.notes.find((target) => target.id === noteId);
|
|
13500
13384
|
if (!note) return;
|
|
13501
|
-
const totalSteps = this.getMaxStep() +
|
|
13502
|
-
const
|
|
13503
|
-
const
|
|
13385
|
+
const totalSteps = this.getMaxStep() + this.getConfig().stepsPerBar;
|
|
13386
|
+
const cfg = this.getConfig();
|
|
13387
|
+
const upr = cfg.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
13388
|
+
const pitchRangeStart = cfg.pitchRangeStart;
|
|
13389
|
+
const pitchRangeEnd = pitchRangeStart + (cfg.keyCount - 1) * upr;
|
|
13504
13390
|
const clampedPitch = Math.min(
|
|
13505
13391
|
Math.max(pitch, pitchRangeStart),
|
|
13506
13392
|
pitchRangeEnd
|
|
@@ -13510,7 +13396,7 @@ var MMLCore = class _MMLCore {
|
|
|
13510
13396
|
totalSteps - note.durationSteps
|
|
13511
13397
|
);
|
|
13512
13398
|
note.startStep = clampedStart;
|
|
13513
|
-
note.
|
|
13399
|
+
note.pitchUnits = clampedPitch;
|
|
13514
13400
|
this.notes.sort((a, b) => a.startStep - b.startStep);
|
|
13515
13401
|
this.generateAndNotify();
|
|
13516
13402
|
}
|
|
@@ -13555,7 +13441,7 @@ var MMLCore = class _MMLCore {
|
|
|
13555
13441
|
* ただし、残りステップ(limit)は絶対に超えない。
|
|
13556
13442
|
*/
|
|
13557
13443
|
stepsToMMLDuration(steps, limit) {
|
|
13558
|
-
const config =
|
|
13444
|
+
const config = this.getConfig();
|
|
13559
13445
|
const total = config.stepsPerBar;
|
|
13560
13446
|
const candidates = [
|
|
13561
13447
|
{ dur: "1.", s: total * 1.5 },
|
|
@@ -13591,7 +13477,7 @@ var MMLCore = class _MMLCore {
|
|
|
13591
13477
|
* ギャップに収まる最大の音符を探す(減算アルゴリズム用)
|
|
13592
13478
|
*/
|
|
13593
13479
|
findBestFitDuration(gap) {
|
|
13594
|
-
const config =
|
|
13480
|
+
const config = this.getConfig();
|
|
13595
13481
|
const durations = [1, 2, 4, 8, 12, 16, 24, 32, 48, 64];
|
|
13596
13482
|
for (const d of durations) {
|
|
13597
13483
|
const stepLen = config.stepsPerBar / d;
|
|
@@ -13601,12 +13487,20 @@ var MMLCore = class _MMLCore {
|
|
|
13601
13487
|
}
|
|
13602
13488
|
return { dur: 64, steps: config.stepsPerBar / 64 };
|
|
13603
13489
|
}
|
|
13490
|
+
/** ピッチ(units) → その音律での「オクターブ番号」と「音名」。 */
|
|
13491
|
+
spell(pitchUnits) {
|
|
13492
|
+
const edo = this.getConfig().edo ?? 12;
|
|
13493
|
+
const names = edo === 31 ? EDO31_NAMES : PITCH_MAP;
|
|
13494
|
+
const octave = Math.floor(pitchUnits / 372) - 1;
|
|
13495
|
+
const within = (pitchUnits % 372 + 372) % 372;
|
|
13496
|
+
const step = Math.round(within / (372 / edo));
|
|
13497
|
+
return { octave, name: names[step % edo] };
|
|
13498
|
+
}
|
|
13604
13499
|
/**
|
|
13605
13500
|
* ピッチからオクターブ最適化のある音名を取得
|
|
13606
13501
|
*/
|
|
13607
13502
|
getNoteWithOctave(pitch, lastOctave) {
|
|
13608
|
-
const octave =
|
|
13609
|
-
const name = PITCH_MAP2[pitch % 12];
|
|
13503
|
+
const { octave, name } = this.spell(pitch);
|
|
13610
13504
|
if (lastOctave === -1 || Math.abs(octave - lastOctave) >= 2) {
|
|
13611
13505
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
13612
13506
|
}
|
|
@@ -13629,7 +13523,7 @@ var MMLCore = class _MMLCore {
|
|
|
13629
13523
|
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
13630
13524
|
*/
|
|
13631
13525
|
generateMML = (volumeOverride) => {
|
|
13632
|
-
const config =
|
|
13526
|
+
const config = this.getConfig();
|
|
13633
13527
|
const vol = volumeOverride ?? this.volume;
|
|
13634
13528
|
const header = `t${this.tempo} v${vol}`;
|
|
13635
13529
|
const segments = [];
|
|
@@ -13670,14 +13564,13 @@ var MMLCore = class _MMLCore {
|
|
|
13670
13564
|
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
13671
13565
|
if (notes.length > 1) {
|
|
13672
13566
|
const noteStrs = notes.map((n) => {
|
|
13673
|
-
const oct =
|
|
13674
|
-
const name = PITCH_MAP2[n.pitch % 12];
|
|
13567
|
+
const { octave: oct, name } = this.spell(n.pitchUnits);
|
|
13675
13568
|
return `o${oct}${name}`;
|
|
13676
13569
|
});
|
|
13677
13570
|
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
13678
13571
|
} else {
|
|
13679
13572
|
const { text, currentOctave } = this.getNoteWithOctave(
|
|
13680
|
-
notes[0].
|
|
13573
|
+
notes[0].pitchUnits,
|
|
13681
13574
|
lastOctave
|
|
13682
13575
|
);
|
|
13683
13576
|
segments.push(`${text}${durStr}`);
|
|
@@ -13708,7 +13601,7 @@ var MMLCore = class _MMLCore {
|
|
|
13708
13601
|
* MMLの音長文字列("4", "4.", "12"など)をステップ数に変換する
|
|
13709
13602
|
*/
|
|
13710
13603
|
getStepFromDottedMML(durStr) {
|
|
13711
|
-
const config =
|
|
13604
|
+
const config = this.getConfig();
|
|
13712
13605
|
const total = config.stepsPerBar;
|
|
13713
13606
|
const isDotted = durStr.endsWith(".");
|
|
13714
13607
|
const baseDur = parseInt(isDotted ? durStr.slice(0, -1) : durStr, 10);
|
|
@@ -13718,7 +13611,7 @@ var MMLCore = class _MMLCore {
|
|
|
13718
13611
|
};
|
|
13719
13612
|
var decomposeToMonophonic = (notes) => {
|
|
13720
13613
|
const sorted = [...notes].sort(
|
|
13721
|
-
(a, b) => a.startStep - b.startStep || a.
|
|
13614
|
+
(a, b) => a.startStep - b.startStep || a.pitchUnits - b.pitchUnits
|
|
13722
13615
|
);
|
|
13723
13616
|
const tracks = [];
|
|
13724
13617
|
const trackEnds = [];
|
|
@@ -13753,6 +13646,493 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
|
|
|
13753
13646
|
return chordNotes / notes.length >= threshold;
|
|
13754
13647
|
};
|
|
13755
13648
|
|
|
13649
|
+
// src/renderer.ts
|
|
13650
|
+
var KEYBOARD_WIDTH = 60;
|
|
13651
|
+
var HEADER_HEIGHT = 20;
|
|
13652
|
+
var NATURAL_STEPS3 = {
|
|
13653
|
+
12: [0, 2, 4, 5, 7, 9, 11],
|
|
13654
|
+
31: [0, 5, 10, 13, 18, 23, 28]
|
|
13655
|
+
};
|
|
13656
|
+
var keyTier = (step, edo) => {
|
|
13657
|
+
const naturals = NATURAL_STEPS3[edo] ?? NATURAL_STEPS3[12];
|
|
13658
|
+
let best = edo;
|
|
13659
|
+
for (const n of naturals) {
|
|
13660
|
+
const d = Math.abs(step - n);
|
|
13661
|
+
best = Math.min(best, d, edo - d);
|
|
13662
|
+
}
|
|
13663
|
+
if (best === 0) return 0;
|
|
13664
|
+
return best >= (edo === 31 ? 2 : 1) ? 2 : 1;
|
|
13665
|
+
};
|
|
13666
|
+
var KEY_NAMES = [
|
|
13667
|
+
"C",
|
|
13668
|
+
"C#",
|
|
13669
|
+
"D",
|
|
13670
|
+
"D#",
|
|
13671
|
+
"E",
|
|
13672
|
+
"F",
|
|
13673
|
+
"F#",
|
|
13674
|
+
"G",
|
|
13675
|
+
"G#",
|
|
13676
|
+
"A",
|
|
13677
|
+
"A#",
|
|
13678
|
+
"B"
|
|
13679
|
+
];
|
|
13680
|
+
var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
13681
|
+
let g_header_canvas;
|
|
13682
|
+
let g_key_canvas;
|
|
13683
|
+
let g_grid_canvas;
|
|
13684
|
+
let g_header_ctx;
|
|
13685
|
+
let g_key_ctx;
|
|
13686
|
+
let g_grid_ctx;
|
|
13687
|
+
let g_config = config;
|
|
13688
|
+
const getRenderConfig = () => g_config;
|
|
13689
|
+
let g_draw_offset_x = 0;
|
|
13690
|
+
let g_draw_offset_y = 0;
|
|
13691
|
+
let g_bg_active = false;
|
|
13692
|
+
const setBackgroundActive = (active) => {
|
|
13693
|
+
g_bg_active = active;
|
|
13694
|
+
};
|
|
13695
|
+
const getDrawOffset = () => ({
|
|
13696
|
+
x: g_draw_offset_x,
|
|
13697
|
+
y: g_draw_offset_y
|
|
13698
|
+
});
|
|
13699
|
+
const getGridCanvas = () => g_grid_canvas;
|
|
13700
|
+
const getGridContext = () => g_grid_ctx;
|
|
13701
|
+
const getHeaderCanvas = () => g_header_canvas;
|
|
13702
|
+
const setup = () => {
|
|
13703
|
+
g_config = config;
|
|
13704
|
+
const headerCanvas = document.createElement("canvas");
|
|
13705
|
+
g_header_canvas = headerCanvas;
|
|
13706
|
+
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
13707
|
+
headerCanvas.height = HEADER_HEIGHT;
|
|
13708
|
+
headerCanvas.style.position = "absolute";
|
|
13709
|
+
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13710
|
+
headerCanvas.style.top = "0px";
|
|
13711
|
+
const headerCtx = headerCanvas.getContext("2d");
|
|
13712
|
+
if (!headerCtx)
|
|
13713
|
+
throw new Error("Failed to get 2D rendering context for header.");
|
|
13714
|
+
g_header_ctx = headerCtx;
|
|
13715
|
+
const keyCanvas = document.createElement("canvas");
|
|
13716
|
+
g_key_canvas = keyCanvas;
|
|
13717
|
+
keyCanvas.width = KEYBOARD_WIDTH;
|
|
13718
|
+
keyCanvas.height = height - HEADER_HEIGHT;
|
|
13719
|
+
keyCanvas.style.position = "absolute";
|
|
13720
|
+
keyCanvas.style.left = "0px";
|
|
13721
|
+
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13722
|
+
const keyCtx = keyCanvas.getContext("2d");
|
|
13723
|
+
if (!keyCtx)
|
|
13724
|
+
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
13725
|
+
g_key_ctx = keyCtx;
|
|
13726
|
+
const gridCanvas = document.createElement("canvas");
|
|
13727
|
+
g_grid_canvas = gridCanvas;
|
|
13728
|
+
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
13729
|
+
gridCanvas.height = height - HEADER_HEIGHT;
|
|
13730
|
+
gridCanvas.style.position = "absolute";
|
|
13731
|
+
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13732
|
+
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13733
|
+
gridCanvas.style.touchAction = "none";
|
|
13734
|
+
gridCanvas.style.userSelect = "none";
|
|
13735
|
+
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
13736
|
+
if (!gridCtx)
|
|
13737
|
+
throw new Error("Failed to get 2D rendering context for grid.");
|
|
13738
|
+
g_grid_ctx = gridCtx;
|
|
13739
|
+
mountTarget.innerHTML = "";
|
|
13740
|
+
mountTarget.style.position = "relative";
|
|
13741
|
+
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
13742
|
+
mountTarget.style.height = `${height}px`;
|
|
13743
|
+
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
13744
|
+
drawHeaderCorner();
|
|
13745
|
+
};
|
|
13746
|
+
const drawHeaderCorner = () => {
|
|
13747
|
+
const mountTarget2 = g_key_canvas.parentElement;
|
|
13748
|
+
if (!mountTarget2) return;
|
|
13749
|
+
let cornerDiv = mountTarget2.querySelector(
|
|
13750
|
+
"#header-corner"
|
|
13751
|
+
);
|
|
13752
|
+
if (!cornerDiv) {
|
|
13753
|
+
cornerDiv = document.createElement("div");
|
|
13754
|
+
cornerDiv.id = "header-corner";
|
|
13755
|
+
cornerDiv.style.position = "absolute";
|
|
13756
|
+
cornerDiv.style.left = "0px";
|
|
13757
|
+
cornerDiv.style.top = "0px";
|
|
13758
|
+
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
13759
|
+
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
13760
|
+
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
13761
|
+
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
13762
|
+
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
13763
|
+
mountTarget2.insertBefore(cornerDiv, g_header_canvas);
|
|
13764
|
+
}
|
|
13765
|
+
};
|
|
13766
|
+
const drawKeyboard = () => {
|
|
13767
|
+
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
13768
|
+
const {
|
|
13769
|
+
keyHeight,
|
|
13770
|
+
keyCount,
|
|
13771
|
+
pitchRangeStart,
|
|
13772
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13773
|
+
edo = 12
|
|
13774
|
+
} = g_config;
|
|
13775
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13776
|
+
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
13777
|
+
const WHITE_KEY = "#ccc8b4";
|
|
13778
|
+
const BLACK_KEY = "#111111";
|
|
13779
|
+
const MICRO_KEY = "#4a4a4a";
|
|
13780
|
+
const BK_EDGE = "#383838";
|
|
13781
|
+
const WW_SEP = "#807a6a";
|
|
13782
|
+
const BK_RATIO = 0.62;
|
|
13783
|
+
const MICRO_RATIO = 0.45;
|
|
13784
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13785
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13786
|
+
const units = pitchRangeStart + rowIndex * upr;
|
|
13787
|
+
const step = Math.round(
|
|
13788
|
+
(units % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13789
|
+
);
|
|
13790
|
+
const tier = keyTier(step, edo);
|
|
13791
|
+
const octave = Math.floor(units / UNITS_PER_OCTAVE) - 1;
|
|
13792
|
+
const isC4Range = octave === 4;
|
|
13793
|
+
const screenY = y - g_draw_offset_y;
|
|
13794
|
+
const bkW = Math.floor(
|
|
13795
|
+
KEYBOARD_WIDTH * (tier === 1 ? MICRO_RATIO : BK_RATIO)
|
|
13796
|
+
);
|
|
13797
|
+
if (tier !== 0) {
|
|
13798
|
+
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
13799
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13800
|
+
g_key_ctx.fillStyle = tier === 1 ? MICRO_KEY : isC4Range ? "#1a1408" : BLACK_KEY;
|
|
13801
|
+
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
13802
|
+
g_key_ctx.strokeStyle = BK_EDGE;
|
|
13803
|
+
g_key_ctx.lineWidth = 1;
|
|
13804
|
+
g_key_ctx.beginPath();
|
|
13805
|
+
g_key_ctx.moveTo(bkW, screenY);
|
|
13806
|
+
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
13807
|
+
g_key_ctx.stroke();
|
|
13808
|
+
} else {
|
|
13809
|
+
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
13810
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13811
|
+
if (edo === 12 && (step === 5 || step === 0)) {
|
|
13812
|
+
g_key_ctx.strokeStyle = WW_SEP;
|
|
13813
|
+
g_key_ctx.lineWidth = 1;
|
|
13814
|
+
g_key_ctx.beginPath();
|
|
13815
|
+
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
13816
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
13817
|
+
g_key_ctx.stroke();
|
|
13818
|
+
}
|
|
13819
|
+
}
|
|
13820
|
+
if (step === 0) {
|
|
13821
|
+
g_key_ctx.fillStyle = "#555040";
|
|
13822
|
+
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
13823
|
+
g_key_ctx.textAlign = "right";
|
|
13824
|
+
g_key_ctx.textBaseline = "bottom";
|
|
13825
|
+
g_key_ctx.fillText(
|
|
13826
|
+
`${KEY_NAMES[0]}${octave}`,
|
|
13827
|
+
KEYBOARD_WIDTH - 4,
|
|
13828
|
+
screenY + keyHeight - 2
|
|
13829
|
+
);
|
|
13830
|
+
}
|
|
13831
|
+
}
|
|
13832
|
+
g_key_ctx.beginPath();
|
|
13833
|
+
g_key_ctx.strokeStyle = "#29adff";
|
|
13834
|
+
g_key_ctx.lineWidth = 2;
|
|
13835
|
+
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
13836
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
13837
|
+
g_key_ctx.stroke();
|
|
13838
|
+
};
|
|
13839
|
+
const drawHeader = () => {
|
|
13840
|
+
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
13841
|
+
const { stepWidth, stepsPerBar } = g_config;
|
|
13842
|
+
g_header_ctx.save();
|
|
13843
|
+
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
13844
|
+
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
13845
|
+
g_header_ctx.fillRect(
|
|
13846
|
+
g_draw_offset_x,
|
|
13847
|
+
0,
|
|
13848
|
+
g_header_canvas.width,
|
|
13849
|
+
HEADER_HEIGHT
|
|
13850
|
+
);
|
|
13851
|
+
g_header_ctx.strokeStyle = "#3d405b";
|
|
13852
|
+
g_header_ctx.lineWidth = 1;
|
|
13853
|
+
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
13854
|
+
g_header_ctx.fillStyle = "#83769c";
|
|
13855
|
+
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
13856
|
+
const endBar = Math.ceil(
|
|
13857
|
+
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13858
|
+
);
|
|
13859
|
+
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13860
|
+
const x2 = bar * stepsPerBar * stepWidth;
|
|
13861
|
+
const screenX = x2;
|
|
13862
|
+
g_header_ctx.beginPath();
|
|
13863
|
+
g_header_ctx.moveTo(screenX, 0);
|
|
13864
|
+
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13865
|
+
g_header_ctx.stroke();
|
|
13866
|
+
if (bar >= 0) {
|
|
13867
|
+
g_header_ctx.textAlign = "left";
|
|
13868
|
+
g_header_ctx.textBaseline = "middle";
|
|
13869
|
+
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13870
|
+
}
|
|
13871
|
+
}
|
|
13872
|
+
g_header_ctx.restore();
|
|
13873
|
+
};
|
|
13874
|
+
const drawGrid = (noteLengthSteps = 1) => {
|
|
13875
|
+
drawKeyboard();
|
|
13876
|
+
drawHeader();
|
|
13877
|
+
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13878
|
+
const {
|
|
13879
|
+
keyHeight,
|
|
13880
|
+
keyCount,
|
|
13881
|
+
stepWidth,
|
|
13882
|
+
stepsPerBar,
|
|
13883
|
+
pitchRangeStart,
|
|
13884
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13885
|
+
edo = 12
|
|
13886
|
+
} = g_config;
|
|
13887
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13888
|
+
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13889
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13890
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13891
|
+
const units = pitchRangeStart + rowIndex * upr;
|
|
13892
|
+
const step = Math.round(
|
|
13893
|
+
(units % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13894
|
+
);
|
|
13895
|
+
const tier = keyTier(step, edo);
|
|
13896
|
+
const isC = step === 0;
|
|
13897
|
+
const octave = Math.floor(units / UNITS_PER_OCTAVE) - 1;
|
|
13898
|
+
const isC4Range = octave === 4;
|
|
13899
|
+
const screenY = y - g_draw_offset_y;
|
|
13900
|
+
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";
|
|
13901
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13902
|
+
if (isC4Range) {
|
|
13903
|
+
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13904
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13905
|
+
}
|
|
13906
|
+
g_grid_ctx.beginPath();
|
|
13907
|
+
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13908
|
+
g_grid_ctx.lineWidth = 1;
|
|
13909
|
+
const lineY = screenY + keyHeight;
|
|
13910
|
+
g_grid_ctx.moveTo(0, lineY);
|
|
13911
|
+
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13912
|
+
g_grid_ctx.stroke();
|
|
13913
|
+
}
|
|
13914
|
+
const gridStep = noteLengthSteps || 48;
|
|
13915
|
+
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13916
|
+
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13917
|
+
const lineStep = stepWidth * gridStep;
|
|
13918
|
+
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13919
|
+
const step = x2 / stepWidth;
|
|
13920
|
+
const isBarLine = step % stepsPerBar === 0;
|
|
13921
|
+
const isNoteLine = step % gridStep === 0;
|
|
13922
|
+
const screenX = x2 - g_draw_offset_x;
|
|
13923
|
+
g_grid_ctx.beginPath();
|
|
13924
|
+
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13925
|
+
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13926
|
+
g_grid_ctx.moveTo(screenX, 0);
|
|
13927
|
+
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13928
|
+
g_grid_ctx.stroke();
|
|
13929
|
+
}
|
|
13930
|
+
};
|
|
13931
|
+
const drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13932
|
+
const {
|
|
13933
|
+
keyHeight,
|
|
13934
|
+
stepWidth,
|
|
13935
|
+
keyCount,
|
|
13936
|
+
pitchRangeStart,
|
|
13937
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13938
|
+
} = g_config;
|
|
13939
|
+
const canvasWidth = g_grid_canvas.width;
|
|
13940
|
+
const canvasHeight = g_grid_canvas.height;
|
|
13941
|
+
const [r, g, b, a] = color;
|
|
13942
|
+
for (const note of notes) {
|
|
13943
|
+
const logicalX = note.startStep * stepWidth;
|
|
13944
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13945
|
+
const logicalY = yIndex * keyHeight;
|
|
13946
|
+
const w = note.durationSteps * stepWidth;
|
|
13947
|
+
const h = keyHeight;
|
|
13948
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13949
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13950
|
+
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13951
|
+
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13952
|
+
if (isActive) {
|
|
13953
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13954
|
+
const finalOpacity = a * velocityOpacity;
|
|
13955
|
+
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13956
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13957
|
+
if (w >= 4 && h >= 4) {
|
|
13958
|
+
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13959
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13960
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13961
|
+
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13962
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13963
|
+
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13964
|
+
}
|
|
13965
|
+
} else {
|
|
13966
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13967
|
+
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13968
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13969
|
+
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13970
|
+
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13971
|
+
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13972
|
+
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13973
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13974
|
+
}
|
|
13975
|
+
}
|
|
13976
|
+
};
|
|
13977
|
+
const drawNoteLyrics = (notes, syllables) => {
|
|
13978
|
+
if (syllables.length === 0) return;
|
|
13979
|
+
const {
|
|
13980
|
+
keyHeight,
|
|
13981
|
+
stepWidth,
|
|
13982
|
+
keyCount,
|
|
13983
|
+
pitchRangeStart,
|
|
13984
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13985
|
+
} = g_config;
|
|
13986
|
+
if (keyHeight < 7) return;
|
|
13987
|
+
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13988
|
+
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
13989
|
+
const count = Math.min(sorted.length, syllables.length);
|
|
13990
|
+
g_grid_ctx.save();
|
|
13991
|
+
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
13992
|
+
g_grid_ctx.textAlign = "left";
|
|
13993
|
+
g_grid_ctx.textBaseline = "middle";
|
|
13994
|
+
g_grid_ctx.lineWidth = 3;
|
|
13995
|
+
g_grid_ctx.lineJoin = "round";
|
|
13996
|
+
for (let i2 = 0; i2 < count; i2++) {
|
|
13997
|
+
const kana = syllables[i2];
|
|
13998
|
+
if (!kana) continue;
|
|
13999
|
+
const note = sorted[i2];
|
|
14000
|
+
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
14001
|
+
const renderY = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
|
|
14002
|
+
const w = note.durationSteps * stepWidth;
|
|
14003
|
+
if (w < 6) continue;
|
|
14004
|
+
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
14005
|
+
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
14006
|
+
const textX = renderX + 2;
|
|
14007
|
+
const textY = renderY + keyHeight / 2;
|
|
14008
|
+
g_grid_ctx.save();
|
|
14009
|
+
g_grid_ctx.beginPath();
|
|
14010
|
+
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
14011
|
+
g_grid_ctx.clip();
|
|
14012
|
+
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
14013
|
+
g_grid_ctx.strokeText(kana, textX, textY);
|
|
14014
|
+
g_grid_ctx.fillStyle = "#fff1e8";
|
|
14015
|
+
g_grid_ctx.fillText(kana, textX, textY);
|
|
14016
|
+
g_grid_ctx.restore();
|
|
14017
|
+
}
|
|
14018
|
+
g_grid_ctx.restore();
|
|
14019
|
+
};
|
|
14020
|
+
const drawSelectionRect = (rect) => {
|
|
14021
|
+
if (!rect) return;
|
|
14022
|
+
g_grid_ctx.save();
|
|
14023
|
+
g_grid_ctx.strokeStyle = "#ffec27";
|
|
14024
|
+
g_grid_ctx.lineWidth = 2;
|
|
14025
|
+
g_grid_ctx.setLineDash([4, 4]);
|
|
14026
|
+
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
14027
|
+
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
14028
|
+
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
14029
|
+
g_grid_ctx.restore();
|
|
14030
|
+
};
|
|
14031
|
+
const drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
14032
|
+
const {
|
|
14033
|
+
keyHeight,
|
|
14034
|
+
stepWidth,
|
|
14035
|
+
keyCount,
|
|
14036
|
+
pitchRangeStart,
|
|
14037
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
14038
|
+
} = g_config;
|
|
14039
|
+
for (const note of notes) {
|
|
14040
|
+
if (!selectedIds.has(note.id)) continue;
|
|
14041
|
+
const logicalX = note.startStep * stepWidth;
|
|
14042
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14043
|
+
const logicalY = yIndex * keyHeight;
|
|
14044
|
+
const w = note.durationSteps * stepWidth;
|
|
14045
|
+
const h = keyHeight;
|
|
14046
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
14047
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
14048
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
14049
|
+
const [r, g, b, a] = baseColor;
|
|
14050
|
+
const darkenFactor = 1.3;
|
|
14051
|
+
const darkerR = Math.min(255, r * darkenFactor);
|
|
14052
|
+
const darkerG = Math.min(255, g * darkenFactor);
|
|
14053
|
+
const darkerB = Math.min(255, b * darkenFactor);
|
|
14054
|
+
const finalOpacity = a * velocityOpacity;
|
|
14055
|
+
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
14056
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
14057
|
+
}
|
|
14058
|
+
};
|
|
14059
|
+
const getXY = (e) => {
|
|
14060
|
+
const { clientX, clientY } = e;
|
|
14061
|
+
const rect = g_grid_canvas.getBoundingClientRect();
|
|
14062
|
+
const x2 = Math.floor(clientX - rect.left);
|
|
14063
|
+
const y = Math.floor(clientY - rect.top);
|
|
14064
|
+
return [x2, y, e.buttons];
|
|
14065
|
+
};
|
|
14066
|
+
const getGridPosition = (e) => {
|
|
14067
|
+
const [x2, y] = getXY(e);
|
|
14068
|
+
const {
|
|
14069
|
+
keyCount,
|
|
14070
|
+
pitchRangeStart,
|
|
14071
|
+
keyHeight,
|
|
14072
|
+
stepWidth,
|
|
14073
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
14074
|
+
} = g_config;
|
|
14075
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
14076
|
+
const absoluteY = y + g_draw_offset_y;
|
|
14077
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
14078
|
+
const pitch = pitchRangeStart + (keyCount - 1 - yIndex) * upr;
|
|
14079
|
+
return { step, pitch, x: x2, y };
|
|
14080
|
+
};
|
|
14081
|
+
const onClick = (callback) => {
|
|
14082
|
+
g_grid_canvas.addEventListener(
|
|
14083
|
+
"click",
|
|
14084
|
+
(e) => {
|
|
14085
|
+
const [x2, y] = getXY(e);
|
|
14086
|
+
const {
|
|
14087
|
+
keyCount,
|
|
14088
|
+
pitchRangeStart,
|
|
14089
|
+
keyHeight,
|
|
14090
|
+
stepWidth,
|
|
14091
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
14092
|
+
} = g_config;
|
|
14093
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
14094
|
+
const absoluteY = y + g_draw_offset_y;
|
|
14095
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
14096
|
+
const pitch = pitchRangeStart + (keyCount - 1 - yIndex) * upr;
|
|
14097
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
14098
|
+
requestAnimationFrame(() => callback(step, pitch));
|
|
14099
|
+
}
|
|
14100
|
+
},
|
|
14101
|
+
{ passive: true }
|
|
14102
|
+
);
|
|
14103
|
+
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
14104
|
+
};
|
|
14105
|
+
const setDrawOffset = (x2, y) => {
|
|
14106
|
+
g_draw_offset_x = x2;
|
|
14107
|
+
g_draw_offset_y = y;
|
|
14108
|
+
drawKeyboard();
|
|
14109
|
+
drawHeader();
|
|
14110
|
+
};
|
|
14111
|
+
setup();
|
|
14112
|
+
return {
|
|
14113
|
+
getRenderConfig,
|
|
14114
|
+
setBackgroundActive,
|
|
14115
|
+
getDrawOffset,
|
|
14116
|
+
getGridCanvas,
|
|
14117
|
+
getGridContext,
|
|
14118
|
+
getHeaderCanvas,
|
|
14119
|
+
drawKeyboard,
|
|
14120
|
+
drawHeader,
|
|
14121
|
+
drawGrid,
|
|
14122
|
+
drawNotes,
|
|
14123
|
+
drawNoteLyrics,
|
|
14124
|
+
drawSelectionRect,
|
|
14125
|
+
drawSelectedNotes,
|
|
14126
|
+
getXY,
|
|
14127
|
+
getGridPosition,
|
|
14128
|
+
onClick,
|
|
14129
|
+
setDrawOffset,
|
|
14130
|
+
destroy: () => {
|
|
14131
|
+
mountTarget.innerHTML = "";
|
|
14132
|
+
}
|
|
14133
|
+
};
|
|
14134
|
+
};
|
|
14135
|
+
|
|
13756
14136
|
// src/daw.ts
|
|
13757
14137
|
var CHORD_INFO_HTML2 = `
|
|
13758
14138
|
<div class="dtm-modal-body-content">
|
|
@@ -14039,6 +14419,27 @@ var MIDI_INFO_HTML = `
|
|
|
14039
14419
|
<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>
|
|
14040
14420
|
</div>
|
|
14041
14421
|
`;
|
|
14422
|
+
var EDO_INFO_HTML = `
|
|
14423
|
+
<div class="dtm-modal-section">
|
|
14424
|
+
<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>
|
|
14425
|
+
<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>
|
|
14426
|
+
<p style="margin-top:8px;"><strong>31\u5E73\u5747\u5F8B\u306E\u7279\u5FB4</strong></p>
|
|
14427
|
+
<ul>
|
|
14428
|
+
<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>
|
|
14429
|
+
<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>
|
|
14430
|
+
<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>
|
|
14431
|
+
</ul>
|
|
14432
|
+
<p style="margin-top:8px;"><strong>MML\u3067\u306E\u66F8\u304D\u65B9</strong></p>
|
|
14433
|
+
<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>
|
|
14434
|
+
<ul>
|
|
14435
|
+
<li><code>#</code> \u2026 \u534A\u97F3\u4E0A\u3052\uFF082\u5EA6\uFF09\u3000<code>-</code> \u2026 \u534A\u97F3\u4E0B\u3052\uFF082\u5EA6\uFF09</li>
|
|
14436
|
+
<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>
|
|
14437
|
+
</ul>
|
|
14438
|
+
<p>\u4F8B: <code>#edo=31 @0 o4 c c+ c# d- d_ d;</code></p>
|
|
14439
|
+
<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>
|
|
14440
|
+
<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>
|
|
14441
|
+
</div>
|
|
14442
|
+
`;
|
|
14042
14443
|
var KOE_INFO_HTML = `
|
|
14043
14444
|
<div class="dtm-modal-body-content">
|
|
14044
14445
|
<h4>1. UTAU\u97F3\u6E90\u3092 .koe \u306B\u5909\u63DB\u3059\u308B</h4>
|
|
@@ -14256,7 +14657,7 @@ var AUTO_ROLE_BASE_VOLUME = {
|
|
|
14256
14657
|
chord: 72
|
|
14257
14658
|
};
|
|
14258
14659
|
var computeAutoRoleStats = (notes) => {
|
|
14259
|
-
const avgPitch = notes.reduce((s, n) => s + n.
|
|
14660
|
+
const avgPitch = notes.reduce((s, n) => s + n.pitchUnits, 0) / notes.length;
|
|
14260
14661
|
const avgDur = notes.reduce((s, n) => s + n.durationSteps, 0) / notes.length;
|
|
14261
14662
|
const byStart = /* @__PURE__ */ new Map();
|
|
14262
14663
|
for (const n of notes)
|
|
@@ -14270,8 +14671,9 @@ var computeAutoRoleStats = (notes) => {
|
|
|
14270
14671
|
const notesPerBeat = notes.length / spanBeats;
|
|
14271
14672
|
return { avgPitch, avgDur, avgPoly, maxPoly, notesPerBeat };
|
|
14272
14673
|
};
|
|
14674
|
+
var AUTO_ROLE_BASS_UNITS = 48 * UNITS_PER_SEMITONE;
|
|
14273
14675
|
var classifyTrackRole = (stats) => {
|
|
14274
|
-
if (stats.avgPitch <
|
|
14676
|
+
if (stats.avgPitch < AUTO_ROLE_BASS_UNITS) return "bass";
|
|
14275
14677
|
if (stats.maxPoly >= 2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT)
|
|
14276
14678
|
return "chord";
|
|
14277
14679
|
if (stats.notesPerBeat < 1.2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT * 1.5)
|
|
@@ -14283,7 +14685,10 @@ var computeAutoRoleVolume = (role, stats) => {
|
|
|
14283
14685
|
if (role === "chord" && stats.avgPoly > 1) {
|
|
14284
14686
|
vol /= Math.sqrt(stats.avgPoly);
|
|
14285
14687
|
} else if (role === "bass") {
|
|
14286
|
-
const depthSemitones = Math.max(
|
|
14688
|
+
const depthSemitones = Math.max(
|
|
14689
|
+
0,
|
|
14690
|
+
(AUTO_ROLE_BASS_UNITS - stats.avgPitch) / UNITS_PER_SEMITONE
|
|
14691
|
+
);
|
|
14287
14692
|
vol += Math.min(12, depthSemitones * 0.5);
|
|
14288
14693
|
}
|
|
14289
14694
|
return clamp5(Math.round(vol), 1, 127);
|
|
@@ -14375,17 +14780,48 @@ var mountDAW = (target, options = {}) => {
|
|
|
14375
14780
|
refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
|
|
14376
14781
|
const renderConfig = {
|
|
14377
14782
|
stepsPerBar: 192,
|
|
14378
|
-
keyCount:
|
|
14379
|
-
pitchRangeStart:
|
|
14783
|
+
keyCount: KEY_COUNT,
|
|
14784
|
+
pitchRangeStart: PITCH_RANGE_START,
|
|
14785
|
+
unitsPerRow: unitsPerRow(12),
|
|
14786
|
+
edo: 12,
|
|
14380
14787
|
keyHeight: BASE_KEY_HEIGHT,
|
|
14381
14788
|
stepWidth: BASE_STEP_WIDTH * 2
|
|
14382
14789
|
// zoom100% 相当
|
|
14383
14790
|
};
|
|
14791
|
+
let renderer;
|
|
14384
14792
|
let zoomX = 100;
|
|
14385
14793
|
let zoomY = 100;
|
|
14386
14794
|
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
14387
14795
|
let masterVolume = options.masterVolume ?? 50;
|
|
14388
|
-
|
|
14796
|
+
const applyMasterVolume = (volume) => {
|
|
14797
|
+
masterVolume = clamp5(Math.round(volume), 0, 100);
|
|
14798
|
+
refs.masterVolume.value = String(masterVolume);
|
|
14799
|
+
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
14800
|
+
options.singingVoices?.setVolume(masterVolume / 100);
|
|
14801
|
+
};
|
|
14802
|
+
applyMasterVolume(masterVolume);
|
|
14803
|
+
const snapToEdoGrid = (units) => {
|
|
14804
|
+
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
14805
|
+
return Math.round(units / upr) * upr;
|
|
14806
|
+
};
|
|
14807
|
+
const applyEdo = (edo, opts) => {
|
|
14808
|
+
const next = edo === 31 ? 31 : 12;
|
|
14809
|
+
if (renderConfig.edo === next) return;
|
|
14810
|
+
const upr = unitsPerRow(next);
|
|
14811
|
+
renderConfig.edo = next;
|
|
14812
|
+
renderConfig.unitsPerRow = upr;
|
|
14813
|
+
renderConfig.keyCount = keyCountFor(next);
|
|
14814
|
+
if (!opts?.snap) return;
|
|
14815
|
+
for (const t of trackStates) {
|
|
14816
|
+
t.core.beginBatch();
|
|
14817
|
+
for (const note of [...t.core.getNotes()]) {
|
|
14818
|
+
const snapped = Math.round(note.pitchUnits / upr) * upr;
|
|
14819
|
+
if (snapped !== note.pitchUnits)
|
|
14820
|
+
t.core.moveNote(note.id, note.startStep, snapped);
|
|
14821
|
+
}
|
|
14822
|
+
t.core.endBatch();
|
|
14823
|
+
}
|
|
14824
|
+
};
|
|
14389
14825
|
let reverbAmount = options.reverbAmount ?? 0;
|
|
14390
14826
|
let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
|
|
14391
14827
|
let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
|
|
@@ -14443,8 +14879,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14443
14879
|
let snapGridSteps = 12;
|
|
14444
14880
|
const gridLineSteps = 48;
|
|
14445
14881
|
let currentOffsetX = 0;
|
|
14446
|
-
const _initPitch = options.initialScrollPitch ?? 48;
|
|
14447
|
-
let currentOffsetY = (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215;
|
|
14882
|
+
const _initPitch = pitchV1ToUnits(options.initialScrollPitch ?? 48);
|
|
14883
|
+
let currentOffsetY = (renderConfig.keyCount - 1 - (_initPitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - 215;
|
|
14448
14884
|
let playStartStep = 0;
|
|
14449
14885
|
let isSolo = false;
|
|
14450
14886
|
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
@@ -14539,21 +14975,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
14539
14975
|
if (!ready) return;
|
|
14540
14976
|
if (!suppressPatch && options.onNotesPatch) {
|
|
14541
14977
|
const prevByKey = new Map(
|
|
14542
|
-
prevNotes.map((n) => [`${n.startStep}_${n.
|
|
14978
|
+
prevNotes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14543
14979
|
);
|
|
14544
14980
|
const currByKey = new Map(
|
|
14545
|
-
notes.map((n) => [`${n.startStep}_${n.
|
|
14981
|
+
notes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14546
14982
|
);
|
|
14547
14983
|
const added = notes.filter((n) => {
|
|
14548
|
-
const prev = prevByKey.get(
|
|
14984
|
+
const prev = prevByKey.get(
|
|
14985
|
+
`${n.startStep}_${n.pitchUnits}`
|
|
14986
|
+
);
|
|
14549
14987
|
return !prev || prev.durationSteps !== n.durationSteps || prev.velocity !== n.velocity;
|
|
14550
14988
|
}).map((n) => ({
|
|
14551
14989
|
startStep: n.startStep,
|
|
14552
|
-
|
|
14990
|
+
pitchUnits: n.pitchUnits,
|
|
14553
14991
|
durationSteps: n.durationSteps,
|
|
14554
14992
|
velocity: n.velocity
|
|
14555
14993
|
}));
|
|
14556
|
-
const removed = prevNotes.filter(
|
|
14994
|
+
const removed = prevNotes.filter(
|
|
14995
|
+
(n) => !currByKey.has(`${n.startStep}_${n.pitchUnits}`)
|
|
14996
|
+
).map((n) => ({
|
|
14997
|
+
startStep: n.startStep,
|
|
14998
|
+
pitchUnits: n.pitchUnits
|
|
14999
|
+
}));
|
|
14557
15000
|
if (added.length > 0 || removed.length > 0) {
|
|
14558
15001
|
options.onNotesPatch(config.id, added, removed);
|
|
14559
15002
|
}
|
|
@@ -14563,7 +15006,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14563
15006
|
updateUndoRedo();
|
|
14564
15007
|
}
|
|
14565
15008
|
},
|
|
14566
|
-
config.volume
|
|
15009
|
+
config.volume,
|
|
15010
|
+
() => renderConfig
|
|
14567
15011
|
),
|
|
14568
15012
|
volume: config.volume,
|
|
14569
15013
|
savedChordInput: "",
|
|
@@ -14658,18 +15102,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
14658
15102
|
return (lastNoteMeasure + 2) * renderConfig.stepsPerBar;
|
|
14659
15103
|
};
|
|
14660
15104
|
const getMaxOffsetX = () => {
|
|
14661
|
-
const canvas = getGridCanvas();
|
|
15105
|
+
const canvas = renderer.getGridCanvas();
|
|
14662
15106
|
const maxNoteStep = getMaxNoteStep();
|
|
14663
15107
|
const totalContentWidth = maxNoteStep * renderConfig.stepWidth;
|
|
14664
15108
|
return Math.max(0, totalContentWidth - canvas.width);
|
|
14665
15109
|
};
|
|
14666
15110
|
const getMaxOffsetY = () => {
|
|
14667
15111
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
14668
|
-
return Math.max(0, totalHeight - getGridCanvas().height);
|
|
15112
|
+
return Math.max(0, totalHeight - renderer.getGridCanvas().height);
|
|
14669
15113
|
};
|
|
14670
15114
|
const drawStartLine = () => {
|
|
14671
|
-
const ctx = getGridContext();
|
|
14672
|
-
const canvas = getGridCanvas();
|
|
15115
|
+
const ctx = renderer.getGridContext();
|
|
15116
|
+
const canvas = renderer.getGridCanvas();
|
|
14673
15117
|
if (!ctx) return;
|
|
14674
15118
|
const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
|
|
14675
15119
|
if (x2 < -10 || x2 > canvas.width + 10) return;
|
|
@@ -14684,8 +15128,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14684
15128
|
ctx.restore();
|
|
14685
15129
|
};
|
|
14686
15130
|
const drawPlayhead = () => {
|
|
14687
|
-
const ctx = getGridContext();
|
|
14688
|
-
const canvas = getGridCanvas();
|
|
15131
|
+
const ctx = renderer.getGridContext();
|
|
15132
|
+
const canvas = renderer.getGridCanvas();
|
|
14689
15133
|
if (!ctx) return;
|
|
14690
15134
|
const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
|
|
14691
15135
|
if (x2 < 0 || x2 > canvas.width) return;
|
|
@@ -14699,7 +15143,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14699
15143
|
ctx.restore();
|
|
14700
15144
|
};
|
|
14701
15145
|
const redrawAll = () => {
|
|
14702
|
-
drawGrid(gridLineSteps);
|
|
15146
|
+
renderer.drawGrid(gridLineSteps);
|
|
14703
15147
|
let lyricTargetNotes = null;
|
|
14704
15148
|
let activeTrackState = null;
|
|
14705
15149
|
for (const t of trackStates) {
|
|
@@ -14711,16 +15155,16 @@ var mountDAW = (target, options = {}) => {
|
|
|
14711
15155
|
}
|
|
14712
15156
|
const [r, g, b] = t.config.color;
|
|
14713
15157
|
const notes = t.core.getNotes();
|
|
14714
|
-
drawNotes(notes, [r, g, b, 1], false);
|
|
15158
|
+
renderer.drawNotes(notes, [r, g, b, 1], false);
|
|
14715
15159
|
}
|
|
14716
15160
|
if (activeTrackState) {
|
|
14717
15161
|
const [r, g, b] = activeTrackState.config.color;
|
|
14718
15162
|
const notes = activeTrackState.core.getNotes();
|
|
14719
|
-
drawNotes(notes, [r, g, b, 1], true);
|
|
15163
|
+
renderer.drawNotes(notes, [r, g, b, 1], true);
|
|
14720
15164
|
lyricTargetNotes = notes;
|
|
14721
15165
|
}
|
|
14722
15166
|
if (activeToolMode === "select" && selectionRect) {
|
|
14723
|
-
const ctx = getGridContext();
|
|
15167
|
+
const ctx = renderer.getGridContext();
|
|
14724
15168
|
ctx.save();
|
|
14725
15169
|
ctx.strokeStyle = "#ffec27";
|
|
14726
15170
|
ctx.lineWidth = 2;
|
|
@@ -14743,19 +15187,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
14743
15187
|
if (activeToolMode === "select" && selectedNotes.length > 0) {
|
|
14744
15188
|
const ids = new Set(selectedNotes.map((n) => n.id));
|
|
14745
15189
|
const active = getActive();
|
|
14746
|
-
drawSelectedNotes(active.core.getNotes(), ids, [
|
|
15190
|
+
renderer.drawSelectedNotes(active.core.getNotes(), ids, [
|
|
14747
15191
|
...active.config.color,
|
|
14748
15192
|
1
|
|
14749
15193
|
]);
|
|
14750
15194
|
}
|
|
14751
15195
|
if (lyricTargetNotes)
|
|
14752
|
-
drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
15196
|
+
renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
14753
15197
|
drawStartLine();
|
|
14754
15198
|
if (playbackState === "playing") drawPlayhead();
|
|
14755
15199
|
updateScrollbars();
|
|
14756
15200
|
};
|
|
14757
15201
|
const updateScrollbars = () => {
|
|
14758
|
-
const canvas = getGridCanvas();
|
|
15202
|
+
const canvas = renderer.getGridCanvas();
|
|
14759
15203
|
const maxOffsetX = getMaxOffsetX();
|
|
14760
15204
|
const sbW = refs.hScroll.clientWidth;
|
|
14761
15205
|
if (maxOffsetX <= 0) {
|
|
@@ -14839,7 +15283,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14839
15283
|
const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
14840
15284
|
const ratio = x2 / (rect.width - thumbW);
|
|
14841
15285
|
currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
|
|
14842
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15286
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14843
15287
|
redrawAll();
|
|
14844
15288
|
};
|
|
14845
15289
|
const moveV = (clientY) => {
|
|
@@ -14850,7 +15294,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14850
15294
|
const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
14851
15295
|
const ratio = y / (rect.height - thumbH);
|
|
14852
15296
|
currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
|
|
14853
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15297
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14854
15298
|
redrawAll();
|
|
14855
15299
|
};
|
|
14856
15300
|
};
|
|
@@ -14889,8 +15333,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14889
15333
|
const autoScrollTick = () => {
|
|
14890
15334
|
autoScrollRAF = null;
|
|
14891
15335
|
if (!isSelecting || !lastMoveEvent) return;
|
|
14892
|
-
const canvas = getGridCanvas();
|
|
14893
|
-
const { x: x2, y } = getGridPosition(lastMoveEvent);
|
|
15336
|
+
const canvas = renderer.getGridCanvas();
|
|
15337
|
+
const { x: x2, y } = renderer.getGridPosition(lastMoveEvent);
|
|
14894
15338
|
const dx = computeEdgeSpeed(x2, canvas.width);
|
|
14895
15339
|
const dy = computeEdgeSpeed(y, canvas.height);
|
|
14896
15340
|
if (dx !== 0 || dy !== 0) {
|
|
@@ -14898,7 +15342,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14898
15342
|
const maxOffsetY = getMaxOffsetY();
|
|
14899
15343
|
currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
|
|
14900
15344
|
currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
|
|
14901
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15345
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14902
15346
|
onPointerMove(lastMoveEvent);
|
|
14903
15347
|
}
|
|
14904
15348
|
if (isSelecting) {
|
|
@@ -14919,11 +15363,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14919
15363
|
};
|
|
14920
15364
|
const findActiveNoteAt = (x2, y, margin = 0) => {
|
|
14921
15365
|
const active = getActive();
|
|
14922
|
-
const {
|
|
14923
|
-
|
|
15366
|
+
const {
|
|
15367
|
+
stepWidth,
|
|
15368
|
+
keyHeight,
|
|
15369
|
+
keyCount,
|
|
15370
|
+
pitchRangeStart,
|
|
15371
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15372
|
+
} = renderConfig;
|
|
15373
|
+
const offset = renderer.getDrawOffset();
|
|
14924
15374
|
for (const note of active.core.getNotes()) {
|
|
14925
15375
|
const logicalX = note.startStep * stepWidth;
|
|
14926
|
-
const yIndex = keyCount - 1 - (note.
|
|
15376
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14927
15377
|
const logicalY = yIndex * keyHeight;
|
|
14928
15378
|
const w = note.durationSteps * stepWidth;
|
|
14929
15379
|
const renderX = logicalX - offset.x;
|
|
@@ -14936,7 +15386,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14936
15386
|
const hasNoteAt = (step, pitch, excludeId) => {
|
|
14937
15387
|
const active = getActive();
|
|
14938
15388
|
return active.core.getNotes().some(
|
|
14939
|
-
(n) => n.id !== excludeId && n.
|
|
15389
|
+
(n) => n.id !== excludeId && n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
14940
15390
|
);
|
|
14941
15391
|
};
|
|
14942
15392
|
const snapToGrid = (duration) => Math.max(
|
|
@@ -14947,7 +15397,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14947
15397
|
const onGridPointerDown = (event) => {
|
|
14948
15398
|
event.preventDefault();
|
|
14949
15399
|
options.onResumeAudio?.();
|
|
14950
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15400
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14951
15401
|
const active = getActive();
|
|
14952
15402
|
if (activeToolMode === "eraser") {
|
|
14953
15403
|
if (isActiveLocked()) return;
|
|
@@ -14962,7 +15412,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14962
15412
|
selectedOriginal = selectedNotes.map((n) => ({
|
|
14963
15413
|
id: n.id,
|
|
14964
15414
|
startStep: n.startStep,
|
|
14965
|
-
pitch: n.
|
|
15415
|
+
pitch: n.pitchUnits
|
|
14966
15416
|
}));
|
|
14967
15417
|
isSelecting = true;
|
|
14968
15418
|
dragMode = "move";
|
|
@@ -14981,7 +15431,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14981
15431
|
{
|
|
14982
15432
|
id: clicked.id,
|
|
14983
15433
|
startStep: clicked.startStep,
|
|
14984
|
-
pitch: clicked.
|
|
15434
|
+
pitch: clicked.pitchUnits
|
|
14985
15435
|
}
|
|
14986
15436
|
];
|
|
14987
15437
|
isSelecting = true;
|
|
@@ -14999,9 +15449,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
14999
15449
|
hasDragged = false;
|
|
15000
15450
|
const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
|
|
15001
15451
|
if (existing) {
|
|
15002
|
-
playPreview(existing.
|
|
15452
|
+
playPreview(existing.pitchUnits);
|
|
15003
15453
|
const { stepWidth } = renderConfig;
|
|
15004
|
-
const offset = getDrawOffset();
|
|
15454
|
+
const offset = renderer.getDrawOffset();
|
|
15005
15455
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
15006
15456
|
const w = existing.durationSteps * stepWidth;
|
|
15007
15457
|
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
@@ -15012,17 +15462,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
15012
15462
|
dragOffsetPitch: 0,
|
|
15013
15463
|
startStep: existing.startStep,
|
|
15014
15464
|
durationSteps: existing.durationSteps,
|
|
15015
|
-
lastPreviewPitch: existing.
|
|
15465
|
+
lastPreviewPitch: existing.pitchUnits
|
|
15016
15466
|
};
|
|
15017
15467
|
} else {
|
|
15018
15468
|
dragState = {
|
|
15019
15469
|
noteId: existing.id,
|
|
15020
15470
|
mode: "move",
|
|
15021
15471
|
dragOffsetStep: step - existing.startStep,
|
|
15022
|
-
dragOffsetPitch: pitch - existing.
|
|
15472
|
+
dragOffsetPitch: pitch - existing.pitchUnits,
|
|
15023
15473
|
startStep: existing.startStep,
|
|
15024
15474
|
durationSteps: existing.durationSteps,
|
|
15025
|
-
lastPreviewPitch: existing.
|
|
15475
|
+
lastPreviewPitch: existing.pitchUnits
|
|
15026
15476
|
};
|
|
15027
15477
|
}
|
|
15028
15478
|
suppressClick = true;
|
|
@@ -15033,14 +15483,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15033
15483
|
const newStart = snappedStep;
|
|
15034
15484
|
const newEnd = newStart + currentInsertLength;
|
|
15035
15485
|
const overlapping = active.core.getNotes().some(
|
|
15036
|
-
(n) => n.
|
|
15486
|
+
(n) => n.pitchUnits === pitch && newStart < n.startStep + n.durationSteps && newEnd > n.startStep
|
|
15037
15487
|
);
|
|
15038
15488
|
if (!overlapping) {
|
|
15039
15489
|
active.core.addNote(snappedStep, pitch, {
|
|
15040
15490
|
noteLengthSteps: currentInsertLength
|
|
15041
15491
|
});
|
|
15042
15492
|
playPreview(pitch);
|
|
15043
|
-
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.
|
|
15493
|
+
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.pitchUnits === pitch);
|
|
15044
15494
|
if (newNote) {
|
|
15045
15495
|
dragState = {
|
|
15046
15496
|
noteId: newNote.id,
|
|
@@ -15049,7 +15499,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15049
15499
|
dragOffsetPitch: 0,
|
|
15050
15500
|
startStep: newNote.startStep,
|
|
15051
15501
|
durationSteps: newNote.durationSteps,
|
|
15052
|
-
lastPreviewPitch: newNote.
|
|
15502
|
+
lastPreviewPitch: newNote.pitchUnits
|
|
15053
15503
|
};
|
|
15054
15504
|
hasDragged = true;
|
|
15055
15505
|
}
|
|
@@ -15060,7 +15510,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15060
15510
|
const active = getActive();
|
|
15061
15511
|
if (activeToolMode === "pen") {
|
|
15062
15512
|
if (!dragState) return;
|
|
15063
|
-
const { step, pitch } = getGridPosition(event);
|
|
15513
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15064
15514
|
hasDragged = true;
|
|
15065
15515
|
if (dragState.mode === "move") {
|
|
15066
15516
|
const nextStart = step - dragState.dragOffsetStep;
|
|
@@ -15084,7 +15534,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15084
15534
|
}
|
|
15085
15535
|
if (activeToolMode === "select" && isSelecting && selectionStart) {
|
|
15086
15536
|
ensureAutoScroll(event);
|
|
15087
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15537
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
15088
15538
|
if (dragMode === "rect") {
|
|
15089
15539
|
const rect = {
|
|
15090
15540
|
x: Math.min(x2, selectionStart.x),
|
|
@@ -15093,11 +15543,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
15093
15543
|
height: Math.abs(y - selectionStart.y)
|
|
15094
15544
|
};
|
|
15095
15545
|
selectionRect = rect;
|
|
15096
|
-
const {
|
|
15097
|
-
|
|
15546
|
+
const {
|
|
15547
|
+
stepWidth,
|
|
15548
|
+
keyHeight,
|
|
15549
|
+
keyCount,
|
|
15550
|
+
pitchRangeStart,
|
|
15551
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15552
|
+
} = renderConfig;
|
|
15553
|
+
const offset = renderer.getDrawOffset();
|
|
15098
15554
|
selectedNotes = active.core.getNotes().filter((note) => {
|
|
15099
15555
|
const logicalX = note.startStep * stepWidth;
|
|
15100
|
-
const yIndex = keyCount - 1 - (note.
|
|
15556
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
15101
15557
|
const logicalY = yIndex * keyHeight;
|
|
15102
15558
|
const nx = logicalX - offset.x;
|
|
15103
15559
|
const ny = logicalY - offset.y;
|
|
@@ -15116,7 +15572,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15116
15572
|
const orig = selectedOriginal.find((o) => o.id === note.id);
|
|
15117
15573
|
if (!orig) continue;
|
|
15118
15574
|
const newPitch = orig.pitch + deltaPitch;
|
|
15119
|
-
if (newPitch >=
|
|
15575
|
+
if (newPitch >= PITCH_RANGE_START && newPitch <= PITCH_RANGE_END)
|
|
15120
15576
|
active.core.moveNote(
|
|
15121
15577
|
note.id,
|
|
15122
15578
|
orig.startStep + snappedDelta,
|
|
@@ -15264,22 +15720,23 @@ var mountDAW = (target, options = {}) => {
|
|
|
15264
15720
|
refs.bgOpacityRow.classList.add("dtm-hidden");
|
|
15265
15721
|
}
|
|
15266
15722
|
refs.bgRemoveBtn.classList.toggle("dtm-hidden", !blob2);
|
|
15267
|
-
setBackgroundActive(!!blob2);
|
|
15723
|
+
renderer.setBackgroundActive(!!blob2);
|
|
15268
15724
|
redrawAll();
|
|
15269
15725
|
};
|
|
15270
15726
|
const setupCanvas = () => {
|
|
15271
15727
|
const w = refs.rollContainer.clientWidth || 800;
|
|
15272
15728
|
const h = refs.rollContainer.clientHeight || 450;
|
|
15273
|
-
|
|
15274
|
-
|
|
15729
|
+
renderer?.destroy();
|
|
15730
|
+
renderer = createRenderer(refs.wrapper, w, h, renderConfig);
|
|
15731
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
15275
15732
|
gridCanvas.addEventListener("pointerdown", onGridPointerDown);
|
|
15276
15733
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
15277
15734
|
event.preventDefault();
|
|
15278
15735
|
if (isActiveLocked()) return;
|
|
15279
|
-
const { step, pitch } = getGridPosition(event);
|
|
15736
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15280
15737
|
const active = getActive();
|
|
15281
15738
|
const note = active.core.getNotes().find(
|
|
15282
|
-
(n) => n.
|
|
15739
|
+
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
15283
15740
|
);
|
|
15284
15741
|
if (note) active.core.deleteNoteById(note.id);
|
|
15285
15742
|
});
|
|
@@ -15297,7 +15754,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15297
15754
|
0,
|
|
15298
15755
|
getMaxOffsetX()
|
|
15299
15756
|
);
|
|
15300
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15757
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15301
15758
|
redrawAll();
|
|
15302
15759
|
},
|
|
15303
15760
|
{ passive: false }
|
|
@@ -15307,7 +15764,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15307
15764
|
suppressClick = false;
|
|
15308
15765
|
}
|
|
15309
15766
|
});
|
|
15310
|
-
const headerCanvas = getHeaderCanvas();
|
|
15767
|
+
const headerCanvas = renderer.getHeaderCanvas();
|
|
15311
15768
|
headerCanvas.addEventListener("click", (event) => {
|
|
15312
15769
|
if (playbackState === "playing") return;
|
|
15313
15770
|
const rect = headerCanvas.getBoundingClientRect();
|
|
@@ -15323,11 +15780,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15323
15780
|
}
|
|
15324
15781
|
redrawAll();
|
|
15325
15782
|
});
|
|
15326
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15783
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15327
15784
|
redrawAll();
|
|
15328
15785
|
};
|
|
15329
15786
|
const applyZoomX = () => {
|
|
15330
|
-
const canvas = getGridCanvas();
|
|
15787
|
+
const canvas = renderer.getGridCanvas();
|
|
15331
15788
|
const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
|
|
15332
15789
|
renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
|
|
15333
15790
|
refs.zoomXLabel.textContent = `${zoomX}%`;
|
|
@@ -15336,11 +15793,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15336
15793
|
0,
|
|
15337
15794
|
getMaxOffsetX()
|
|
15338
15795
|
);
|
|
15339
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15796
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15340
15797
|
redrawAll();
|
|
15341
15798
|
};
|
|
15342
15799
|
const applyZoomY = () => {
|
|
15343
|
-
const canvas = getGridCanvas();
|
|
15800
|
+
const canvas = renderer.getGridCanvas();
|
|
15344
15801
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
15345
15802
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
15346
15803
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
@@ -15349,7 +15806,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15349
15806
|
0,
|
|
15350
15807
|
getMaxOffsetY()
|
|
15351
15808
|
);
|
|
15352
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15809
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15353
15810
|
redrawAll();
|
|
15354
15811
|
};
|
|
15355
15812
|
const getViewState = () => ({
|
|
@@ -15361,7 +15818,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15361
15818
|
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
15362
15819
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
15363
15820
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
15364
|
-
options.onPlayNote?.({
|
|
15821
|
+
options.onPlayNote?.({
|
|
15822
|
+
trackId,
|
|
15823
|
+
pitchUnits: pitch,
|
|
15824
|
+
velocity,
|
|
15825
|
+
volume,
|
|
15826
|
+
when,
|
|
15827
|
+
duration
|
|
15828
|
+
});
|
|
15365
15829
|
};
|
|
15366
15830
|
const sequencer = createSequencer({
|
|
15367
15831
|
getTracks: () => trackStates.map((t) => ({
|
|
@@ -15389,7 +15853,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15389
15853
|
},
|
|
15390
15854
|
onTick: (step) => {
|
|
15391
15855
|
currentPlayStep = step;
|
|
15392
|
-
const canvas = getGridCanvas();
|
|
15856
|
+
const canvas = renderer.getGridCanvas();
|
|
15393
15857
|
const visibleSteps = canvas.width / renderConfig.stepWidth;
|
|
15394
15858
|
const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
|
|
15395
15859
|
if (currentPlayStep > threshold) {
|
|
@@ -15399,7 +15863,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15399
15863
|
0,
|
|
15400
15864
|
getMaxOffsetX()
|
|
15401
15865
|
);
|
|
15402
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15866
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15403
15867
|
}
|
|
15404
15868
|
redrawAll();
|
|
15405
15869
|
},
|
|
@@ -15430,7 +15894,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15430
15894
|
(a, b) => a.startStep - b.startStep
|
|
15431
15895
|
);
|
|
15432
15896
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
15433
|
-
const semis = (lt.octave ?? 0) *
|
|
15897
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
15434
15898
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
15435
15899
|
const notes = [];
|
|
15436
15900
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -15438,7 +15902,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15438
15902
|
if (n.startStep < fromStep) continue;
|
|
15439
15903
|
notes.push({
|
|
15440
15904
|
syllable: lt.syllables[i2],
|
|
15441
|
-
pitch: n.
|
|
15905
|
+
pitch: n.pitchUnits + semis,
|
|
15442
15906
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
15443
15907
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
15444
15908
|
});
|
|
@@ -15481,13 +15945,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
15481
15945
|
}
|
|
15482
15946
|
}
|
|
15483
15947
|
if (playbackState !== "paused") {
|
|
15484
|
-
const canvas = getGridCanvas();
|
|
15948
|
+
const canvas = renderer.getGridCanvas();
|
|
15485
15949
|
currentOffsetX = clamp5(
|
|
15486
15950
|
playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
|
|
15487
15951
|
0,
|
|
15488
15952
|
getMaxOffsetX()
|
|
15489
15953
|
);
|
|
15490
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15954
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15491
15955
|
}
|
|
15492
15956
|
playbackState = "playing";
|
|
15493
15957
|
sequencer.start(fromStep);
|
|
@@ -16474,6 +16938,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16474
16938
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16475
16939
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16476
16940
|
mode,
|
|
16941
|
+
edo: renderConfig.edo,
|
|
16477
16942
|
trackInstruments: trackInstMeta,
|
|
16478
16943
|
trackCompression: trackCompMeta,
|
|
16479
16944
|
trackWidth: trackWidthMeta,
|
|
@@ -16502,6 +16967,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16502
16967
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16503
16968
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16504
16969
|
mode,
|
|
16970
|
+
edo: renderConfig.edo,
|
|
16505
16971
|
trackInstruments: trackInstMeta,
|
|
16506
16972
|
trackCompression: trackCompMeta,
|
|
16507
16973
|
trackWidth: trackWidthMeta,
|
|
@@ -16641,19 +17107,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
16641
17107
|
}
|
|
16642
17108
|
}
|
|
16643
17109
|
if (candidateNotes.length === 0) return null;
|
|
16644
|
-
const sum = candidateNotes.reduce((acc, note) => acc + note.
|
|
17110
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitchUnits, 0);
|
|
16645
17111
|
return Math.round(sum / candidateNotes.length);
|
|
16646
17112
|
};
|
|
16647
17113
|
const centerPitch = (pitch) => {
|
|
16648
|
-
const canvas = getGridCanvas();
|
|
16649
|
-
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
17114
|
+
const canvas = renderer.getGridCanvas();
|
|
17115
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE);
|
|
16650
17116
|
const logicalY = yIndex * renderConfig.keyHeight;
|
|
16651
17117
|
currentOffsetY = clamp5(
|
|
16652
17118
|
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
16653
17119
|
0,
|
|
16654
17120
|
getMaxOffsetY()
|
|
16655
17121
|
);
|
|
16656
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
17122
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16657
17123
|
};
|
|
16658
17124
|
const clearAll = () => {
|
|
16659
17125
|
for (const t of trackStates) {
|
|
@@ -16711,10 +17177,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
16711
17177
|
if (!meta.drumFont) applyDrumPatternFont(meta.drum);
|
|
16712
17178
|
}
|
|
16713
17179
|
if (meta.volume !== void 0) {
|
|
16714
|
-
|
|
16715
|
-
refs.masterVolume.value = String(meta.volume);
|
|
16716
|
-
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
17180
|
+
applyMasterVolume(meta.volume);
|
|
16717
17181
|
}
|
|
17182
|
+
applyEdo(meta.edo ?? 12);
|
|
17183
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16718
17184
|
if (meta.drumVolume !== void 0) {
|
|
16719
17185
|
drumVolume = meta.drumVolume;
|
|
16720
17186
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -16879,7 +17345,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16879
17345
|
if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
|
|
16880
17346
|
const t = trackStates[p.trackIndex];
|
|
16881
17347
|
if (!t) continue;
|
|
16882
|
-
t.core.addNote(p.startStep, p.
|
|
17348
|
+
t.core.addNote(p.startStep, p.pitchUnits, {
|
|
16883
17349
|
noteLengthSteps: p.durationSteps,
|
|
16884
17350
|
velocity: DEFAULT_VELOCITY
|
|
16885
17351
|
});
|
|
@@ -16896,7 +17362,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16896
17362
|
if (firstPitch !== null) {
|
|
16897
17363
|
centerPitch(firstPitch);
|
|
16898
17364
|
} else {
|
|
16899
|
-
centerPitch(48);
|
|
17365
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16900
17366
|
}
|
|
16901
17367
|
redrawAll();
|
|
16902
17368
|
updateTrackPanel();
|
|
@@ -16914,6 +17380,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16914
17380
|
const chordTrack = trackStates.find((t) => t.config.id === "chord");
|
|
16915
17381
|
if (!chordTrack) return;
|
|
16916
17382
|
const placements = buildChordPlacements({
|
|
17383
|
+
// 和音は五度連鎖経由で音律の格子へ写す(31平均律では長3度が10度になる)
|
|
17384
|
+
edo: renderConfig.edo,
|
|
16917
17385
|
chordStr: active.savedChordInput,
|
|
16918
17386
|
patternType: active.savedChordPattern,
|
|
16919
17387
|
rootShift: active.savedChordRoot,
|
|
@@ -16923,7 +17391,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16923
17391
|
chordTrack.core.clearNotesWithoutHistory();
|
|
16924
17392
|
chordTrack.core.beginBatch();
|
|
16925
17393
|
for (const p of placements) {
|
|
16926
|
-
chordTrack.core.addNote(p.startStep, p.
|
|
17394
|
+
chordTrack.core.addNote(p.startStep, p.pitchUnits, {
|
|
16927
17395
|
noteLengthSteps: Math.max(1, p.durationSteps),
|
|
16928
17396
|
velocity: p.velocity
|
|
16929
17397
|
});
|
|
@@ -16961,6 +17429,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16961
17429
|
active.vocalOctaveUnison = "none";
|
|
16962
17430
|
} else {
|
|
16963
17431
|
clearAll();
|
|
17432
|
+
applyEdo(12);
|
|
17433
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16964
17434
|
for (const t of trackStates) t.core.setLoadMode(true);
|
|
16965
17435
|
for (const t of trackStates) {
|
|
16966
17436
|
t.lyrics = "";
|
|
@@ -16987,7 +17457,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16987
17457
|
if (applyActiveOnly && p.trackId !== activeTrackId) continue;
|
|
16988
17458
|
const t = trackStates.find((ts) => ts.config.id === p.trackId);
|
|
16989
17459
|
if (!t) continue;
|
|
16990
|
-
t.core.addNote(p.startStep, p.pitch, {
|
|
17460
|
+
t.core.addNote(p.startStep, snapToEdoGrid(pitchV1ToUnits(p.pitch)), {
|
|
16991
17461
|
noteLengthSteps: p.durationSteps,
|
|
16992
17462
|
velocity: p.velocity
|
|
16993
17463
|
});
|
|
@@ -17004,7 +17474,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17004
17474
|
if (firstPitch !== null) {
|
|
17005
17475
|
centerPitch(firstPitch);
|
|
17006
17476
|
} else {
|
|
17007
|
-
centerPitch(48);
|
|
17477
|
+
centerPitch(pitchV1ToUnits(48));
|
|
17008
17478
|
}
|
|
17009
17479
|
redrawAll();
|
|
17010
17480
|
updateTrackPanel();
|
|
@@ -17068,18 +17538,21 @@ var mountDAW = (target, options = {}) => {
|
|
|
17068
17538
|
}
|
|
17069
17539
|
}, 30);
|
|
17070
17540
|
};
|
|
17071
|
-
const showConfirmModal = (message) => new Promise((resolve) => {
|
|
17541
|
+
const showConfirmModal = (message, opts) => new Promise((resolve) => {
|
|
17542
|
+
const title = opts?.title ?? "\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D";
|
|
17543
|
+
const yes = opts?.yes ?? "\u306F\u3044\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u308B\uFF09";
|
|
17544
|
+
const no = opts?.no ?? "\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09";
|
|
17072
17545
|
const overlay = document.createElement("div");
|
|
17073
17546
|
overlay.className = "dtm-modal-overlay";
|
|
17074
17547
|
overlay.innerHTML = `
|
|
17075
17548
|
<div class="dtm-modal">
|
|
17076
17549
|
<div class="dtm-modal-header">
|
|
17077
|
-
<span class="dtm-modal-title"
|
|
17550
|
+
<span class="dtm-modal-title">${title}</span>
|
|
17078
17551
|
</div>
|
|
17079
17552
|
<div class="dtm-modal-body"><p>${message}</p></div>
|
|
17080
17553
|
<div class="dtm-confirm-footer">
|
|
17081
|
-
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no"
|
|
17082
|
-
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes"
|
|
17554
|
+
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no">${no}</button>
|
|
17555
|
+
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes">${yes}</button>
|
|
17083
17556
|
</div>
|
|
17084
17557
|
</div>`;
|
|
17085
17558
|
const close = (result) => {
|
|
@@ -17187,10 +17660,36 @@ var mountDAW = (target, options = {}) => {
|
|
|
17187
17660
|
});
|
|
17188
17661
|
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
17189
17662
|
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
17663
|
+
refs.edoSelect.addEventListener("change", async () => {
|
|
17664
|
+
const next = Number.parseInt(refs.edoSelect.value, 10) === 31 ? 31 : 12;
|
|
17665
|
+
const prev = renderConfig.edo ?? 12;
|
|
17666
|
+
if (next === prev) return;
|
|
17667
|
+
const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
|
|
17668
|
+
if (next === 12 && prev === 31 && hasNotes) {
|
|
17669
|
+
const okToDrop = await showConfirmModal(
|
|
17670
|
+
"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",
|
|
17671
|
+
{
|
|
17672
|
+
title: "\u97F3\u5F8B\u306E\u78BA\u8A8D",
|
|
17673
|
+
yes: "\u5207\u308A\u66FF\u3048\u308B",
|
|
17674
|
+
no: "\u3084\u3081\u308B"
|
|
17675
|
+
}
|
|
17676
|
+
);
|
|
17677
|
+
if (!okToDrop) {
|
|
17678
|
+
refs.edoSelect.value = String(prev);
|
|
17679
|
+
return;
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
applyEdo(next, { snap: true });
|
|
17683
|
+
const first = getFirstDetectedPitch();
|
|
17684
|
+
centerPitch(first ?? pitchV1ToUnits(48));
|
|
17685
|
+
redrawAll();
|
|
17686
|
+
updateUndoRedo();
|
|
17687
|
+
});
|
|
17688
|
+
refs.edoInfoBtn.addEventListener("click", () => {
|
|
17689
|
+
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
17690
|
+
});
|
|
17190
17691
|
refs.masterVolume.addEventListener("input", () => {
|
|
17191
|
-
|
|
17192
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17193
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17692
|
+
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
17194
17693
|
});
|
|
17195
17694
|
refs.masterComp.addEventListener("input", () => {
|
|
17196
17695
|
masterCompression = Number.parseInt(refs.masterComp.value, 10) || 0;
|
|
@@ -17254,10 +17753,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17254
17753
|
const scale = targetPeak / observedPeakMax;
|
|
17255
17754
|
const suggested = clamp5(Math.round(masterVolume * scale), 10, 100);
|
|
17256
17755
|
if (suggested !== masterVolume) {
|
|
17257
|
-
|
|
17258
|
-
refs.masterVolume.value = String(masterVolume);
|
|
17259
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17260
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17756
|
+
applyMasterVolume(suggested);
|
|
17261
17757
|
}
|
|
17262
17758
|
observedPeakMax = 0;
|
|
17263
17759
|
observedPlayMs = 0;
|
|
@@ -17417,7 +17913,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
17417
17913
|
generateRandomPattern(getActive().core, {
|
|
17418
17914
|
stepsPerBar: renderConfig.stepsPerBar,
|
|
17419
17915
|
startStep: playStartStep,
|
|
17420
|
-
pitchRangeStart: renderConfig.pitchRangeStart
|
|
17916
|
+
pitchRangeStart: renderConfig.pitchRangeStart,
|
|
17917
|
+
edo: renderConfig.edo
|
|
17421
17918
|
});
|
|
17422
17919
|
redrawAll();
|
|
17423
17920
|
});
|
|
@@ -17754,7 +18251,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17754
18251
|
id: 0,
|
|
17755
18252
|
startStep: p.startStep,
|
|
17756
18253
|
durationSteps: p.durationSteps,
|
|
17757
|
-
|
|
18254
|
+
pitchUnits: snapToEdoGrid(pitchV1ToUnits(p.pitch)),
|
|
17758
18255
|
velocity: p.velocity
|
|
17759
18256
|
}));
|
|
17760
18257
|
const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
|
|
@@ -18209,10 +18706,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18209
18706
|
const newStart = playStartStep + (note.startStep - minStart);
|
|
18210
18707
|
const newEnd = newStart + note.durationSteps;
|
|
18211
18708
|
const overlap = notes.some(
|
|
18212
|
-
(ex) => ex.
|
|
18709
|
+
(ex) => ex.pitchUnits === note.pitchUnits && newStart < ex.startStep + ex.durationSteps && newEnd > ex.startStep
|
|
18213
18710
|
);
|
|
18214
18711
|
if (!overlap)
|
|
18215
|
-
core.addNote(newStart, note.
|
|
18712
|
+
core.addNote(newStart, note.pitchUnits, {
|
|
18216
18713
|
noteLengthSteps: note.durationSteps,
|
|
18217
18714
|
velocity: note.velocity
|
|
18218
18715
|
});
|
|
@@ -18285,13 +18782,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
18285
18782
|
pausedPlayStep = step;
|
|
18286
18783
|
currentPlayStep = step;
|
|
18287
18784
|
playbackState = "paused";
|
|
18288
|
-
const canvas = getGridCanvas();
|
|
18785
|
+
const canvas = renderer.getGridCanvas();
|
|
18289
18786
|
currentOffsetX = clamp5(
|
|
18290
18787
|
step * renderConfig.stepWidth - canvas.width * 0.5,
|
|
18291
18788
|
0,
|
|
18292
18789
|
getMaxOffsetX()
|
|
18293
18790
|
);
|
|
18294
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18791
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18295
18792
|
updateTransport();
|
|
18296
18793
|
redrawAll();
|
|
18297
18794
|
};
|
|
@@ -18359,16 +18856,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18359
18856
|
forcePauseAt,
|
|
18360
18857
|
setLoading,
|
|
18361
18858
|
setMasterVolume: (volume) => {
|
|
18362
|
-
|
|
18363
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18364
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18365
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18859
|
+
applyMasterVolume(volume);
|
|
18366
18860
|
},
|
|
18367
18861
|
setVolume: (volume) => {
|
|
18368
|
-
|
|
18369
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18370
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18371
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18862
|
+
applyMasterVolume(volume);
|
|
18372
18863
|
},
|
|
18373
18864
|
setDrumVolume: (volume) => {
|
|
18374
18865
|
drumVolume = clamp5(volume, 0, 100);
|
|
@@ -18411,15 +18902,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
18411
18902
|
suppressPatch = true;
|
|
18412
18903
|
track.core.beginBatch();
|
|
18413
18904
|
for (const n of added) {
|
|
18414
|
-
const existing = track.core.getNotes().find(
|
|
18905
|
+
const existing = track.core.getNotes().find(
|
|
18906
|
+
(e) => e.startStep === n.startStep && e.pitchUnits === n.pitchUnits
|
|
18907
|
+
);
|
|
18415
18908
|
if (existing) track.core.deleteNoteById(existing.id);
|
|
18416
|
-
track.core.addNote(n.startStep, n.
|
|
18909
|
+
track.core.addNote(n.startStep, n.pitchUnits, {
|
|
18417
18910
|
noteLengthSteps: n.durationSteps,
|
|
18418
18911
|
velocity: n.velocity
|
|
18419
18912
|
});
|
|
18420
18913
|
}
|
|
18421
18914
|
for (const r of removed) {
|
|
18422
|
-
const note = track.core.getNotes().find(
|
|
18915
|
+
const note = track.core.getNotes().find(
|
|
18916
|
+
(n) => n.startStep === r.startStep && n.pitchUnits === r.pitchUnits
|
|
18917
|
+
);
|
|
18423
18918
|
if (note) track.core.deleteNoteById(note.id);
|
|
18424
18919
|
}
|
|
18425
18920
|
track.core.endBatch();
|
|
@@ -18461,9 +18956,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
18461
18956
|
if (t.config.id === activeTrackId) updateTrackPanel();
|
|
18462
18957
|
},
|
|
18463
18958
|
noteToCanvas: (step, pitch) => {
|
|
18464
|
-
const canvas = getGridCanvas();
|
|
18959
|
+
const canvas = renderer.getGridCanvas();
|
|
18465
18960
|
const x2 = step * renderConfig.stepWidth - currentOffsetX;
|
|
18466
|
-
const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
|
|
18961
|
+
const y = (renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - currentOffsetY;
|
|
18467
18962
|
const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
|
|
18468
18963
|
let side = null;
|
|
18469
18964
|
if (!onScreen) {
|
|
@@ -18527,7 +19022,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18527
19022
|
id: id++,
|
|
18528
19023
|
startStep: p.startStep,
|
|
18529
19024
|
durationSteps: p.durationSteps,
|
|
18530
|
-
|
|
19025
|
+
pitchUnits: p.pitchUnits,
|
|
18531
19026
|
velocity: p.velocity
|
|
18532
19027
|
}));
|
|
18533
19028
|
return {
|
|
@@ -18553,7 +19048,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18553
19048
|
(a, b) => a.startStep - b.startStep
|
|
18554
19049
|
);
|
|
18555
19050
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
18556
|
-
const semis = (lt.octave ?? 0) *
|
|
19051
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
18557
19052
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
18558
19053
|
const notes = [];
|
|
18559
19054
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -18561,7 +19056,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18561
19056
|
if (n.startStep < fromStep) continue;
|
|
18562
19057
|
notes.push({
|
|
18563
19058
|
syllable: lt.syllables[i2],
|
|
18564
|
-
pitch: n.
|
|
19059
|
+
pitch: n.pitchUnits + semis,
|
|
18565
19060
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
18566
19061
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
18567
19062
|
});
|
|
@@ -18749,24 +19244,28 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18749
19244
|
config,
|
|
18750
19245
|
noteLengthSteps = 1
|
|
18751
19246
|
} = options;
|
|
18752
|
-
|
|
19247
|
+
const renderer = createRenderer(mountTarget, width, height, config);
|
|
18753
19248
|
let currentNoteLengthSteps = noteLengthSteps;
|
|
18754
19249
|
let selectionRect = null;
|
|
18755
19250
|
let isSelecting = false;
|
|
18756
19251
|
let selectionStart = null;
|
|
18757
19252
|
let selectedNotes = [];
|
|
18758
19253
|
let copiedNotes = [];
|
|
18759
|
-
const core = new MMLCore(
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
19254
|
+
const core = new MMLCore(
|
|
19255
|
+
{
|
|
19256
|
+
onMMLGenerated: handlers.onMMLGenerated,
|
|
19257
|
+
onNotesChanged: (notes) => {
|
|
19258
|
+
handlers.onNotesChanged(notes);
|
|
19259
|
+
}
|
|
19260
|
+
},
|
|
19261
|
+
80,
|
|
19262
|
+
() => config
|
|
19263
|
+
);
|
|
18765
19264
|
const getAddNoteOptions = () => ({
|
|
18766
19265
|
noteLengthSteps: currentNoteLengthSteps
|
|
18767
19266
|
});
|
|
18768
19267
|
let suppressClick = false;
|
|
18769
|
-
onClick((step, pitch) => {
|
|
19268
|
+
renderer.onClick((step, pitch) => {
|
|
18770
19269
|
if (suppressClick) {
|
|
18771
19270
|
suppressClick = false;
|
|
18772
19271
|
return;
|
|
@@ -18778,7 +19277,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18778
19277
|
} else if (mode === "eraser") {
|
|
18779
19278
|
const notes = core.getNotes();
|
|
18780
19279
|
const note = notes.find(
|
|
18781
|
-
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.
|
|
19280
|
+
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.pitchUnits === pitch
|
|
18782
19281
|
);
|
|
18783
19282
|
if (note) {
|
|
18784
19283
|
core.deleteNoteById(note.id);
|
|
@@ -18786,17 +19285,23 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18786
19285
|
}
|
|
18787
19286
|
}
|
|
18788
19287
|
});
|
|
18789
|
-
const gridCanvas = getGridCanvas();
|
|
19288
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
18790
19289
|
const resizeHandleWidth = 6;
|
|
18791
19290
|
let dragState = null;
|
|
18792
19291
|
let hasDragged = false;
|
|
18793
19292
|
let lastPreviewPitch = null;
|
|
18794
19293
|
const findNoteAtPosition = (x2, y) => {
|
|
18795
|
-
const {
|
|
18796
|
-
|
|
19294
|
+
const {
|
|
19295
|
+
stepWidth,
|
|
19296
|
+
keyHeight,
|
|
19297
|
+
keyCount,
|
|
19298
|
+
pitchRangeStart,
|
|
19299
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19300
|
+
} = renderer.getRenderConfig();
|
|
19301
|
+
const offset = renderer.getDrawOffset();
|
|
18797
19302
|
for (const note of core.getNotes()) {
|
|
18798
19303
|
const logicalX = note.startStep * stepWidth;
|
|
18799
|
-
const yIndex = keyCount - 1 - (note.
|
|
19304
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18800
19305
|
const logicalY = yIndex * keyHeight;
|
|
18801
19306
|
const w = note.durationSteps * stepWidth;
|
|
18802
19307
|
const h = keyHeight;
|
|
@@ -18810,7 +19315,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18810
19315
|
};
|
|
18811
19316
|
const handlePointerMove = (e) => {
|
|
18812
19317
|
if (core.getToolMode() === "select" && isSelecting && selectionStart) {
|
|
18813
|
-
const { x: x2, y } = getGridPosition(e);
|
|
19318
|
+
const { x: x2, y } = renderer.getGridPosition(e);
|
|
18814
19319
|
const minX = Math.min(x2, selectionStart.x);
|
|
18815
19320
|
const minY = Math.min(y, selectionStart.y);
|
|
18816
19321
|
const width2 = Math.abs(x2 - selectionStart.x);
|
|
@@ -18822,7 +19327,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18822
19327
|
}
|
|
18823
19328
|
if (!dragState) return;
|
|
18824
19329
|
hasDragged = true;
|
|
18825
|
-
const { step, pitch } = getGridPosition(e);
|
|
19330
|
+
const { step, pitch } = renderer.getGridPosition(e);
|
|
18826
19331
|
if (dragState.mode === "move") {
|
|
18827
19332
|
if (dragState.selectedNotes && dragState.selectedNotes.length > 0) {
|
|
18828
19333
|
const noteId = dragState.noteId;
|
|
@@ -18831,10 +19336,10 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18831
19336
|
const nextStart2 = step - dragState.dragOffsetStep;
|
|
18832
19337
|
const nextPitch2 = pitch - dragState.dragOffsetPitch;
|
|
18833
19338
|
const stepDelta = nextStart2 - baseNote.startStep;
|
|
18834
|
-
const pitchDelta = nextPitch2 - baseNote.
|
|
19339
|
+
const pitchDelta = nextPitch2 - baseNote.pitchUnits;
|
|
18835
19340
|
for (const note of dragState.selectedNotes) {
|
|
18836
19341
|
const newStart = note.startStep + stepDelta;
|
|
18837
|
-
const newPitch = note.
|
|
19342
|
+
const newPitch = note.pitchUnits + pitchDelta;
|
|
18838
19343
|
core.moveNote(note.id, newStart, newPitch);
|
|
18839
19344
|
}
|
|
18840
19345
|
if (options.onPreviewSound && pitch !== lastPreviewPitch) {
|
|
@@ -18875,7 +19380,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18875
19380
|
}
|
|
18876
19381
|
};
|
|
18877
19382
|
gridCanvas.addEventListener("pointerdown", (e) => {
|
|
18878
|
-
const { x: x2, y, step, pitch } = getGridPosition(e);
|
|
19383
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(e);
|
|
18879
19384
|
const currentMode = core.getToolMode();
|
|
18880
19385
|
if (currentMode === "select") {
|
|
18881
19386
|
const clickedNote = findNoteAtPosition(x2, y);
|
|
@@ -18886,7 +19391,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18886
19391
|
noteId: clickedNote.id,
|
|
18887
19392
|
mode: "move",
|
|
18888
19393
|
dragOffsetStep: step - clickedNote.startStep,
|
|
18889
|
-
dragOffsetPitch: pitch - clickedNote.
|
|
19394
|
+
dragOffsetPitch: pitch - clickedNote.pitchUnits,
|
|
18890
19395
|
startStep: clickedNote.startStep,
|
|
18891
19396
|
selectedNotes: notesInRect
|
|
18892
19397
|
// 複数選択ノートを保存
|
|
@@ -18904,10 +19409,16 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18904
19409
|
}
|
|
18905
19410
|
const note = findNoteAtPosition(x2, y);
|
|
18906
19411
|
if (!note) return;
|
|
18907
|
-
const {
|
|
18908
|
-
|
|
19412
|
+
const {
|
|
19413
|
+
stepWidth,
|
|
19414
|
+
keyHeight,
|
|
19415
|
+
keyCount,
|
|
19416
|
+
pitchRangeStart,
|
|
19417
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19418
|
+
} = renderer.getRenderConfig();
|
|
19419
|
+
const offset = renderer.getDrawOffset();
|
|
18909
19420
|
const logicalX = note.startStep * stepWidth;
|
|
18910
|
-
const yIndex = keyCount - 1 - (note.
|
|
19421
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18911
19422
|
const logicalY = yIndex * keyHeight;
|
|
18912
19423
|
const renderX = logicalX - offset.x;
|
|
18913
19424
|
const renderY = logicalY - offset.y;
|
|
@@ -18926,7 +19437,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18926
19437
|
noteId: note.id,
|
|
18927
19438
|
mode: "move",
|
|
18928
19439
|
dragOffsetStep: step - note.startStep,
|
|
18929
|
-
dragOffsetPitch: pitch - note.
|
|
19440
|
+
dragOffsetPitch: pitch - note.pitchUnits,
|
|
18930
19441
|
startStep: note.startStep
|
|
18931
19442
|
};
|
|
18932
19443
|
});
|
|
@@ -18937,41 +19448,47 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18937
19448
|
"wheel",
|
|
18938
19449
|
(e) => {
|
|
18939
19450
|
e.preventDefault();
|
|
18940
|
-
const configValues = getRenderConfig();
|
|
19451
|
+
const configValues = renderer.getRenderConfig();
|
|
18941
19452
|
const gridHeight = gridCanvas.height;
|
|
18942
19453
|
const maxOffsetY = Math.max(
|
|
18943
19454
|
0,
|
|
18944
19455
|
configValues.keyCount * configValues.keyHeight - gridHeight
|
|
18945
19456
|
);
|
|
18946
|
-
const currentOffset = getDrawOffset();
|
|
19457
|
+
const currentOffset = renderer.getDrawOffset();
|
|
18947
19458
|
const nextOffsetY = Math.min(
|
|
18948
19459
|
Math.max(currentOffset.y + e.deltaY, 0),
|
|
18949
19460
|
maxOffsetY
|
|
18950
19461
|
);
|
|
18951
|
-
setDrawOffset(currentOffset.x, nextOffsetY);
|
|
18952
|
-
drawGrid();
|
|
18953
|
-
drawNotes(core.getNotes());
|
|
19462
|
+
renderer.setDrawOffset(currentOffset.x, nextOffsetY);
|
|
19463
|
+
renderer.drawGrid();
|
|
19464
|
+
renderer.drawNotes(core.getNotes());
|
|
18954
19465
|
},
|
|
18955
19466
|
{ passive: false }
|
|
18956
19467
|
);
|
|
18957
19468
|
const redraw = () => {
|
|
18958
|
-
drawGrid();
|
|
18959
|
-
drawNotes(core.getNotes());
|
|
19469
|
+
renderer.drawGrid();
|
|
19470
|
+
renderer.drawNotes(core.getNotes());
|
|
18960
19471
|
if (core.getToolMode() === "select") {
|
|
18961
|
-
drawSelectionRect(selectionRect);
|
|
19472
|
+
renderer.drawSelectionRect(selectionRect);
|
|
18962
19473
|
if (selectedNotes.length > 0) {
|
|
18963
19474
|
const selectedIds = new Set(selectedNotes.map((n) => n.id));
|
|
18964
|
-
drawSelectedNotes(core.getNotes(), selectedIds);
|
|
19475
|
+
renderer.drawSelectedNotes(core.getNotes(), selectedIds);
|
|
18965
19476
|
}
|
|
18966
19477
|
}
|
|
18967
19478
|
};
|
|
18968
19479
|
const getNotesInRect = (rect) => {
|
|
18969
|
-
const {
|
|
18970
|
-
|
|
19480
|
+
const {
|
|
19481
|
+
stepWidth,
|
|
19482
|
+
keyHeight,
|
|
19483
|
+
keyCount,
|
|
19484
|
+
pitchRangeStart,
|
|
19485
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19486
|
+
} = renderer.getRenderConfig();
|
|
19487
|
+
const offset = renderer.getDrawOffset();
|
|
18971
19488
|
const notes = [];
|
|
18972
19489
|
for (const note of core.getNotes()) {
|
|
18973
19490
|
const logicalX = note.startStep * stepWidth;
|
|
18974
|
-
const yIndex = keyCount - 1 - (note.
|
|
19491
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18975
19492
|
const logicalY = yIndex * keyHeight;
|
|
18976
19493
|
const noteRect = {
|
|
18977
19494
|
x: logicalX - offset.x,
|
|
@@ -19018,7 +19535,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
19018
19535
|
const minStart = Math.min(...copiedNotes.map((n) => n.startStep));
|
|
19019
19536
|
copiedNotes.forEach((note) => {
|
|
19020
19537
|
const newStep = startStep + (note.startStep - minStart);
|
|
19021
|
-
core.addNote(newStep, note.
|
|
19538
|
+
core.addNote(newStep, note.pitchUnits, {
|
|
19022
19539
|
noteLengthSteps: note.durationSteps,
|
|
19023
19540
|
velocity: note.velocity
|
|
19024
19541
|
});
|
|
@@ -19860,10 +20377,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
19860
20377
|
);
|
|
19861
20378
|
}
|
|
19862
20379
|
if (!sfInst) return;
|
|
20380
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
19863
20381
|
sfInst.play({
|
|
19864
20382
|
ctx: audioCtx,
|
|
19865
20383
|
destination: getChannelStrip(e.trackId).input,
|
|
19866
|
-
pitch:
|
|
20384
|
+
pitch: midi,
|
|
20385
|
+
detuneCents,
|
|
19867
20386
|
volume: e.volume,
|
|
19868
20387
|
velocity: e.velocity,
|
|
19869
20388
|
when: e.when,
|
|
@@ -20179,10 +20698,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20179
20698
|
);
|
|
20180
20699
|
}
|
|
20181
20700
|
if (!sfInst) return;
|
|
20701
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20182
20702
|
sfInst.play({
|
|
20183
20703
|
ctx: audioCtx,
|
|
20184
20704
|
destination: getChannelStrip(e.trackId).input,
|
|
20185
|
-
pitch:
|
|
20705
|
+
pitch: midi,
|
|
20706
|
+
detuneCents,
|
|
20186
20707
|
volume: e.volume,
|
|
20187
20708
|
velocity: e.velocity,
|
|
20188
20709
|
when: e.when,
|
|
@@ -20268,10 +20789,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20268
20789
|
);
|
|
20269
20790
|
}
|
|
20270
20791
|
if (!sfInst) return;
|
|
20792
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20271
20793
|
sfInst.play({
|
|
20272
20794
|
ctx: audioCtx,
|
|
20273
20795
|
destination: getChannelStrip(e.trackId).input,
|
|
20274
|
-
pitch:
|
|
20796
|
+
pitch: midi,
|
|
20797
|
+
detuneCents,
|
|
20275
20798
|
volume: e.volume,
|
|
20276
20799
|
velocity: e.velocity,
|
|
20277
20800
|
when: e.when,
|
|
@@ -20350,10 +20873,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20350
20873
|
);
|
|
20351
20874
|
}
|
|
20352
20875
|
if (!sfInst) return;
|
|
20876
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20353
20877
|
sfInst.play({
|
|
20354
20878
|
ctx: audioCtx,
|
|
20355
20879
|
destination: getChannelStrip(e.trackId).input,
|
|
20356
|
-
pitch:
|
|
20880
|
+
pitch: midi,
|
|
20881
|
+
detuneCents,
|
|
20357
20882
|
volume: e.volume,
|
|
20358
20883
|
velocity: e.velocity,
|
|
20359
20884
|
when: e.when,
|
|
@@ -20390,10 +20915,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20390
20915
|
sfInst = resolveSoundFont(defaultPreset, role, "simple");
|
|
20391
20916
|
}
|
|
20392
20917
|
if (!sfInst) return;
|
|
20918
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20393
20919
|
sfInst.play({
|
|
20394
20920
|
ctx: audioCtx,
|
|
20395
20921
|
destination: getChannelStrip(e.trackId).input,
|
|
20396
|
-
pitch:
|
|
20922
|
+
pitch: midi,
|
|
20923
|
+
detuneCents,
|
|
20397
20924
|
volume: e.volume,
|
|
20398
20925
|
velocity: e.velocity,
|
|
20399
20926
|
when: e.when,
|
|
@@ -20416,10 +20943,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20416
20943
|
if (audioCtx.state === "suspended") {
|
|
20417
20944
|
await audioCtx.resume();
|
|
20418
20945
|
}
|
|
20946
|
+
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
20419
20947
|
sfInst.play({
|
|
20420
20948
|
ctx: audioCtx,
|
|
20421
20949
|
destination: masterGain,
|
|
20422
|
-
pitch:
|
|
20950
|
+
pitch: midi,
|
|
20951
|
+
detuneCents,
|
|
20423
20952
|
volume: vol / 100,
|
|
20424
20953
|
when: 0,
|
|
20425
20954
|
duration: dur
|
|
@@ -20439,7 +20968,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20439
20968
|
// 伴奏トラック
|
|
20440
20969
|
startStep: p.startStep,
|
|
20441
20970
|
durationSteps: p.durationSteps,
|
|
20442
|
-
|
|
20971
|
+
pitchUnits: p.pitchUnits,
|
|
20443
20972
|
velocity: p.velocity
|
|
20444
20973
|
}));
|
|
20445
20974
|
const playerPreset = defaultPreset;
|
|
@@ -20447,10 +20976,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20447
20976
|
const playPlayerNote = (e) => {
|
|
20448
20977
|
const sfInst = resolveSoundFont(playerPreset, "chord");
|
|
20449
20978
|
if (!sfInst) return;
|
|
20979
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20450
20980
|
sfInst.play({
|
|
20451
20981
|
ctx: audioCtx,
|
|
20452
20982
|
destination: getChannelStrip("chord").input,
|
|
20453
|
-
pitch:
|
|
20983
|
+
pitch: midi,
|
|
20984
|
+
detuneCents,
|
|
20454
20985
|
volume: e.volume,
|
|
20455
20986
|
velocity: e.velocity,
|
|
20456
20987
|
when: e.when,
|
|
@@ -20589,8 +21120,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20589
21120
|
};
|
|
20590
21121
|
// Annotate the CommonJS export names for ESM import in node:
|
|
20591
21122
|
0 && (module.exports = {
|
|
21123
|
+
A4_HZ,
|
|
21124
|
+
A4_UNITS,
|
|
21125
|
+
CENTS_PER_UNIT,
|
|
20592
21126
|
DAW_CSS,
|
|
20593
21127
|
DEFAULT_BPM,
|
|
21128
|
+
DEFAULT_EDO,
|
|
20594
21129
|
DEFAULT_GATE,
|
|
20595
21130
|
DEFAULT_PAN,
|
|
20596
21131
|
DEFAULT_PLAYBACK_VELOCITY,
|
|
@@ -20600,21 +21135,30 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20600
21135
|
DRUM_FONT,
|
|
20601
21136
|
DRUM_KEYS,
|
|
20602
21137
|
DRUM_PATTERNS,
|
|
21138
|
+
EDO31_NAMES,
|
|
20603
21139
|
GM_INSTRUMENT_NAMES,
|
|
20604
21140
|
INSTRUMENT_PRESETS,
|
|
21141
|
+
KEY_COUNT,
|
|
20605
21142
|
KOE_BASE_URL,
|
|
20606
21143
|
KOE_VOICEBANKS,
|
|
20607
21144
|
KOE_VOICEBANK_LABELS,
|
|
20608
21145
|
KOE_VOICEBANK_TERMS,
|
|
20609
21146
|
LinkedList,
|
|
20610
21147
|
MAX_VOCAL_VOLUME,
|
|
21148
|
+
MICRO_STEP,
|
|
20611
21149
|
MMLCore,
|
|
20612
21150
|
MML_END_MARKER,
|
|
20613
21151
|
MidiSearchClient,
|
|
21152
|
+
PITCH_ENCODING_VERSION,
|
|
20614
21153
|
PITCH_MAP,
|
|
21154
|
+
PITCH_RANGE_END,
|
|
21155
|
+
PITCH_RANGE_START,
|
|
20615
21156
|
PREWARM_NOTES,
|
|
20616
21157
|
TRACKS_ADVANCED,
|
|
20617
21158
|
TRACKS_SIMPLE,
|
|
21159
|
+
UNITS_PER_EDO31_DEGREE,
|
|
21160
|
+
UNITS_PER_OCTAVE,
|
|
21161
|
+
UNITS_PER_SEMITONE,
|
|
20618
21162
|
VIBRATO_MIN_SEC,
|
|
20619
21163
|
VOICE_IMAGES,
|
|
20620
21164
|
VOICE_IMAGE_KEY,
|
|
@@ -20624,6 +21168,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20624
21168
|
buildChordPlacements,
|
|
20625
21169
|
buildDrumPatternJson,
|
|
20626
21170
|
buildNameToKeyMapping,
|
|
21171
|
+
chromaticStep,
|
|
20627
21172
|
collectPitchTokens,
|
|
20628
21173
|
concatFloat32,
|
|
20629
21174
|
createAudioContext,
|
|
@@ -20632,19 +21177,13 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20632
21177
|
createKoeVoice,
|
|
20633
21178
|
createLyricsConductor,
|
|
20634
21179
|
createPianoRoll,
|
|
21180
|
+
createRenderer,
|
|
20635
21181
|
createSequencer,
|
|
20636
21182
|
createSingingVoices,
|
|
20637
21183
|
createSynth,
|
|
20638
21184
|
createVoiceRegistry,
|
|
20639
21185
|
decodeMml,
|
|
20640
21186
|
decomposeToMonophonic,
|
|
20641
|
-
drawGrid,
|
|
20642
|
-
drawHeader,
|
|
20643
|
-
drawKeyboard,
|
|
20644
|
-
drawNoteLyrics,
|
|
20645
|
-
drawNotes,
|
|
20646
|
-
drawSelectedNotes,
|
|
20647
|
-
drawSelectionRect,
|
|
20648
21187
|
encodeMml,
|
|
20649
21188
|
encodeWavPCM16,
|
|
20650
21189
|
exportMIDI,
|
|
@@ -20652,36 +21191,34 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20652
21191
|
extractMidiDrumPattern,
|
|
20653
21192
|
extractMidiPlacements,
|
|
20654
21193
|
extractMidiPlacementsByTrack,
|
|
21194
|
+
fifthToStep,
|
|
21195
|
+
fifthToUnits,
|
|
20655
21196
|
formatMmlMeta,
|
|
20656
21197
|
freqFromPitch,
|
|
20657
21198
|
generateRandomPattern,
|
|
20658
|
-
getDrawOffset,
|
|
20659
21199
|
getDrumPatternKeys,
|
|
20660
|
-
getGridCanvas,
|
|
20661
|
-
getGridContext,
|
|
20662
|
-
getGridPosition,
|
|
20663
|
-
getHeaderCanvas,
|
|
20664
21200
|
getMidiBPM,
|
|
20665
|
-
getRenderConfig,
|
|
20666
|
-
getXY,
|
|
20667
21201
|
icon,
|
|
20668
|
-
init,
|
|
20669
21202
|
injectStyles,
|
|
20670
21203
|
isChordHeavyTrack,
|
|
21204
|
+
isNaturalLetter,
|
|
20671
21205
|
isPlausibleMidiTranscription,
|
|
20672
21206
|
isValidHttpUrl,
|
|
21207
|
+
keyCountFor,
|
|
20673
21208
|
koeUrl,
|
|
21209
|
+
midiToUnits,
|
|
20674
21210
|
mountChordPlayer,
|
|
20675
21211
|
mountDAW,
|
|
20676
21212
|
mountMmlPlayer,
|
|
21213
|
+
naturalStep,
|
|
20677
21214
|
normalizeDrumPatterns,
|
|
20678
21215
|
normalizeLyrics,
|
|
20679
|
-
onClick,
|
|
20680
21216
|
panToStereo,
|
|
20681
21217
|
parseCustomVocals,
|
|
20682
21218
|
parseLyrics,
|
|
20683
21219
|
parseMML,
|
|
20684
21220
|
parseMmlMeta,
|
|
21221
|
+
pitchV1ToUnits,
|
|
20685
21222
|
playChords,
|
|
20686
21223
|
playMML,
|
|
20687
21224
|
playNote,
|
|
@@ -20689,13 +21226,18 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20689
21226
|
playSingingMML,
|
|
20690
21227
|
resolveDrumPattern,
|
|
20691
21228
|
resolveLoopPoint,
|
|
20692
|
-
setBackgroundActive,
|
|
20693
|
-
setDrawOffset,
|
|
20694
21229
|
shiftNotes,
|
|
20695
21230
|
showLoadingOverlay,
|
|
21231
|
+
spellingToUnits,
|
|
20696
21232
|
stripCustomVocals,
|
|
20697
21233
|
stripLyrics,
|
|
20698
21234
|
stripMmlMeta,
|
|
20699
21235
|
transposeNotes,
|
|
21236
|
+
unitsPerRow,
|
|
21237
|
+
unitsPerStep,
|
|
21238
|
+
unitsToHz,
|
|
21239
|
+
unitsToMidi,
|
|
21240
|
+
unitsToMidiDetune,
|
|
21241
|
+
unitsToPitchV1,
|
|
20700
21242
|
vocalVolumeToGain
|
|
20701
21243
|
});
|