@onjmin/dtm 1.0.8 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/dist/index.d.mts +303 -107
- package/dist/index.d.ts +303 -107
- package/dist/index.js +1301 -738
- package/dist/index.mjs +1269 -720
- package/dist/voice-worker.js +3 -3
- package/package.json +2 -2
package/dist/index.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,30 +35,41 @@ __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,
|
|
65
|
+
addUnits: () => addUnits,
|
|
52
66
|
analyzeMidiTracks: () => analyzeMidiTracks,
|
|
53
67
|
applyHarmonicFilter: () => applyHarmonicFilter,
|
|
54
68
|
applyMonophonic: () => applyMonophonic,
|
|
55
69
|
buildChordPlacements: () => buildChordPlacements,
|
|
56
70
|
buildDrumPatternJson: () => buildDrumPatternJson,
|
|
57
71
|
buildNameToKeyMapping: () => buildNameToKeyMapping,
|
|
72
|
+
chromaticStep: () => chromaticStep,
|
|
58
73
|
collectPitchTokens: () => collectPitchTokens,
|
|
59
74
|
concatFloat32: () => concatFloat32,
|
|
60
75
|
createAudioContext: () => createAudioContext,
|
|
@@ -63,19 +78,13 @@ __export(index_exports, {
|
|
|
63
78
|
createKoeVoice: () => createKoeVoice,
|
|
64
79
|
createLyricsConductor: () => createLyricsConductor,
|
|
65
80
|
createPianoRoll: () => createPianoRoll,
|
|
81
|
+
createRenderer: () => createRenderer,
|
|
66
82
|
createSequencer: () => createSequencer,
|
|
67
83
|
createSingingVoices: () => createSingingVoices,
|
|
68
84
|
createSynth: () => createSynth,
|
|
69
85
|
createVoiceRegistry: () => createVoiceRegistry,
|
|
70
86
|
decodeMml: () => decodeMml,
|
|
71
87
|
decomposeToMonophonic: () => decomposeToMonophonic,
|
|
72
|
-
drawGrid: () => drawGrid,
|
|
73
|
-
drawHeader: () => drawHeader,
|
|
74
|
-
drawKeyboard: () => drawKeyboard,
|
|
75
|
-
drawNoteLyrics: () => drawNoteLyrics,
|
|
76
|
-
drawNotes: () => drawNotes,
|
|
77
|
-
drawSelectedNotes: () => drawSelectedNotes,
|
|
78
|
-
drawSelectionRect: () => drawSelectionRect,
|
|
79
88
|
encodeMml: () => encodeMml,
|
|
80
89
|
encodeWavPCM16: () => encodeWavPCM16,
|
|
81
90
|
exportMIDI: () => exportMIDI,
|
|
@@ -83,36 +92,35 @@ __export(index_exports, {
|
|
|
83
92
|
extractMidiDrumPattern: () => extractMidiDrumPattern,
|
|
84
93
|
extractMidiPlacements: () => extractMidiPlacements,
|
|
85
94
|
extractMidiPlacementsByTrack: () => extractMidiPlacementsByTrack,
|
|
95
|
+
fifthToStep: () => fifthToStep,
|
|
96
|
+
fifthToUnits: () => fifthToUnits,
|
|
86
97
|
formatMmlMeta: () => formatMmlMeta,
|
|
87
98
|
freqFromPitch: () => freqFromPitch,
|
|
88
99
|
generateRandomPattern: () => generateRandomPattern,
|
|
89
|
-
getDrawOffset: () => getDrawOffset,
|
|
90
100
|
getDrumPatternKeys: () => getDrumPatternKeys,
|
|
91
|
-
getGridCanvas: () => getGridCanvas,
|
|
92
|
-
getGridContext: () => getGridContext,
|
|
93
|
-
getGridPosition: () => getGridPosition,
|
|
94
|
-
getHeaderCanvas: () => getHeaderCanvas,
|
|
95
101
|
getMidiBPM: () => getMidiBPM,
|
|
96
|
-
getRenderConfig: () => getRenderConfig,
|
|
97
|
-
getXY: () => getXY,
|
|
98
102
|
icon: () => icon,
|
|
99
|
-
init: () => init,
|
|
100
103
|
injectStyles: () => injectStyles,
|
|
101
104
|
isChordHeavyTrack: () => isChordHeavyTrack,
|
|
105
|
+
isNaturalLetter: () => isNaturalLetter,
|
|
102
106
|
isPlausibleMidiTranscription: () => isPlausibleMidiTranscription,
|
|
103
107
|
isValidHttpUrl: () => isValidHttpUrl,
|
|
108
|
+
keyCountFor: () => keyCountFor,
|
|
104
109
|
koeUrl: () => koeUrl,
|
|
110
|
+
midiNote: () => midiNote,
|
|
111
|
+
midiToUnits: () => midiToUnits,
|
|
105
112
|
mountChordPlayer: () => mountChordPlayer,
|
|
106
113
|
mountDAW: () => mountDAW,
|
|
107
114
|
mountMmlPlayer: () => mountMmlPlayer,
|
|
115
|
+
naturalStep: () => naturalStep,
|
|
108
116
|
normalizeDrumPatterns: () => normalizeDrumPatterns,
|
|
109
117
|
normalizeLyrics: () => normalizeLyrics,
|
|
110
|
-
onClick: () => onClick,
|
|
111
118
|
panToStereo: () => panToStereo,
|
|
112
119
|
parseCustomVocals: () => parseCustomVocals,
|
|
113
120
|
parseLyrics: () => parseLyrics,
|
|
114
121
|
parseMML: () => parseMML,
|
|
115
122
|
parseMmlMeta: () => parseMmlMeta,
|
|
123
|
+
pitchV1ToUnits: () => pitchV1ToUnits,
|
|
116
124
|
playChords: () => playChords,
|
|
117
125
|
playMML: () => playMML,
|
|
118
126
|
playNote: () => playNote,
|
|
@@ -120,14 +128,20 @@ __export(index_exports, {
|
|
|
120
128
|
playSingingMML: () => playSingingMML,
|
|
121
129
|
resolveDrumPattern: () => resolveDrumPattern,
|
|
122
130
|
resolveLoopPoint: () => resolveLoopPoint,
|
|
123
|
-
setBackgroundActive: () => setBackgroundActive,
|
|
124
|
-
setDrawOffset: () => setDrawOffset,
|
|
125
131
|
shiftNotes: () => shiftNotes,
|
|
126
132
|
showLoadingOverlay: () => showLoadingOverlay,
|
|
133
|
+
spellingToUnits: () => spellingToUnits,
|
|
127
134
|
stripCustomVocals: () => stripCustomVocals,
|
|
128
135
|
stripLyrics: () => stripLyrics,
|
|
129
136
|
stripMmlMeta: () => stripMmlMeta,
|
|
130
137
|
transposeNotes: () => transposeNotes,
|
|
138
|
+
units: () => units,
|
|
139
|
+
unitsPerRow: () => unitsPerRow,
|
|
140
|
+
unitsPerStep: () => unitsPerStep,
|
|
141
|
+
unitsToHz: () => unitsToHz,
|
|
142
|
+
unitsToMidi: () => unitsToMidi,
|
|
143
|
+
unitsToMidiDetune: () => unitsToMidiDetune,
|
|
144
|
+
unitsToPitchV1: () => unitsToPitchV1,
|
|
131
145
|
vocalVolumeToGain: () => vocalVolumeToGain
|
|
132
146
|
});
|
|
133
147
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -282,7 +296,7 @@ async function buildNameToKeyMapping() {
|
|
|
282
296
|
}
|
|
283
297
|
var GM_INSTRUMENT_NAMES = FONT_NAME_SURIKOV.trim().split("\n").map((line) => line.slice(line.indexOf(" ") + 1));
|
|
284
298
|
|
|
285
|
-
// node_modules/.pnpm/@onjmin+chord-parser@1.
|
|
299
|
+
// node_modules/.pnpm/@onjmin+chord-parser@1.1.1/node_modules/@onjmin/chord-parser/dist/index.mjs
|
|
286
300
|
var SHARP_NAMES = [
|
|
287
301
|
"C",
|
|
288
302
|
"C#",
|
|
@@ -313,6 +327,10 @@ var FLAT_NAMES = [
|
|
|
313
327
|
];
|
|
314
328
|
var toPitchClass = (n) => (n % 12 + 12) % 12;
|
|
315
329
|
var noteName = (pc, flat = false) => (flat ? FLAT_NAMES : SHARP_NAMES)[toPitchClass(pc)];
|
|
330
|
+
var LETTER_FIFTH = [0, 2, 4, -1, 1, 3, 5];
|
|
331
|
+
var DEGREE_FIFTH = [0, 2, 4, -1, 1, 3, 5];
|
|
332
|
+
var deg2fifth = (deg) => DEGREE_FIFTH[(deg - 1) % 7];
|
|
333
|
+
var HALF_FIFTH = 7;
|
|
316
334
|
var SyntaxErrorWithPos = class extends Error {
|
|
317
335
|
constructor(input, msg) {
|
|
318
336
|
super(
|
|
@@ -359,6 +377,16 @@ var Input = class _Input {
|
|
|
359
377
|
};
|
|
360
378
|
var Output = class {
|
|
361
379
|
pitch = null;
|
|
380
|
+
/** ルート音の五度圏インデックス。`pitch` と対で持つ。 */
|
|
381
|
+
rootFifth = 0;
|
|
382
|
+
/**
|
|
383
|
+
* 構成音の五度圏オフセット。キーは「ルートからの相対半音 mod 12」。
|
|
384
|
+
*
|
|
385
|
+
* 五度圏インデックスはオクターブ不変なので、分数コードのオクターブシフト
|
|
386
|
+
* (`value` の配列を ±12 する処理)の影響を受けない。そのため構成音の集合とは
|
|
387
|
+
* 別にこの対応表を持っておけば、配列操作側に手を入れずに綴りを保てる。
|
|
388
|
+
*/
|
|
389
|
+
relFifths = /* @__PURE__ */ new Map();
|
|
362
390
|
chord = null;
|
|
363
391
|
isChord = false;
|
|
364
392
|
pending = null;
|
|
@@ -442,9 +470,17 @@ var parseFormula = (input, output = new Output(), nest = 0) => {
|
|
|
442
470
|
case DIVIDE: {
|
|
443
471
|
const o = parseFormula(input, new Output(), nest);
|
|
444
472
|
const v = [...output.value];
|
|
473
|
+
const rebase = (semi, fifth) => noteFifth(
|
|
474
|
+
output,
|
|
475
|
+
semi - output.pitch,
|
|
476
|
+
fifth - output.rootFifth
|
|
477
|
+
);
|
|
445
478
|
if (o.isChord) {
|
|
479
|
+
for (const [rel, f] of o.relFifths)
|
|
480
|
+
rebase(o.pitch + rel, o.rootFifth + f);
|
|
446
481
|
output.value = [...o.value].concat(v);
|
|
447
482
|
} else {
|
|
483
|
+
rebase(o.pitch, o.rootFifth);
|
|
448
484
|
const a = v.sort((x2, y) => x2 - y);
|
|
449
485
|
const pitch = (o.pitch + 3) % 12 - 3;
|
|
450
486
|
if (a[0] < pitch) {
|
|
@@ -491,63 +527,91 @@ var parsePitch = (input, output) => {
|
|
|
491
527
|
const pitch = pitchMatcher.parse(input);
|
|
492
528
|
if (pitch === null) err(input, "Not found pitch");
|
|
493
529
|
output.pitch = pitch;
|
|
530
|
+
output.rootFifth = LETTER_FIFTH[idx2pitch.slice(0, 7).indexOf(pitch)];
|
|
494
531
|
for (let i2 = 0; i2 < 2; i2++) {
|
|
495
532
|
const half = parseHalf(input, true);
|
|
496
533
|
if (half === null) break;
|
|
497
534
|
output.pitch += half;
|
|
535
|
+
output.rootFifth += half * HALF_FIFTH;
|
|
498
536
|
}
|
|
499
537
|
return parseBase(input, output);
|
|
500
538
|
};
|
|
539
|
+
var noteFifth = (out, semi, fifth) => {
|
|
540
|
+
out.relFifths.set((semi % 12 + 12) % 12, fifth);
|
|
541
|
+
};
|
|
501
542
|
var MAJOR = [0, 4, 7];
|
|
502
543
|
var DIM = [0, 3, 6];
|
|
544
|
+
var BASE_FIFTHS = /* @__PURE__ */ new Map();
|
|
545
|
+
var MINOR = [0, 3, 7];
|
|
546
|
+
var AUG = [0, 4, 8];
|
|
547
|
+
var HALF_DIM = [0, 3, 6, 10];
|
|
548
|
+
BASE_FIFTHS.set(MAJOR, [0, 4, 1]);
|
|
549
|
+
BASE_FIFTHS.set(MINOR, [0, -3, 1]);
|
|
550
|
+
BASE_FIFTHS.set(DIM, [0, -3, -6]);
|
|
551
|
+
BASE_FIFTHS.set(AUG, [0, 4, 8]);
|
|
552
|
+
BASE_FIFTHS.set(HALF_DIM, [0, -3, -6, -2]);
|
|
503
553
|
var baseMatcher = new Matcher();
|
|
504
|
-
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"],
|
|
554
|
+
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"], MINOR);
|
|
505
555
|
baseMatcher.set(["dim", "\u3007"], DIM);
|
|
506
|
-
baseMatcher.set("+",
|
|
507
|
-
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"],
|
|
556
|
+
baseMatcher.set("+", AUG);
|
|
557
|
+
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"], HALF_DIM);
|
|
508
558
|
var parseBase = (input, output) => {
|
|
509
559
|
const isMajMarker = /^maj/i.test(input.str.slice(input.idx));
|
|
510
560
|
const res = isMajMarker ? null : baseMatcher.parse(input);
|
|
511
561
|
if (res !== null) output.isChord = true;
|
|
512
|
-
|
|
562
|
+
const base = res || MAJOR;
|
|
563
|
+
output.chord = new Set(base);
|
|
564
|
+
const baseFifths = BASE_FIFTHS.get(base);
|
|
565
|
+
if (baseFifths)
|
|
566
|
+
for (const [i2, semi] of base.entries())
|
|
567
|
+
noteFifth(output, semi, baseFifths[i2]);
|
|
513
568
|
if (res === DIM) {
|
|
514
569
|
const { num } = input;
|
|
515
570
|
const chord = output.chord;
|
|
516
|
-
if (num !== null)
|
|
571
|
+
if (num !== null) {
|
|
572
|
+
chord.add(deg2pitch(num) - 2);
|
|
573
|
+
noteFifth(output, deg2pitch(num) - 2, deg2fifth(num) - 2 * HALF_FIFTH);
|
|
574
|
+
}
|
|
517
575
|
}
|
|
518
576
|
output.nest = input.nest;
|
|
519
577
|
return parseTerm(input, output);
|
|
520
578
|
};
|
|
521
|
-
var
|
|
522
|
-
chord.add(deg2pitch(
|
|
579
|
+
var put = (chord, out, deg, half = 0) => {
|
|
580
|
+
chord.add(deg2pitch(deg) + half);
|
|
581
|
+
noteFifth(out, deg2pitch(deg) + half, deg2fifth(deg) + half * HALF_FIFTH);
|
|
582
|
+
};
|
|
583
|
+
var add = (chord, n, half, out) => {
|
|
584
|
+
put(chord, out, n, half);
|
|
523
585
|
};
|
|
524
|
-
var aug = (chord) => {
|
|
586
|
+
var aug = (chord, out) => {
|
|
525
587
|
chord.delete(deg2pitch(5));
|
|
526
|
-
chord
|
|
588
|
+
put(chord, out, 5, 1);
|
|
527
589
|
};
|
|
528
|
-
var _7th = (chord, n, _half2, isFlat = false) => {
|
|
590
|
+
var _7th = (chord, n, _half2, out, isFlat = false) => {
|
|
529
591
|
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 >=
|
|
592
|
+
else if (n === 6) put(chord, out, 6);
|
|
593
|
+
else if (n === 69) {
|
|
594
|
+
put(chord, out, 6);
|
|
595
|
+
put(chord, out, 9);
|
|
596
|
+
} else {
|
|
597
|
+
if (n >= 7) put(chord, out, 7, isFlat ? -1 : 0);
|
|
598
|
+
if (n >= 9) put(chord, out, 9);
|
|
599
|
+
if (n >= 11) put(chord, out, 11);
|
|
600
|
+
if (n >= 13) put(chord, out, 13);
|
|
537
601
|
}
|
|
538
602
|
};
|
|
539
|
-
var _half = (chord, n, half) => {
|
|
603
|
+
var _half = (chord, n, half, out) => {
|
|
540
604
|
chord.delete(deg2pitch(n));
|
|
541
|
-
chord
|
|
605
|
+
put(chord, out, n, half);
|
|
542
606
|
};
|
|
543
607
|
var funcMatcher = new Matcher();
|
|
544
608
|
funcMatcher.set("add", add);
|
|
545
609
|
funcMatcher.set(["omit", "no"], (chord, n, half) => {
|
|
546
610
|
chord.delete(deg2pitch(n) + half);
|
|
547
611
|
});
|
|
548
|
-
funcMatcher.set("sus", (chord, n, half) => {
|
|
612
|
+
funcMatcher.set("sus", (chord, n, half, out) => {
|
|
549
613
|
chord.delete(deg2pitch(3));
|
|
550
|
-
chord
|
|
614
|
+
put(chord, out, n, half);
|
|
551
615
|
});
|
|
552
616
|
funcMatcher.set(
|
|
553
617
|
["M", "maj", "Maj", "major", "Major", "\u25B3", "\u0394"],
|
|
@@ -563,17 +627,18 @@ var parseFunc = (input, output) => {
|
|
|
563
627
|
const half = parseHalf(input);
|
|
564
628
|
const { num } = input;
|
|
565
629
|
if (num === null) {
|
|
566
|
-
if (isAug) aug(chord);
|
|
630
|
+
if (isAug) aug(chord, output);
|
|
567
631
|
else err(input, "Not found number");
|
|
568
632
|
}
|
|
569
633
|
if (half === null) {
|
|
570
|
-
if (input.nest === output.nest)
|
|
571
|
-
|
|
634
|
+
if (input.nest === output.nest)
|
|
635
|
+
_7th(chord, num, 0, output, true);
|
|
636
|
+
else add(chord, num, 0, output);
|
|
572
637
|
} else {
|
|
573
|
-
_half(chord, num, half);
|
|
638
|
+
_half(chord, num, half, output);
|
|
574
639
|
}
|
|
575
640
|
} else if (func === aug) {
|
|
576
|
-
aug(chord);
|
|
641
|
+
aug(chord, output);
|
|
577
642
|
} else {
|
|
578
643
|
output.pending = func;
|
|
579
644
|
}
|
|
@@ -587,7 +652,8 @@ var parsePending = (input, output) => {
|
|
|
587
652
|
pending(
|
|
588
653
|
chord,
|
|
589
654
|
num,
|
|
590
|
-
half === null ? 0 : half
|
|
655
|
+
half === null ? 0 : half,
|
|
656
|
+
output
|
|
591
657
|
);
|
|
592
658
|
output.pending = null;
|
|
593
659
|
return parseTerm(input, output);
|
|
@@ -599,12 +665,29 @@ var parseChord = (symbol) => {
|
|
|
599
665
|
const pitchClasses = [...new Set(notes.map(toPitchClass))].sort(
|
|
600
666
|
(a, b) => a - b
|
|
601
667
|
);
|
|
668
|
+
const rootPitch = output.pitch;
|
|
669
|
+
const rootFifth = output.rootFifth;
|
|
670
|
+
const noteFifths = notes.map((n) => {
|
|
671
|
+
const rel = ((n - rootPitch) % 12 + 12) % 12;
|
|
672
|
+
const f = output.relFifths.get(rel);
|
|
673
|
+
if (f !== void 0) return rootFifth + f;
|
|
674
|
+
let best = 0;
|
|
675
|
+
for (let k = -6; k <= 6; k++) {
|
|
676
|
+
if ((k * 7 % 12 + 12) % 12 === rel) {
|
|
677
|
+
best = k;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return rootFifth + best;
|
|
682
|
+
});
|
|
602
683
|
return {
|
|
603
684
|
symbol,
|
|
604
|
-
root: toPitchClass(
|
|
685
|
+
root: toPitchClass(rootPitch),
|
|
605
686
|
notes,
|
|
606
687
|
pitchClasses,
|
|
607
|
-
intervals
|
|
688
|
+
intervals,
|
|
689
|
+
rootFifth,
|
|
690
|
+
noteFifths
|
|
608
691
|
};
|
|
609
692
|
};
|
|
610
693
|
var QUALITY_SOURCE = [
|
|
@@ -1633,10 +1716,77 @@ var createChannelStrip = (ctx, destination, options = {}) => {
|
|
|
1633
1716
|
};
|
|
1634
1717
|
};
|
|
1635
1718
|
|
|
1719
|
+
// src/tuning.ts
|
|
1720
|
+
var units = (n) => n;
|
|
1721
|
+
var midiNote = (n) => n;
|
|
1722
|
+
var addUnits = (a, delta) => a + delta;
|
|
1723
|
+
var UNITS_PER_OCTAVE = 372;
|
|
1724
|
+
var UNITS_PER_SEMITONE = 372 / 12;
|
|
1725
|
+
var UNITS_PER_EDO31_DEGREE = 372 / 31;
|
|
1726
|
+
var CENTS_PER_UNIT = 1200 / UNITS_PER_OCTAVE;
|
|
1727
|
+
var A4_UNITS = 69 * UNITS_PER_SEMITONE;
|
|
1728
|
+
var A4_HZ = 440;
|
|
1729
|
+
var DEFAULT_EDO = 12;
|
|
1730
|
+
var unitsPerStep = (edo) => UNITS_PER_OCTAVE / edo;
|
|
1731
|
+
var midiToUnits = (midi) => midi * UNITS_PER_SEMITONE;
|
|
1732
|
+
var unitsToMidi = (u) => u / UNITS_PER_SEMITONE;
|
|
1733
|
+
var unitsToHz = (u) => A4_HZ * 2 ** ((u - A4_UNITS) / UNITS_PER_OCTAVE);
|
|
1734
|
+
var unitsToMidiDetune = (u) => {
|
|
1735
|
+
const midi = Math.round(u / UNITS_PER_SEMITONE);
|
|
1736
|
+
return {
|
|
1737
|
+
midi,
|
|
1738
|
+
detuneCents: (u - midi * UNITS_PER_SEMITONE) * CENTS_PER_UNIT
|
|
1739
|
+
};
|
|
1740
|
+
};
|
|
1741
|
+
var NATURAL_STEPS = {
|
|
1742
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
1743
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
1744
|
+
};
|
|
1745
|
+
var chromaticStep = (edo) => edo === 31 ? 2 : 1;
|
|
1746
|
+
var MICRO_STEP = 1;
|
|
1747
|
+
var isNaturalLetter = (ch) => Object.hasOwn(NATURAL_STEPS[12], ch);
|
|
1748
|
+
var naturalStep = (ch, edo) => NATURAL_STEPS[edo][ch] ?? null;
|
|
1749
|
+
var spellingToUnits = (letter, octave, chromatic, micro, edo) => {
|
|
1750
|
+
const step = naturalStep(letter, edo);
|
|
1751
|
+
if (step === null) return null;
|
|
1752
|
+
const perStep = unitsPerStep(edo);
|
|
1753
|
+
const stepsFromC = step + chromatic * chromaticStep(edo) + micro * MICRO_STEP;
|
|
1754
|
+
return (octave + 1) * UNITS_PER_OCTAVE + stepsFromC * perStep;
|
|
1755
|
+
};
|
|
1756
|
+
var FIFTH_STEPS = { 12: 7, 31: 18 };
|
|
1757
|
+
var fifthToStep = (fifthIndex, edo) => {
|
|
1758
|
+
const n = fifthIndex * FIFTH_STEPS[edo];
|
|
1759
|
+
return (n % edo + edo) % edo;
|
|
1760
|
+
};
|
|
1761
|
+
var fifthToUnits = (fifthIndex, edo) => fifthToStep(fifthIndex, edo) * unitsPerStep(edo);
|
|
1762
|
+
var PITCH_ENCODING_VERSION = 2;
|
|
1763
|
+
var pitchV1ToUnits = (pitch) => pitch * UNITS_PER_SEMITONE;
|
|
1764
|
+
var unitsToPitchV1 = (u) => Math.round(u / UNITS_PER_SEMITONE);
|
|
1765
|
+
|
|
1636
1766
|
// src/chords.ts
|
|
1637
1767
|
var C3 = 48;
|
|
1768
|
+
var MEANTONE_STEP_BY_SEMITONE = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
1769
|
+
var chordToneToUnits = (relSemitone, relFifth, rootSemitone, rootFifth, edo) => {
|
|
1770
|
+
const perStep = UNITS_PER_OCTAVE / edo;
|
|
1771
|
+
const rootOct = Math.floor(rootSemitone / 12);
|
|
1772
|
+
const rootWithin = (rootSemitone % 12 + 12) % 12;
|
|
1773
|
+
const rootStep = edo === 31 ? (fifthToStep(rootFifth, 31) + MEANTONE_STEP_BY_SEMITONE[rootWithin] - fifthToStep(rootFifth, 31) + 31) % 31 : rootWithin;
|
|
1774
|
+
const relWithin12 = (relSemitone % 12 + 12) % 12;
|
|
1775
|
+
const relOct = Math.round((relSemitone - relWithin12) / 12);
|
|
1776
|
+
const relStep = (fifthToStep(relFifth, edo) - fifthToStep(rootFifth, edo) + edo) % edo;
|
|
1777
|
+
return units(
|
|
1778
|
+
(rootOct + relOct) * UNITS_PER_OCTAVE + (rootStep + relStep) * perStep
|
|
1779
|
+
);
|
|
1780
|
+
};
|
|
1638
1781
|
var buildChordPlacements = (options) => {
|
|
1639
|
-
const {
|
|
1782
|
+
const {
|
|
1783
|
+
chordStr,
|
|
1784
|
+
patternType,
|
|
1785
|
+
rootShift,
|
|
1786
|
+
bpm,
|
|
1787
|
+
stepsPerBar,
|
|
1788
|
+
edo = 12
|
|
1789
|
+
} = options;
|
|
1640
1790
|
const placements = [];
|
|
1641
1791
|
if (!chordStr.trim()) return placements;
|
|
1642
1792
|
const offset = rootShift;
|
|
@@ -1665,8 +1815,20 @@ var buildChordPlacements = (options) => {
|
|
|
1665
1815
|
for (const group of Object.values(chordGroups)) {
|
|
1666
1816
|
for (const chord of group) {
|
|
1667
1817
|
let notes;
|
|
1818
|
+
let toUnits;
|
|
1668
1819
|
try {
|
|
1669
|
-
|
|
1820
|
+
const parsed = parseChord(`${chord.key}${chord.chord}`);
|
|
1821
|
+
notes = [...parsed.notes];
|
|
1822
|
+
const fifthOf = new Map(
|
|
1823
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1824
|
+
);
|
|
1825
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1826
|
+
rel,
|
|
1827
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1828
|
+
C3 + offset,
|
|
1829
|
+
parsed.rootFifth,
|
|
1830
|
+
edo === 31 ? 31 : 12
|
|
1831
|
+
);
|
|
1670
1832
|
} catch {
|
|
1671
1833
|
continue;
|
|
1672
1834
|
}
|
|
@@ -1675,7 +1837,7 @@ var buildChordPlacements = (options) => {
|
|
|
1675
1837
|
for (const noteOffset of notes) {
|
|
1676
1838
|
placements.push({
|
|
1677
1839
|
startStep: chord.whenStep,
|
|
1678
|
-
|
|
1840
|
+
pitchUnits: toUnits(noteOffset),
|
|
1679
1841
|
durationSteps: noteLength,
|
|
1680
1842
|
velocity: 100
|
|
1681
1843
|
});
|
|
@@ -1685,7 +1847,7 @@ var buildChordPlacements = (options) => {
|
|
|
1685
1847
|
notes.forEach((noteOffset, i2) => {
|
|
1686
1848
|
placements.push({
|
|
1687
1849
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1688
|
-
|
|
1850
|
+
pitchUnits: toUnits(noteOffset),
|
|
1689
1851
|
durationSteps: noteLength - i2 * arpInterval,
|
|
1690
1852
|
velocity: 100
|
|
1691
1853
|
});
|
|
@@ -1695,7 +1857,7 @@ var buildChordPlacements = (options) => {
|
|
|
1695
1857
|
notes.forEach((noteOffset, i2) => {
|
|
1696
1858
|
placements.push({
|
|
1697
1859
|
startStep: chord.whenStep + i2 * arpInterval,
|
|
1698
|
-
|
|
1860
|
+
pitchUnits: toUnits(noteOffset),
|
|
1699
1861
|
durationSteps: Math.max(12, noteLength - i2 * arpInterval),
|
|
1700
1862
|
velocity: 100
|
|
1701
1863
|
});
|
|
@@ -1709,7 +1871,7 @@ var buildChordPlacements = (options) => {
|
|
|
1709
1871
|
for (const noteOffset of notes) {
|
|
1710
1872
|
placements.push({
|
|
1711
1873
|
startStep: syncopatedStep,
|
|
1712
|
-
|
|
1874
|
+
pitchUnits: toUnits(noteOffset),
|
|
1713
1875
|
durationSteps: Math.min(halfBeat, 12),
|
|
1714
1876
|
velocity: 100
|
|
1715
1877
|
});
|
|
@@ -1728,7 +1890,7 @@ var buildChordPlacements = (options) => {
|
|
|
1728
1890
|
for (const noteOffset of notes) {
|
|
1729
1891
|
placements.push({
|
|
1730
1892
|
startStep: noteStart,
|
|
1731
|
-
|
|
1893
|
+
pitchUnits: toUnits(noteOffset),
|
|
1732
1894
|
durationSteps: yatsumeLengthSteps,
|
|
1733
1895
|
velocity: 100
|
|
1734
1896
|
});
|
|
@@ -1740,7 +1902,7 @@ var buildChordPlacements = (options) => {
|
|
|
1740
1902
|
const stepOffset = i2 * Math.floor(stepsPerBar / 4);
|
|
1741
1903
|
placements.push({
|
|
1742
1904
|
startStep: chord.whenStep + stepOffset,
|
|
1743
|
-
|
|
1905
|
+
pitchUnits: toUnits(noteOffset),
|
|
1744
1906
|
durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
|
|
1745
1907
|
velocity: 100
|
|
1746
1908
|
});
|
|
@@ -1752,8 +1914,20 @@ var buildChordPlacements = (options) => {
|
|
|
1752
1914
|
const chordNames = chordStr.split(/[\s,]+/).filter((c) => c);
|
|
1753
1915
|
chordNames.forEach((chordName, barIndex) => {
|
|
1754
1916
|
let notes;
|
|
1917
|
+
let toUnits;
|
|
1755
1918
|
try {
|
|
1756
|
-
|
|
1919
|
+
const parsed = parseChord(chordName);
|
|
1920
|
+
notes = [...parsed.notes];
|
|
1921
|
+
const fifthOf = new Map(
|
|
1922
|
+
parsed.notes.map((n, i2) => [n, parsed.noteFifths[i2]])
|
|
1923
|
+
);
|
|
1924
|
+
toUnits = (rel) => chordToneToUnits(
|
|
1925
|
+
rel,
|
|
1926
|
+
fifthOf.get(rel) ?? parsed.rootFifth,
|
|
1927
|
+
C3 + offset,
|
|
1928
|
+
parsed.rootFifth,
|
|
1929
|
+
edo === 31 ? 31 : 12
|
|
1930
|
+
);
|
|
1757
1931
|
} catch {
|
|
1758
1932
|
return;
|
|
1759
1933
|
}
|
|
@@ -1763,7 +1937,7 @@ var buildChordPlacements = (options) => {
|
|
|
1763
1937
|
const stepOffset = i2 * 3;
|
|
1764
1938
|
placements.push({
|
|
1765
1939
|
startStep: startStep + stepOffset,
|
|
1766
|
-
|
|
1940
|
+
pitchUnits: toUnits(noteOffset),
|
|
1767
1941
|
durationSteps: chordLength - stepOffset,
|
|
1768
1942
|
velocity: 100
|
|
1769
1943
|
});
|
|
@@ -2178,7 +2352,7 @@ var Worldline = class _Worldline {
|
|
|
2178
2352
|
const WL = this.wasm;
|
|
2179
2353
|
const FS = WORLDLINE_SAMPLE_RATE;
|
|
2180
2354
|
const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
|
|
2181
|
-
const
|
|
2355
|
+
const midiNote2 = Math.round(69 + 12 * Math.log2(basePitch / 440));
|
|
2182
2356
|
const posMs = 0;
|
|
2183
2357
|
const reqLen = preMs + durationMs;
|
|
2184
2358
|
const cutMs = WL_FRAME_MS * 2;
|
|
@@ -2202,7 +2376,7 @@ var Worldline = class _Worldline {
|
|
|
2202
2376
|
sv(8, samplePtr, "*");
|
|
2203
2377
|
sv(12, 0, "i32");
|
|
2204
2378
|
sv(16, 0, "*");
|
|
2205
|
-
sv(20,
|
|
2379
|
+
sv(20, midiNote2, "i32");
|
|
2206
2380
|
sv(24, 100, "double");
|
|
2207
2381
|
sv(32, 0, "double");
|
|
2208
2382
|
sv(40, reqLen, "double");
|
|
@@ -2295,6 +2469,11 @@ var DEFAULT_VELOCITY = 100;
|
|
|
2295
2469
|
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
2296
2470
|
var DEFAULT_STEPS_PER_BAR = 192;
|
|
2297
2471
|
var MML_END_MARKER = "#end;";
|
|
2472
|
+
var PITCH_RANGE_START = 0;
|
|
2473
|
+
var PITCH_RANGE_END = 3937;
|
|
2474
|
+
var unitsPerRow = (edo) => 372 / edo;
|
|
2475
|
+
var keyCountFor = (edo) => Math.floor((PITCH_RANGE_END - PITCH_RANGE_START) / unitsPerRow(edo)) + 1;
|
|
2476
|
+
var KEY_COUNT = keyCountFor(12);
|
|
2298
2477
|
|
|
2299
2478
|
// src/vibrato.ts
|
|
2300
2479
|
var VIBRATO_MIN_SEC = 0.35;
|
|
@@ -2694,7 +2873,8 @@ var FORMANTS = {
|
|
|
2694
2873
|
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
2695
2874
|
N: [250, 1e3]
|
|
2696
2875
|
};
|
|
2697
|
-
var
|
|
2876
|
+
var unitsToFreq = (units2) => 440 * 2 ** ((units2 - 2139) / 372);
|
|
2877
|
+
var unitsToMidiFloat = (units2) => units2 / 31;
|
|
2698
2878
|
var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
2699
2879
|
const active = /* @__PURE__ */ new Set();
|
|
2700
2880
|
const voice = (syllable, e) => {
|
|
@@ -2728,7 +2908,7 @@ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
|
|
|
2728
2908
|
}
|
|
2729
2909
|
const osc = ctx.createOscillator();
|
|
2730
2910
|
osc.type = "sawtooth";
|
|
2731
|
-
osc.frequency.value =
|
|
2911
|
+
osc.frequency.value = unitsToFreq(e.pitchUnits);
|
|
2732
2912
|
const makeFormant = (freq, q2, gainScale) => {
|
|
2733
2913
|
const filter = ctx.createBiquadFilter();
|
|
2734
2914
|
filter.type = "bandpass";
|
|
@@ -2980,7 +3160,7 @@ var createLocalBackend = async (options) => {
|
|
|
2980
3160
|
if (!consonantPcm || !vowelPcm) return null;
|
|
2981
3161
|
const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
|
|
2982
3162
|
if (!spliced) return null;
|
|
2983
|
-
const targetHz =
|
|
3163
|
+
const targetHz = unitsToFreq(pitch);
|
|
2984
3164
|
const audio = worldline.renderNote({
|
|
2985
3165
|
pcm: spliced.pcm,
|
|
2986
3166
|
pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
|
|
@@ -3009,7 +3189,7 @@ var createLocalBackend = async (options) => {
|
|
|
3009
3189
|
if (!pcm || pcm.length === 0) return null;
|
|
3010
3190
|
const entry = bank.manifest.phonemes[alias];
|
|
3011
3191
|
const lead = leadInFromEntry(entry);
|
|
3012
|
-
const targetHz =
|
|
3192
|
+
const targetHz = unitsToFreq(pitch);
|
|
3013
3193
|
if (worldline) {
|
|
3014
3194
|
const audio = worldline.renderNote({
|
|
3015
3195
|
pcm,
|
|
@@ -3216,7 +3396,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3216
3396
|
backend.pitchTokens,
|
|
3217
3397
|
syllable,
|
|
3218
3398
|
prevVowel,
|
|
3219
|
-
e.
|
|
3399
|
+
unitsToMidiFloat(e.pitchUnits)
|
|
3220
3400
|
);
|
|
3221
3401
|
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
3222
3402
|
if (!alias) return;
|
|
@@ -3224,7 +3404,7 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3224
3404
|
const peak = Math.max(1e-4, e.volume);
|
|
3225
3405
|
const pan = e.pan ?? 0;
|
|
3226
3406
|
const durationMs = Math.max(60, e.duration * 1e3);
|
|
3227
|
-
void renderInto(alias, e.
|
|
3407
|
+
void renderInto(alias, e.pitchUnits, durationMs).then((r) => {
|
|
3228
3408
|
if (r)
|
|
3229
3409
|
schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
|
|
3230
3410
|
});
|
|
@@ -3236,7 +3416,10 @@ var createKoeVoice = async (ctx, destination, options) => {
|
|
|
3236
3416
|
backend.pitchTokens,
|
|
3237
3417
|
syllable,
|
|
3238
3418
|
prevVowelArg,
|
|
3239
|
-
|
|
3419
|
+
// 多音階バンクのピッチトークン("_G4" 等)は録音の音名なので、
|
|
3420
|
+
// 最寄り選択はMIDIノート番号の尺度で行う。units のまま渡すと常に
|
|
3421
|
+
// 最高音のトークンが選ばれてしまう。
|
|
3422
|
+
unitsToMidiFloat(pitch)
|
|
3240
3423
|
);
|
|
3241
3424
|
if (!alias) return null;
|
|
3242
3425
|
const dMs = Math.max(60, durationMs);
|
|
@@ -3268,13 +3451,14 @@ var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
|
3268
3451
|
var STREAM_POLL_MS = 100;
|
|
3269
3452
|
var OCTAVE_UNISON_PEAK_SCALE = 0.6;
|
|
3270
3453
|
var octaveUnisonOffsets = (mode) => {
|
|
3454
|
+
const OCT = 372;
|
|
3271
3455
|
switch (mode) {
|
|
3272
3456
|
case "down":
|
|
3273
|
-
return [-
|
|
3457
|
+
return [-OCT];
|
|
3274
3458
|
case "up":
|
|
3275
|
-
return [
|
|
3459
|
+
return [OCT];
|
|
3276
3460
|
case "both":
|
|
3277
|
-
return [-
|
|
3461
|
+
return [-OCT, OCT];
|
|
3278
3462
|
default:
|
|
3279
3463
|
return [];
|
|
3280
3464
|
}
|
|
@@ -3484,7 +3668,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3484
3668
|
const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
|
|
3485
3669
|
model(note.syllable, {
|
|
3486
3670
|
trackId: track.id ?? "",
|
|
3487
|
-
pitch,
|
|
3671
|
+
pitchUnits: pitch,
|
|
3488
3672
|
velocity: 100,
|
|
3489
3673
|
volume: effPeak,
|
|
3490
3674
|
when,
|
|
@@ -3499,7 +3683,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
3499
3683
|
};
|
|
3500
3684
|
dispatchNote(note.pitch, 1);
|
|
3501
3685
|
for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
|
|
3502
|
-
dispatchNote(note.pitch + offset, OCTAVE_UNISON_PEAK_SCALE);
|
|
3686
|
+
dispatchNote(units(note.pitch + offset), OCTAVE_UNISON_PEAK_SCALE);
|
|
3503
3687
|
}
|
|
3504
3688
|
if (!(model.renderToCache && model.scheduleCached)) {
|
|
3505
3689
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
@@ -3550,17 +3734,13 @@ var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
|
3550
3734
|
};
|
|
3551
3735
|
|
|
3552
3736
|
// 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
|
|
3737
|
+
var NATURAL_STEPS2 = {
|
|
3738
|
+
12: { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 },
|
|
3739
|
+
31: { c: 0, d: 5, e: 10, f: 13, g: 18, a: 23, b: 28 }
|
|
3561
3740
|
};
|
|
3741
|
+
var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
|
|
3562
3742
|
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;
|
|
3743
|
+
var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
|
|
3564
3744
|
var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
|
|
3565
3745
|
var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
|
|
3566
3746
|
var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
|
|
@@ -3610,6 +3790,9 @@ var parseMmlMeta = (mml) => {
|
|
|
3610
3790
|
if (m[2] === "simple" || m[2] === "advanced") {
|
|
3611
3791
|
meta.mode = m[2];
|
|
3612
3792
|
}
|
|
3793
|
+
} else if (key === "edo") {
|
|
3794
|
+
const e = Number.parseInt(m[2], 10);
|
|
3795
|
+
if (e === 12 || e === 31) meta.edo = e;
|
|
3613
3796
|
}
|
|
3614
3797
|
}
|
|
3615
3798
|
for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
|
|
@@ -3712,6 +3895,7 @@ var formatMmlMeta = (meta, space = "") => {
|
|
|
3712
3895
|
if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
|
|
3713
3896
|
parts.push(`#fadeout=${meta.fadeOut}`);
|
|
3714
3897
|
if (meta.mode) parts.push(`#mode=${meta.mode}`);
|
|
3898
|
+
if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
|
|
3715
3899
|
if (meta.trackInstruments) {
|
|
3716
3900
|
for (const [idx, name] of Object.entries(meta.trackInstruments)) {
|
|
3717
3901
|
if (name) parts.push(`#t${idx}inst=${name}`);
|
|
@@ -3786,6 +3970,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
3786
3970
|
const endMarkerBase = MML_END_MARKER.replace(/;+$/, "");
|
|
3787
3971
|
const endRegex = new RegExp(`(?<![cdafgCDAFG])${endMarkerBase}\\b;?`, "gi");
|
|
3788
3972
|
const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
|
|
3973
|
+
const edo = meta.edo === 31 ? 31 : 12;
|
|
3974
|
+
const naturals = NATURAL_STEPS2[edo];
|
|
3975
|
+
const unitsPerStep2 = 372 / edo;
|
|
3976
|
+
const chromaticStep2 = edo === 31 ? 2 : 1;
|
|
3789
3977
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
3790
3978
|
let trackIndex = 0;
|
|
3791
3979
|
let sourceTrackIndex = 0;
|
|
@@ -3833,6 +4021,19 @@ var parseMML = (mml, options = {}) => {
|
|
|
3833
4021
|
type
|
|
3834
4022
|
});
|
|
3835
4023
|
};
|
|
4024
|
+
const readAccidentals = () => {
|
|
4025
|
+
let delta = 0;
|
|
4026
|
+
while (j < body.length) {
|
|
4027
|
+
const a = body[j];
|
|
4028
|
+
if (a === "#") delta += chromaticStep2;
|
|
4029
|
+
else if (a === "-") delta -= chromaticStep2;
|
|
4030
|
+
else if (a === "+") delta += edo === 31 ? 1 : chromaticStep2;
|
|
4031
|
+
else if (a === "_") delta -= edo === 31 ? 1 : chromaticStep2;
|
|
4032
|
+
else break;
|
|
4033
|
+
j++;
|
|
4034
|
+
}
|
|
4035
|
+
return delta;
|
|
4036
|
+
};
|
|
3836
4037
|
const parseLength = () => {
|
|
3837
4038
|
let numStr = "";
|
|
3838
4039
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -3904,17 +4105,13 @@ var parseMML = (mml, options = {}) => {
|
|
|
3904
4105
|
const savedOctave = octave;
|
|
3905
4106
|
while (j < body.length && body[j] !== "]") {
|
|
3906
4107
|
const c = body[j];
|
|
3907
|
-
if (
|
|
3908
|
-
let
|
|
4108
|
+
if (isNatural(c)) {
|
|
4109
|
+
let pitchSteps = naturals[c];
|
|
3909
4110
|
j++;
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
pitch--;
|
|
3915
|
-
j++;
|
|
3916
|
-
}
|
|
3917
|
-
chordNotes.push((octave + 1) * 12 + pitch);
|
|
4111
|
+
pitchSteps += readAccidentals();
|
|
4112
|
+
chordNotes.push(
|
|
4113
|
+
units((octave + 1) * 372 + pitchSteps * unitsPerStep2)
|
|
4114
|
+
);
|
|
3918
4115
|
} else if (c === ">") {
|
|
3919
4116
|
octave = Math.min(8, octave + 1);
|
|
3920
4117
|
j++;
|
|
@@ -3940,7 +4137,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
3940
4137
|
placements.push({
|
|
3941
4138
|
trackIndex,
|
|
3942
4139
|
startStep: currentStep,
|
|
3943
|
-
|
|
4140
|
+
pitchUnits: p,
|
|
3944
4141
|
durationSteps: Math.max(1, steps),
|
|
3945
4142
|
velocity
|
|
3946
4143
|
});
|
|
@@ -3948,23 +4145,17 @@ var parseMML = (mml, options = {}) => {
|
|
|
3948
4145
|
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
3949
4146
|
currentStep += steps;
|
|
3950
4147
|
octave = savedOctave;
|
|
3951
|
-
} else if (
|
|
3952
|
-
let
|
|
4148
|
+
} else if (isNatural(ch)) {
|
|
4149
|
+
let pitchSteps = naturals[ch];
|
|
3953
4150
|
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;
|
|
4151
|
+
pitchSteps += readAccidentals();
|
|
4152
|
+
const midiPitch = units((octave + 1) * 372 + pitchSteps * unitsPerStep2);
|
|
3962
4153
|
const steps = parseLength();
|
|
3963
4154
|
recordContributor();
|
|
3964
4155
|
placements.push({
|
|
3965
4156
|
trackIndex,
|
|
3966
4157
|
startStep: currentStep,
|
|
3967
|
-
|
|
4158
|
+
pitchUnits: midiPitch,
|
|
3968
4159
|
durationSteps: Math.max(1, steps),
|
|
3969
4160
|
velocity
|
|
3970
4161
|
});
|
|
@@ -4096,7 +4287,7 @@ var createSequencer = (options) => {
|
|
|
4096
4287
|
maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
|
|
4097
4288
|
timeline.push({
|
|
4098
4289
|
trackId: track.id,
|
|
4099
|
-
pitch: note.
|
|
4290
|
+
pitch: note.pitchUnits,
|
|
4100
4291
|
volume: track.volume / 100,
|
|
4101
4292
|
velocity: note.velocity ?? DEFAULT_PLAYBACK_VELOCITY,
|
|
4102
4293
|
when,
|
|
@@ -4159,7 +4350,7 @@ var createSequencer = (options) => {
|
|
|
4159
4350
|
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
4160
4351
|
options.onPlayNote({
|
|
4161
4352
|
trackId: ev.trackId,
|
|
4162
|
-
|
|
4353
|
+
pitchUnits: ev.pitch,
|
|
4163
4354
|
velocity: ev.velocity,
|
|
4164
4355
|
volume: currentVolume * velocityVolume,
|
|
4165
4356
|
when: Math.max(0, _when),
|
|
@@ -6479,7 +6670,7 @@ var SONG_DRUM_PATTERNS = {
|
|
|
6479
6670
|
};
|
|
6480
6671
|
|
|
6481
6672
|
// src/synth.ts
|
|
6482
|
-
var freqFromPitch = (
|
|
6673
|
+
var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
|
|
6483
6674
|
var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
6484
6675
|
const wave = tone.wave ?? "square";
|
|
6485
6676
|
const attack = tone.attack ?? 0;
|
|
@@ -6495,7 +6686,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
|
|
|
6495
6686
|
const osc = ctx.createOscillator();
|
|
6496
6687
|
const gain = ctx.createGain();
|
|
6497
6688
|
osc.type = wave;
|
|
6498
|
-
osc.frequency.value = freqFromPitch(e.
|
|
6689
|
+
osc.frequency.value = freqFromPitch(e.pitchUnits);
|
|
6499
6690
|
const t0 = ctx.currentTime + e.when;
|
|
6500
6691
|
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
|
|
6501
6692
|
if (tone.decay) {
|
|
@@ -6600,7 +6791,7 @@ var playPlacements = (placements, options) => {
|
|
|
6600
6791
|
id: id++,
|
|
6601
6792
|
startStep: p.startStep,
|
|
6602
6793
|
durationSteps: p.durationSteps,
|
|
6603
|
-
|
|
6794
|
+
pitchUnits: p.pitchUnits,
|
|
6604
6795
|
velocity: p.velocity
|
|
6605
6796
|
}));
|
|
6606
6797
|
return {
|
|
@@ -6771,7 +6962,7 @@ var playNote = (options) => {
|
|
|
6771
6962
|
const dur = options.duration ?? 1;
|
|
6772
6963
|
synth.playNote({
|
|
6773
6964
|
trackId: "melody",
|
|
6774
|
-
|
|
6965
|
+
pitchUnits: options.pitchUnits,
|
|
6775
6966
|
velocity: 100,
|
|
6776
6967
|
volume: vol / 100,
|
|
6777
6968
|
when: 0,
|
|
@@ -6792,7 +6983,7 @@ var playChords = (chordStr, options = {}) => {
|
|
|
6792
6983
|
// 伴奏トラック
|
|
6793
6984
|
startStep: p.startStep,
|
|
6794
6985
|
durationSteps: p.durationSteps,
|
|
6795
|
-
|
|
6986
|
+
pitchUnits: p.pitchUnits,
|
|
6796
6987
|
velocity: p.velocity
|
|
6797
6988
|
}));
|
|
6798
6989
|
return playPlacements(placements, {
|
|
@@ -8949,7 +9140,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8949
9140
|
0
|
|
8950
9141
|
);
|
|
8951
9142
|
const timedNotes = placements.map((p) => ({
|
|
8952
|
-
pitch: p.
|
|
9143
|
+
pitch: unitsToPitchV1(p.pitchUnits),
|
|
8953
9144
|
when: p.startStep * secondsPerStep,
|
|
8954
9145
|
duration: p.durationSteps * secondsPerStep
|
|
8955
9146
|
}));
|
|
@@ -8982,7 +9173,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
8982
9173
|
id: id++,
|
|
8983
9174
|
startStep: p.startStep,
|
|
8984
9175
|
durationSteps: p.durationSteps,
|
|
8985
|
-
|
|
9176
|
+
pitchUnits: p.pitchUnits,
|
|
8986
9177
|
velocity: DEFAULT_VELOCITY
|
|
8987
9178
|
}));
|
|
8988
9179
|
return {
|
|
@@ -9807,7 +9998,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9807
9998
|
(a, b) => a.startStep - b.startStep
|
|
9808
9999
|
);
|
|
9809
10000
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
9810
|
-
const semis = (lt.octave ?? 0) *
|
|
10001
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
9811
10002
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
9812
10003
|
const notes = [];
|
|
9813
10004
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -9815,7 +10006,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
9815
10006
|
if (n.startStep < fromStep) continue;
|
|
9816
10007
|
notes.push({
|
|
9817
10008
|
syllable: lt.syllables[i2],
|
|
9818
|
-
pitch: n.
|
|
10009
|
+
pitch: units(n.pitchUnits + semis),
|
|
9819
10010
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
9820
10011
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
9821
10012
|
});
|
|
@@ -10090,6 +10281,7 @@ var SoundFont = class _SoundFont {
|
|
|
10090
10281
|
pitch = 60,
|
|
10091
10282
|
volume = 1,
|
|
10092
10283
|
velocity,
|
|
10284
|
+
detuneCents = 0,
|
|
10093
10285
|
when = 0,
|
|
10094
10286
|
duration = 1
|
|
10095
10287
|
} = {}) {
|
|
@@ -10109,7 +10301,7 @@ var SoundFont = class _SoundFont {
|
|
|
10109
10301
|
Object.assign(src, _param.src);
|
|
10110
10302
|
const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
|
|
10111
10303
|
const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
|
|
10112
|
-
src.detune.setValueAtTime(humanizeCents, 0);
|
|
10304
|
+
src.detune.setValueAtTime(humanizeCents + detuneCents, 0);
|
|
10113
10305
|
const effectiveVolume = volume * humanizeGainMul;
|
|
10114
10306
|
const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
|
|
10115
10307
|
const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
|
|
@@ -11248,7 +11440,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11248
11440
|
scheduleMetronomeBeats(ctx, cutGain);
|
|
11249
11441
|
};
|
|
11250
11442
|
const wafPlayDynamic = ({
|
|
11251
|
-
|
|
11443
|
+
pitchUnits,
|
|
11252
11444
|
velocity,
|
|
11253
11445
|
volume,
|
|
11254
11446
|
when,
|
|
@@ -11258,10 +11450,12 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11258
11450
|
}) => {
|
|
11259
11451
|
const waf = activeGmName ? _wafCache.get(activeGmName) : null;
|
|
11260
11452
|
if (waf) {
|
|
11453
|
+
const { midi, detuneCents } = unitsToMidiDetune(pitchUnits);
|
|
11261
11454
|
waf.play({
|
|
11262
11455
|
ctx,
|
|
11263
11456
|
destination,
|
|
11264
|
-
pitch,
|
|
11457
|
+
pitch: midi,
|
|
11458
|
+
detuneCents,
|
|
11265
11459
|
volume: volume * 0.85,
|
|
11266
11460
|
velocity,
|
|
11267
11461
|
when,
|
|
@@ -11271,7 +11465,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11271
11465
|
const synth = createSynth(ctx, destination);
|
|
11272
11466
|
synth.playNote({
|
|
11273
11467
|
trackId: "chord",
|
|
11274
|
-
|
|
11468
|
+
pitchUnits,
|
|
11275
11469
|
velocity: 100,
|
|
11276
11470
|
volume,
|
|
11277
11471
|
when,
|
|
@@ -11300,7 +11494,8 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11300
11494
|
trackIndex: 3,
|
|
11301
11495
|
startStep: whenStep,
|
|
11302
11496
|
durationSteps,
|
|
11303
|
-
|
|
11497
|
+
// chord-parser の構成音は12平均律の半音。内部表現の units へ写す。
|
|
11498
|
+
pitchUnits: pitchV1ToUnits(C32 + noteOffset),
|
|
11304
11499
|
velocity: 100
|
|
11305
11500
|
});
|
|
11306
11501
|
}
|
|
@@ -11313,7 +11508,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
|
|
|
11313
11508
|
synth: false,
|
|
11314
11509
|
onPlayNote: (e) => {
|
|
11315
11510
|
wafPlayDynamic({
|
|
11316
|
-
|
|
11511
|
+
pitchUnits: e.pitchUnits,
|
|
11317
11512
|
velocity: e.velocity,
|
|
11318
11513
|
volume: e.volume,
|
|
11319
11514
|
when: e.when,
|
|
@@ -11755,6 +11950,14 @@ var buildUI = (target, options) => {
|
|
|
11755
11950
|
<summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
|
|
11756
11951
|
<div class="dtm-panel-body">
|
|
11757
11952
|
<div data-dtm="preset-select-slot"></div>
|
|
11953
|
+
<div class="dtm-row">
|
|
11954
|
+
<span class="dtm-label">\u97F3\u5F8B</span>
|
|
11955
|
+
<select class="dtm-select dtm-grow" data-dtm="edo-select">
|
|
11956
|
+
<option value="12">12\u5E73\u5747\u5F8B\uFF08\u901A\u5E38\uFF09</option>
|
|
11957
|
+
<option value="31">31\u5E73\u5747\u5F8B\uFF08\u5FAE\u5206\u97F3\uFF09</option>
|
|
11958
|
+
</select>
|
|
11959
|
+
<button class="dtm-infobtn" data-dtm="edo-info" title="\u97F3\u5F8B\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
|
|
11960
|
+
</div>
|
|
11758
11961
|
<div class="dtm-row">
|
|
11759
11962
|
<span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
|
|
11760
11963
|
<input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
|
|
@@ -12087,6 +12290,8 @@ var buildUI = (target, options) => {
|
|
|
12087
12290
|
copyMiniBtn: sel("copy-mini"),
|
|
12088
12291
|
overlay: sel("overlay"),
|
|
12089
12292
|
mmlInfoBtn: sel("mml-info"),
|
|
12293
|
+
edoSelect: sel("edo-select"),
|
|
12294
|
+
edoInfoBtn: sel("edo-info"),
|
|
12090
12295
|
modalOverlay: sel("modal-overlay"),
|
|
12091
12296
|
modalTitle: sel("modal-title"),
|
|
12092
12297
|
modalBody: sel("modal-body"),
|
|
@@ -12207,17 +12412,21 @@ var SCALES = [
|
|
|
12207
12412
|
[0, 2, 4, 7, 9]
|
|
12208
12413
|
// Pentatonic Major
|
|
12209
12414
|
];
|
|
12415
|
+
var MEANTONE_STEP_BY_SEMITONE2 = [0, 3, 5, 8, 10, 13, 16, 18, 21, 23, 26, 28];
|
|
12210
12416
|
var generateRandomPattern = (core, options) => {
|
|
12211
|
-
const { stepsPerBar, startStep, pitchRangeStart } = options;
|
|
12417
|
+
const { stepsPerBar, startStep, pitchRangeStart, edo = 12 } = options;
|
|
12212
12418
|
const numBars = 8;
|
|
12213
12419
|
const noteLength = 24;
|
|
12214
|
-
const
|
|
12420
|
+
const semitoneToStep = (semi) => edo === 31 ? MEANTONE_STEP_BY_SEMITONE2[semi] : semi;
|
|
12421
|
+
const upr = UNITS_PER_OCTAVE / edo;
|
|
12422
|
+
const basePitch = pitchRangeStart + 60 * UNITS_PER_SEMITONE;
|
|
12215
12423
|
const scale = SCALES[Math.floor(Math.random() * SCALES.length)];
|
|
12216
12424
|
const rootOffset = Math.floor(Math.random() * 12);
|
|
12217
12425
|
const availablePitches = [];
|
|
12218
12426
|
for (let i2 = 0; i2 < 12; i2++) {
|
|
12219
12427
|
const noteInOctave = (i2 - rootOffset + 12) % 12;
|
|
12220
|
-
if (scale.includes(noteInOctave))
|
|
12428
|
+
if (scale.includes(noteInOctave))
|
|
12429
|
+
availablePitches.push(units(basePitch + semitoneToStep(i2) * upr));
|
|
12221
12430
|
}
|
|
12222
12431
|
core.beginBatch();
|
|
12223
12432
|
for (let bar = 0; bar < numBars; bar++) {
|
|
@@ -12252,14 +12461,17 @@ var applyHarmonicFilter = (targetCore, chordCore, options) => {
|
|
|
12252
12461
|
const isNewBar = halfBar % 2 === 0;
|
|
12253
12462
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12254
12463
|
if (chordHere.length > 0) {
|
|
12255
|
-
currentClasses = new Set(
|
|
12464
|
+
currentClasses = new Set(
|
|
12465
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12466
|
+
);
|
|
12256
12467
|
} else if (isNewBar) {
|
|
12257
12468
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12258
12469
|
}
|
|
12259
12470
|
if (currentClasses.size === 0) continue;
|
|
12260
12471
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12261
12472
|
for (const n of activeHere) {
|
|
12262
|
-
if (!currentClasses.has(n.
|
|
12473
|
+
if (!currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE))
|
|
12474
|
+
targetCore.deleteNoteById(n.id);
|
|
12263
12475
|
}
|
|
12264
12476
|
}
|
|
12265
12477
|
targetCore.endBatch();
|
|
@@ -12281,13 +12493,17 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12281
12493
|
const isNewBar = halfBar % 2 === 0;
|
|
12282
12494
|
const chordHere = chordCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12283
12495
|
if (chordHere.length > 0) {
|
|
12284
|
-
currentClasses = new Set(
|
|
12496
|
+
currentClasses = new Set(
|
|
12497
|
+
chordHere.map((n) => n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12498
|
+
);
|
|
12285
12499
|
} else if (isNewBar) {
|
|
12286
12500
|
currentClasses = /* @__PURE__ */ new Set();
|
|
12287
12501
|
}
|
|
12288
12502
|
if (currentClasses.size === 0) continue;
|
|
12289
12503
|
const activeHere = targetCore.getNotes().filter((n) => n.startStep >= start && n.startStep < end);
|
|
12290
|
-
const filtered = activeHere.filter(
|
|
12504
|
+
const filtered = activeHere.filter(
|
|
12505
|
+
(n) => currentClasses.has(n.pitchUnits % UNITS_PER_OCTAVE)
|
|
12506
|
+
);
|
|
12291
12507
|
const filteredIds = new Set(filtered.map((n) => n.id));
|
|
12292
12508
|
for (const n of activeHere) {
|
|
12293
12509
|
if (!filteredIds.has(n.id)) targetCore.deleteNoteById(n.id);
|
|
@@ -12299,7 +12515,7 @@ var applyMonophonic = (targetCore, chordCore, options) => {
|
|
|
12299
12515
|
}
|
|
12300
12516
|
for (const notesAtTime of timeMap.values()) {
|
|
12301
12517
|
if (notesAtTime.length > 1) {
|
|
12302
|
-
notesAtTime.sort((a, b) => b.
|
|
12518
|
+
notesAtTime.sort((a, b) => b.pitchUnits - a.pitchUnits);
|
|
12303
12519
|
const [, ...others] = notesAtTime;
|
|
12304
12520
|
for (const on of others) targetCore.deleteNoteById(on.id);
|
|
12305
12521
|
}
|
|
@@ -12315,18 +12531,23 @@ var shiftNotes = (cores, shiftSteps) => {
|
|
|
12315
12531
|
for (const note of notes) {
|
|
12316
12532
|
const newStart = note.startStep + shiftSteps;
|
|
12317
12533
|
if (newStart < 0) core.deleteNoteById(note.id);
|
|
12318
|
-
else core.moveNote(note.id, newStart, note.
|
|
12534
|
+
else core.moveNote(note.id, newStart, note.pitchUnits);
|
|
12319
12535
|
}
|
|
12320
12536
|
core.saveHistory();
|
|
12321
12537
|
}
|
|
12322
12538
|
};
|
|
12323
|
-
var transposeNotes = (cores,
|
|
12324
|
-
if (
|
|
12539
|
+
var transposeNotes = (cores, steps) => {
|
|
12540
|
+
if (steps === 0) return;
|
|
12325
12541
|
for (const core of cores) {
|
|
12326
12542
|
const notes = [...core.getNotes()];
|
|
12327
12543
|
for (const note of notes) {
|
|
12328
|
-
const newPitch =
|
|
12329
|
-
|
|
12544
|
+
const newPitch = units(
|
|
12545
|
+
Math.max(
|
|
12546
|
+
PITCH_RANGE_START,
|
|
12547
|
+
Math.min(PITCH_RANGE_END, note.pitchUnits + steps)
|
|
12548
|
+
)
|
|
12549
|
+
);
|
|
12550
|
+
if (newPitch !== note.pitchUnits) {
|
|
12330
12551
|
core.moveNote(note.id, note.startStep, newPitch);
|
|
12331
12552
|
}
|
|
12332
12553
|
}
|
|
@@ -12651,11 +12872,31 @@ var exportMIDI = (options) => {
|
|
|
12651
12872
|
const div = 480;
|
|
12652
12873
|
const tickPerStep = div / STEPS_PER_BEAT3;
|
|
12653
12874
|
const midiTracks = [];
|
|
12654
|
-
|
|
12875
|
+
const NOTE_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
|
|
12876
|
+
const BEND_RANGE_SEMITONES = 2;
|
|
12877
|
+
const bendKey = (cents) => Math.round(cents * 100);
|
|
12878
|
+
const bendUse = /* @__PURE__ */ new Map();
|
|
12879
|
+
for (const track of tracks) {
|
|
12880
|
+
for (const n of track.notes) {
|
|
12881
|
+
const { detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12882
|
+
const k = bendKey(detuneCents);
|
|
12883
|
+
bendUse.set(k, (bendUse.get(k) ?? 0) + 1);
|
|
12884
|
+
}
|
|
12885
|
+
}
|
|
12886
|
+
const bendChannel = /* @__PURE__ */ new Map();
|
|
12887
|
+
const ordered = [...bendUse.entries()].sort((a, b) => b[1] - a[1]);
|
|
12888
|
+
for (const [k] of ordered) {
|
|
12889
|
+
if (bendChannel.size >= NOTE_CHANNELS.length) break;
|
|
12890
|
+
bendChannel.set(k, NOTE_CHANNELS[bendChannel.size]);
|
|
12891
|
+
}
|
|
12892
|
+
tracks.forEach((track) => {
|
|
12655
12893
|
if (track.notes.length === 0) return;
|
|
12656
|
-
const channel = ch < 9 ? ch : ch + 1 & 15;
|
|
12657
12894
|
const events = [];
|
|
12658
12895
|
for (const n of track.notes) {
|
|
12896
|
+
const { midi, detuneCents } = unitsToMidiDetune(n.pitchUnits);
|
|
12897
|
+
const k = bendKey(detuneCents);
|
|
12898
|
+
const channel = bendChannel.get(k) ?? bendChannel.get(0) ?? 0;
|
|
12899
|
+
const note = Math.max(0, Math.min(127, midi));
|
|
12659
12900
|
const startTick = Math.round(n.startStep * tickPerStep);
|
|
12660
12901
|
const endTick = Math.round(
|
|
12661
12902
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
@@ -12663,12 +12904,25 @@ var exportMIDI = (options) => {
|
|
|
12663
12904
|
const vel = Math.round(
|
|
12664
12905
|
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
12665
12906
|
);
|
|
12666
|
-
events.push({ t: startTick, m: [144 | channel,
|
|
12667
|
-
events.push({ t: endTick, m: [144 | channel,
|
|
12907
|
+
events.push({ t: startTick, m: [144 | channel, note, vel] });
|
|
12908
|
+
events.push({ t: endTick, m: [144 | channel, note, 0] });
|
|
12668
12909
|
}
|
|
12669
12910
|
events.sort((a, b) => a.t - b.t);
|
|
12670
12911
|
midiTracks.push(events);
|
|
12671
12912
|
});
|
|
12913
|
+
const bendSetup = [];
|
|
12914
|
+
for (const [k, channel] of bendChannel) {
|
|
12915
|
+
const cents = k / 100;
|
|
12916
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 0] });
|
|
12917
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 0] });
|
|
12918
|
+
bendSetup.push({ t: 0, m: [176 | channel, 6, BEND_RANGE_SEMITONES] });
|
|
12919
|
+
bendSetup.push({ t: 0, m: [176 | channel, 38, 0] });
|
|
12920
|
+
bendSetup.push({ t: 0, m: [176 | channel, 101, 127] });
|
|
12921
|
+
bendSetup.push({ t: 0, m: [176 | channel, 100, 127] });
|
|
12922
|
+
const raw = 8192 + Math.round(cents / (BEND_RANGE_SEMITONES * 100) * 8192);
|
|
12923
|
+
const v = Math.max(0, Math.min(16383, raw));
|
|
12924
|
+
bendSetup.push({ t: 0, m: [224 | channel, v & 127, v >> 7 & 127] });
|
|
12925
|
+
}
|
|
12672
12926
|
const maxStep = Math.max(
|
|
12673
12927
|
...tracks.filter((t) => t.notes.length > 0).map(
|
|
12674
12928
|
(t) => Math.max(...t.notes.map((n) => n.startStep + n.durationSteps))
|
|
@@ -12705,6 +12959,7 @@ var exportMIDI = (options) => {
|
|
|
12705
12959
|
headerChunks(arr, midiTracks.length + 1, div);
|
|
12706
12960
|
trackChunks(arr, (a) => {
|
|
12707
12961
|
a.push(0, 255, 81, 3, ...to3byte(Math.round(6e7 / bpm)));
|
|
12962
|
+
for (const ev of bendSetup) a.push(0, ...ev.m);
|
|
12708
12963
|
});
|
|
12709
12964
|
for (const events of midiTracks) {
|
|
12710
12965
|
trackChunks(arr, (a) => {
|
|
@@ -12951,403 +13206,8 @@ var LinkedList = class {
|
|
|
12951
13206
|
}
|
|
12952
13207
|
};
|
|
12953
13208
|
|
|
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
13209
|
// src/mml-core.ts
|
|
13350
|
-
var
|
|
13210
|
+
var PITCH_MAP = [
|
|
13351
13211
|
"c",
|
|
13352
13212
|
"c+",
|
|
13353
13213
|
"d",
|
|
@@ -13361,6 +13221,39 @@ var PITCH_MAP2 = [
|
|
|
13361
13221
|
"a+",
|
|
13362
13222
|
"b"
|
|
13363
13223
|
];
|
|
13224
|
+
var EDO31_NAMES = [
|
|
13225
|
+
"c",
|
|
13226
|
+
"c+",
|
|
13227
|
+
"c#",
|
|
13228
|
+
"d-",
|
|
13229
|
+
"d_",
|
|
13230
|
+
"d",
|
|
13231
|
+
"d+",
|
|
13232
|
+
"d#",
|
|
13233
|
+
"e-",
|
|
13234
|
+
"e_",
|
|
13235
|
+
"e",
|
|
13236
|
+
"f-",
|
|
13237
|
+
"e#",
|
|
13238
|
+
"f",
|
|
13239
|
+
"f+",
|
|
13240
|
+
"f#",
|
|
13241
|
+
"g-",
|
|
13242
|
+
"g_",
|
|
13243
|
+
"g",
|
|
13244
|
+
"g+",
|
|
13245
|
+
"g#",
|
|
13246
|
+
"a-",
|
|
13247
|
+
"a_",
|
|
13248
|
+
"a",
|
|
13249
|
+
"a+",
|
|
13250
|
+
"a#",
|
|
13251
|
+
"b-",
|
|
13252
|
+
"b_",
|
|
13253
|
+
"b",
|
|
13254
|
+
"b+",
|
|
13255
|
+
"b#"
|
|
13256
|
+
];
|
|
13364
13257
|
var MMLCore = class _MMLCore {
|
|
13365
13258
|
notes = [];
|
|
13366
13259
|
nextNoteId = 0;
|
|
@@ -13374,9 +13267,12 @@ var MMLCore = class _MMLCore {
|
|
|
13374
13267
|
lastUndoTime = 0;
|
|
13375
13268
|
static UNDO_DEBOUNCE_MS = 100;
|
|
13376
13269
|
toolMode = "pen";
|
|
13377
|
-
|
|
13270
|
+
/** グリッド寸法の供給元(描画器ではなく呼び出し側が持つ設定を読む) */
|
|
13271
|
+
getConfig;
|
|
13272
|
+
constructor(handlers, volume = 80, getConfig) {
|
|
13378
13273
|
this.handlers = handlers;
|
|
13379
13274
|
this.volume = volume;
|
|
13275
|
+
this.getConfig = getConfig;
|
|
13380
13276
|
this.lastHistorySnapshot = JSON.stringify(this.notes);
|
|
13381
13277
|
this.history.add([]);
|
|
13382
13278
|
this.generateAndNotify();
|
|
@@ -13463,14 +13359,14 @@ var MMLCore = class _MMLCore {
|
|
|
13463
13359
|
*/
|
|
13464
13360
|
addNote(step, pitch, options) {
|
|
13465
13361
|
const existingIndex = this.notes.findIndex(
|
|
13466
|
-
(n) => n.startStep === step && n.
|
|
13362
|
+
(n) => n.startStep === step && n.pitchUnits === pitch
|
|
13467
13363
|
);
|
|
13468
13364
|
if (existingIndex === -1) {
|
|
13469
13365
|
const newNote = {
|
|
13470
13366
|
id: this.nextNoteId++,
|
|
13471
13367
|
startStep: step,
|
|
13472
13368
|
durationSteps: options.noteLengthSteps,
|
|
13473
|
-
pitch,
|
|
13369
|
+
pitchUnits: pitch,
|
|
13474
13370
|
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
13475
13371
|
};
|
|
13476
13372
|
this.notes.push(newNote);
|
|
@@ -13498,19 +13394,20 @@ var MMLCore = class _MMLCore {
|
|
|
13498
13394
|
moveNote(noteId, startStep, pitch) {
|
|
13499
13395
|
const note = this.notes.find((target) => target.id === noteId);
|
|
13500
13396
|
if (!note) return;
|
|
13501
|
-
const totalSteps = this.getMaxStep() +
|
|
13502
|
-
const
|
|
13503
|
-
const
|
|
13504
|
-
const
|
|
13505
|
-
|
|
13506
|
-
|
|
13397
|
+
const totalSteps = this.getMaxStep() + this.getConfig().stepsPerBar;
|
|
13398
|
+
const cfg = this.getConfig();
|
|
13399
|
+
const upr = cfg.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
13400
|
+
const pitchRangeStart = cfg.pitchRangeStart;
|
|
13401
|
+
const pitchRangeEnd = pitchRangeStart + (cfg.keyCount - 1) * upr;
|
|
13402
|
+
const clampedPitch = units(
|
|
13403
|
+
Math.min(Math.max(pitch, pitchRangeStart), pitchRangeEnd)
|
|
13507
13404
|
);
|
|
13508
13405
|
const clampedStart = Math.min(
|
|
13509
13406
|
Math.max(startStep, 0),
|
|
13510
13407
|
totalSteps - note.durationSteps
|
|
13511
13408
|
);
|
|
13512
13409
|
note.startStep = clampedStart;
|
|
13513
|
-
note.
|
|
13410
|
+
note.pitchUnits = clampedPitch;
|
|
13514
13411
|
this.notes.sort((a, b) => a.startStep - b.startStep);
|
|
13515
13412
|
this.generateAndNotify();
|
|
13516
13413
|
}
|
|
@@ -13555,7 +13452,7 @@ var MMLCore = class _MMLCore {
|
|
|
13555
13452
|
* ただし、残りステップ(limit)は絶対に超えない。
|
|
13556
13453
|
*/
|
|
13557
13454
|
stepsToMMLDuration(steps, limit) {
|
|
13558
|
-
const config =
|
|
13455
|
+
const config = this.getConfig();
|
|
13559
13456
|
const total = config.stepsPerBar;
|
|
13560
13457
|
const candidates = [
|
|
13561
13458
|
{ dur: "1.", s: total * 1.5 },
|
|
@@ -13591,7 +13488,7 @@ var MMLCore = class _MMLCore {
|
|
|
13591
13488
|
* ギャップに収まる最大の音符を探す(減算アルゴリズム用)
|
|
13592
13489
|
*/
|
|
13593
13490
|
findBestFitDuration(gap) {
|
|
13594
|
-
const config =
|
|
13491
|
+
const config = this.getConfig();
|
|
13595
13492
|
const durations = [1, 2, 4, 8, 12, 16, 24, 32, 48, 64];
|
|
13596
13493
|
for (const d of durations) {
|
|
13597
13494
|
const stepLen = config.stepsPerBar / d;
|
|
@@ -13601,12 +13498,20 @@ var MMLCore = class _MMLCore {
|
|
|
13601
13498
|
}
|
|
13602
13499
|
return { dur: 64, steps: config.stepsPerBar / 64 };
|
|
13603
13500
|
}
|
|
13501
|
+
/** ピッチ(units) → その音律での「オクターブ番号」と「音名」。 */
|
|
13502
|
+
spell(pitchUnits) {
|
|
13503
|
+
const edo = this.getConfig().edo ?? 12;
|
|
13504
|
+
const names = edo === 31 ? EDO31_NAMES : PITCH_MAP;
|
|
13505
|
+
const octave = Math.floor(pitchUnits / 372) - 1;
|
|
13506
|
+
const within = (pitchUnits % 372 + 372) % 372;
|
|
13507
|
+
const step = Math.round(within / (372 / edo));
|
|
13508
|
+
return { octave, name: names[step % edo] };
|
|
13509
|
+
}
|
|
13604
13510
|
/**
|
|
13605
13511
|
* ピッチからオクターブ最適化のある音名を取得
|
|
13606
13512
|
*/
|
|
13607
13513
|
getNoteWithOctave(pitch, lastOctave) {
|
|
13608
|
-
const octave =
|
|
13609
|
-
const name = PITCH_MAP2[pitch % 12];
|
|
13514
|
+
const { octave, name } = this.spell(pitch);
|
|
13610
13515
|
if (lastOctave === -1 || Math.abs(octave - lastOctave) >= 2) {
|
|
13611
13516
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
13612
13517
|
}
|
|
@@ -13629,7 +13534,7 @@ var MMLCore = class _MMLCore {
|
|
|
13629
13534
|
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
13630
13535
|
*/
|
|
13631
13536
|
generateMML = (volumeOverride) => {
|
|
13632
|
-
const config =
|
|
13537
|
+
const config = this.getConfig();
|
|
13633
13538
|
const vol = volumeOverride ?? this.volume;
|
|
13634
13539
|
const header = `t${this.tempo} v${vol}`;
|
|
13635
13540
|
const segments = [];
|
|
@@ -13670,14 +13575,13 @@ var MMLCore = class _MMLCore {
|
|
|
13670
13575
|
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
13671
13576
|
if (notes.length > 1) {
|
|
13672
13577
|
const noteStrs = notes.map((n) => {
|
|
13673
|
-
const oct =
|
|
13674
|
-
const name = PITCH_MAP2[n.pitch % 12];
|
|
13578
|
+
const { octave: oct, name } = this.spell(n.pitchUnits);
|
|
13675
13579
|
return `o${oct}${name}`;
|
|
13676
13580
|
});
|
|
13677
13581
|
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
13678
13582
|
} else {
|
|
13679
13583
|
const { text, currentOctave } = this.getNoteWithOctave(
|
|
13680
|
-
notes[0].
|
|
13584
|
+
notes[0].pitchUnits,
|
|
13681
13585
|
lastOctave
|
|
13682
13586
|
);
|
|
13683
13587
|
segments.push(`${text}${durStr}`);
|
|
@@ -13708,7 +13612,7 @@ var MMLCore = class _MMLCore {
|
|
|
13708
13612
|
* MMLの音長文字列("4", "4.", "12"など)をステップ数に変換する
|
|
13709
13613
|
*/
|
|
13710
13614
|
getStepFromDottedMML(durStr) {
|
|
13711
|
-
const config =
|
|
13615
|
+
const config = this.getConfig();
|
|
13712
13616
|
const total = config.stepsPerBar;
|
|
13713
13617
|
const isDotted = durStr.endsWith(".");
|
|
13714
13618
|
const baseDur = parseInt(isDotted ? durStr.slice(0, -1) : durStr, 10);
|
|
@@ -13718,7 +13622,7 @@ var MMLCore = class _MMLCore {
|
|
|
13718
13622
|
};
|
|
13719
13623
|
var decomposeToMonophonic = (notes) => {
|
|
13720
13624
|
const sorted = [...notes].sort(
|
|
13721
|
-
(a, b) => a.startStep - b.startStep || a.
|
|
13625
|
+
(a, b) => a.startStep - b.startStep || a.pitchUnits - b.pitchUnits
|
|
13722
13626
|
);
|
|
13723
13627
|
const tracks = [];
|
|
13724
13628
|
const trackEnds = [];
|
|
@@ -13753,6 +13657,493 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
|
|
|
13753
13657
|
return chordNotes / notes.length >= threshold;
|
|
13754
13658
|
};
|
|
13755
13659
|
|
|
13660
|
+
// src/renderer.ts
|
|
13661
|
+
var KEYBOARD_WIDTH = 60;
|
|
13662
|
+
var HEADER_HEIGHT = 20;
|
|
13663
|
+
var NATURAL_STEPS3 = {
|
|
13664
|
+
12: [0, 2, 4, 5, 7, 9, 11],
|
|
13665
|
+
31: [0, 5, 10, 13, 18, 23, 28]
|
|
13666
|
+
};
|
|
13667
|
+
var keyTier = (step, edo) => {
|
|
13668
|
+
const naturals = NATURAL_STEPS3[edo] ?? NATURAL_STEPS3[12];
|
|
13669
|
+
let best = edo;
|
|
13670
|
+
for (const n of naturals) {
|
|
13671
|
+
const d = Math.abs(step - n);
|
|
13672
|
+
best = Math.min(best, d, edo - d);
|
|
13673
|
+
}
|
|
13674
|
+
if (best === 0) return 0;
|
|
13675
|
+
return best >= (edo === 31 ? 2 : 1) ? 2 : 1;
|
|
13676
|
+
};
|
|
13677
|
+
var KEY_NAMES = [
|
|
13678
|
+
"C",
|
|
13679
|
+
"C#",
|
|
13680
|
+
"D",
|
|
13681
|
+
"D#",
|
|
13682
|
+
"E",
|
|
13683
|
+
"F",
|
|
13684
|
+
"F#",
|
|
13685
|
+
"G",
|
|
13686
|
+
"G#",
|
|
13687
|
+
"A",
|
|
13688
|
+
"A#",
|
|
13689
|
+
"B"
|
|
13690
|
+
];
|
|
13691
|
+
var createRenderer = (mountTarget, width = 800, height = 450, config) => {
|
|
13692
|
+
let g_header_canvas;
|
|
13693
|
+
let g_key_canvas;
|
|
13694
|
+
let g_grid_canvas;
|
|
13695
|
+
let g_header_ctx;
|
|
13696
|
+
let g_key_ctx;
|
|
13697
|
+
let g_grid_ctx;
|
|
13698
|
+
let g_config = config;
|
|
13699
|
+
const getRenderConfig = () => g_config;
|
|
13700
|
+
let g_draw_offset_x = 0;
|
|
13701
|
+
let g_draw_offset_y = 0;
|
|
13702
|
+
let g_bg_active = false;
|
|
13703
|
+
const setBackgroundActive = (active) => {
|
|
13704
|
+
g_bg_active = active;
|
|
13705
|
+
};
|
|
13706
|
+
const getDrawOffset = () => ({
|
|
13707
|
+
x: g_draw_offset_x,
|
|
13708
|
+
y: g_draw_offset_y
|
|
13709
|
+
});
|
|
13710
|
+
const getGridCanvas = () => g_grid_canvas;
|
|
13711
|
+
const getGridContext = () => g_grid_ctx;
|
|
13712
|
+
const getHeaderCanvas = () => g_header_canvas;
|
|
13713
|
+
const setup = () => {
|
|
13714
|
+
g_config = config;
|
|
13715
|
+
const headerCanvas = document.createElement("canvas");
|
|
13716
|
+
g_header_canvas = headerCanvas;
|
|
13717
|
+
headerCanvas.width = width - KEYBOARD_WIDTH;
|
|
13718
|
+
headerCanvas.height = HEADER_HEIGHT;
|
|
13719
|
+
headerCanvas.style.position = "absolute";
|
|
13720
|
+
headerCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13721
|
+
headerCanvas.style.top = "0px";
|
|
13722
|
+
const headerCtx = headerCanvas.getContext("2d");
|
|
13723
|
+
if (!headerCtx)
|
|
13724
|
+
throw new Error("Failed to get 2D rendering context for header.");
|
|
13725
|
+
g_header_ctx = headerCtx;
|
|
13726
|
+
const keyCanvas = document.createElement("canvas");
|
|
13727
|
+
g_key_canvas = keyCanvas;
|
|
13728
|
+
keyCanvas.width = KEYBOARD_WIDTH;
|
|
13729
|
+
keyCanvas.height = height - HEADER_HEIGHT;
|
|
13730
|
+
keyCanvas.style.position = "absolute";
|
|
13731
|
+
keyCanvas.style.left = "0px";
|
|
13732
|
+
keyCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13733
|
+
const keyCtx = keyCanvas.getContext("2d");
|
|
13734
|
+
if (!keyCtx)
|
|
13735
|
+
throw new Error("Failed to get 2D rendering context for keyboard.");
|
|
13736
|
+
g_key_ctx = keyCtx;
|
|
13737
|
+
const gridCanvas = document.createElement("canvas");
|
|
13738
|
+
g_grid_canvas = gridCanvas;
|
|
13739
|
+
gridCanvas.width = width - KEYBOARD_WIDTH;
|
|
13740
|
+
gridCanvas.height = height - HEADER_HEIGHT;
|
|
13741
|
+
gridCanvas.style.position = "absolute";
|
|
13742
|
+
gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
|
|
13743
|
+
gridCanvas.style.top = `${HEADER_HEIGHT}px`;
|
|
13744
|
+
gridCanvas.style.touchAction = "none";
|
|
13745
|
+
gridCanvas.style.userSelect = "none";
|
|
13746
|
+
const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
|
|
13747
|
+
if (!gridCtx)
|
|
13748
|
+
throw new Error("Failed to get 2D rendering context for grid.");
|
|
13749
|
+
g_grid_ctx = gridCtx;
|
|
13750
|
+
mountTarget.innerHTML = "";
|
|
13751
|
+
mountTarget.style.position = "relative";
|
|
13752
|
+
mountTarget.style.width = `${width + KEYBOARD_WIDTH}px`;
|
|
13753
|
+
mountTarget.style.height = `${height}px`;
|
|
13754
|
+
mountTarget.append(headerCanvas, keyCanvas, gridCanvas);
|
|
13755
|
+
drawHeaderCorner();
|
|
13756
|
+
};
|
|
13757
|
+
const drawHeaderCorner = () => {
|
|
13758
|
+
const mountTarget2 = g_key_canvas.parentElement;
|
|
13759
|
+
if (!mountTarget2) return;
|
|
13760
|
+
let cornerDiv = mountTarget2.querySelector(
|
|
13761
|
+
"#header-corner"
|
|
13762
|
+
);
|
|
13763
|
+
if (!cornerDiv) {
|
|
13764
|
+
cornerDiv = document.createElement("div");
|
|
13765
|
+
cornerDiv.id = "header-corner";
|
|
13766
|
+
cornerDiv.style.position = "absolute";
|
|
13767
|
+
cornerDiv.style.left = "0px";
|
|
13768
|
+
cornerDiv.style.top = "0px";
|
|
13769
|
+
cornerDiv.style.width = `${KEYBOARD_WIDTH}px`;
|
|
13770
|
+
cornerDiv.style.height = `${HEADER_HEIGHT}px`;
|
|
13771
|
+
cornerDiv.style.backgroundColor = "#0a0f1f";
|
|
13772
|
+
cornerDiv.style.borderRight = "2px solid #29adff";
|
|
13773
|
+
cornerDiv.style.borderBottom = "2px solid #29adff";
|
|
13774
|
+
mountTarget2.insertBefore(cornerDiv, g_header_canvas);
|
|
13775
|
+
}
|
|
13776
|
+
};
|
|
13777
|
+
const drawKeyboard = () => {
|
|
13778
|
+
g_key_ctx.clearRect(0, 0, g_key_canvas.width, g_key_canvas.height);
|
|
13779
|
+
const {
|
|
13780
|
+
keyHeight,
|
|
13781
|
+
keyCount,
|
|
13782
|
+
pitchRangeStart,
|
|
13783
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13784
|
+
edo = 12
|
|
13785
|
+
} = g_config;
|
|
13786
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13787
|
+
const endY = g_draw_offset_y + g_key_canvas.height;
|
|
13788
|
+
const WHITE_KEY = "#ccc8b4";
|
|
13789
|
+
const BLACK_KEY = "#111111";
|
|
13790
|
+
const MICRO_KEY = "#4a4a4a";
|
|
13791
|
+
const BK_EDGE = "#383838";
|
|
13792
|
+
const WW_SEP = "#807a6a";
|
|
13793
|
+
const BK_RATIO = 0.62;
|
|
13794
|
+
const MICRO_RATIO = 0.45;
|
|
13795
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13796
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13797
|
+
const units2 = pitchRangeStart + rowIndex * upr;
|
|
13798
|
+
const step = Math.round(
|
|
13799
|
+
(units2 % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13800
|
+
);
|
|
13801
|
+
const tier = keyTier(step, edo);
|
|
13802
|
+
const octave = Math.floor(units2 / UNITS_PER_OCTAVE) - 1;
|
|
13803
|
+
const isC4Range = octave === 4;
|
|
13804
|
+
const screenY = y - g_draw_offset_y;
|
|
13805
|
+
const bkW = Math.floor(
|
|
13806
|
+
KEYBOARD_WIDTH * (tier === 1 ? MICRO_RATIO : BK_RATIO)
|
|
13807
|
+
);
|
|
13808
|
+
if (tier !== 0) {
|
|
13809
|
+
g_key_ctx.fillStyle = isC4Range ? "#d8d4be" : WHITE_KEY;
|
|
13810
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13811
|
+
g_key_ctx.fillStyle = tier === 1 ? MICRO_KEY : isC4Range ? "#1a1408" : BLACK_KEY;
|
|
13812
|
+
g_key_ctx.fillRect(0, screenY, bkW, keyHeight);
|
|
13813
|
+
g_key_ctx.strokeStyle = BK_EDGE;
|
|
13814
|
+
g_key_ctx.lineWidth = 1;
|
|
13815
|
+
g_key_ctx.beginPath();
|
|
13816
|
+
g_key_ctx.moveTo(bkW, screenY);
|
|
13817
|
+
g_key_ctx.lineTo(bkW, screenY + keyHeight);
|
|
13818
|
+
g_key_ctx.stroke();
|
|
13819
|
+
} else {
|
|
13820
|
+
g_key_ctx.fillStyle = isC4Range ? "#dedad0" : WHITE_KEY;
|
|
13821
|
+
g_key_ctx.fillRect(0, screenY, KEYBOARD_WIDTH, keyHeight);
|
|
13822
|
+
if (edo === 12 && (step === 5 || step === 0)) {
|
|
13823
|
+
g_key_ctx.strokeStyle = WW_SEP;
|
|
13824
|
+
g_key_ctx.lineWidth = 1;
|
|
13825
|
+
g_key_ctx.beginPath();
|
|
13826
|
+
g_key_ctx.moveTo(0, screenY + keyHeight - 0.5);
|
|
13827
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, screenY + keyHeight - 0.5);
|
|
13828
|
+
g_key_ctx.stroke();
|
|
13829
|
+
}
|
|
13830
|
+
}
|
|
13831
|
+
if (step === 0) {
|
|
13832
|
+
g_key_ctx.fillStyle = "#555040";
|
|
13833
|
+
g_key_ctx.font = "10px 'k8x12',monospace";
|
|
13834
|
+
g_key_ctx.textAlign = "right";
|
|
13835
|
+
g_key_ctx.textBaseline = "bottom";
|
|
13836
|
+
g_key_ctx.fillText(
|
|
13837
|
+
`${KEY_NAMES[0]}${octave}`,
|
|
13838
|
+
KEYBOARD_WIDTH - 4,
|
|
13839
|
+
screenY + keyHeight - 2
|
|
13840
|
+
);
|
|
13841
|
+
}
|
|
13842
|
+
}
|
|
13843
|
+
g_key_ctx.beginPath();
|
|
13844
|
+
g_key_ctx.strokeStyle = "#29adff";
|
|
13845
|
+
g_key_ctx.lineWidth = 2;
|
|
13846
|
+
g_key_ctx.moveTo(KEYBOARD_WIDTH, 0);
|
|
13847
|
+
g_key_ctx.lineTo(KEYBOARD_WIDTH, g_key_canvas.height);
|
|
13848
|
+
g_key_ctx.stroke();
|
|
13849
|
+
};
|
|
13850
|
+
const drawHeader = () => {
|
|
13851
|
+
g_header_ctx.clearRect(0, 0, g_header_canvas.width, g_header_canvas.height);
|
|
13852
|
+
const { stepWidth, stepsPerBar } = g_config;
|
|
13853
|
+
g_header_ctx.save();
|
|
13854
|
+
g_header_ctx.translate(-g_draw_offset_x, 0);
|
|
13855
|
+
g_header_ctx.fillStyle = g_bg_active ? "rgba(10,15,31,0.55)" : "#0a0f1f";
|
|
13856
|
+
g_header_ctx.fillRect(
|
|
13857
|
+
g_draw_offset_x,
|
|
13858
|
+
0,
|
|
13859
|
+
g_header_canvas.width,
|
|
13860
|
+
HEADER_HEIGHT
|
|
13861
|
+
);
|
|
13862
|
+
g_header_ctx.strokeStyle = "#3d405b";
|
|
13863
|
+
g_header_ctx.lineWidth = 1;
|
|
13864
|
+
g_header_ctx.font = "11px 'k8x12',monospace";
|
|
13865
|
+
g_header_ctx.fillStyle = "#83769c";
|
|
13866
|
+
const startBar = Math.floor(g_draw_offset_x / (stepsPerBar * stepWidth));
|
|
13867
|
+
const endBar = Math.ceil(
|
|
13868
|
+
(g_draw_offset_x + g_header_canvas.width) / (stepsPerBar * stepWidth)
|
|
13869
|
+
);
|
|
13870
|
+
for (let bar = startBar; bar <= endBar + 1; bar++) {
|
|
13871
|
+
const x2 = bar * stepsPerBar * stepWidth;
|
|
13872
|
+
const screenX = x2;
|
|
13873
|
+
g_header_ctx.beginPath();
|
|
13874
|
+
g_header_ctx.moveTo(screenX, 0);
|
|
13875
|
+
g_header_ctx.lineTo(screenX, HEADER_HEIGHT);
|
|
13876
|
+
g_header_ctx.stroke();
|
|
13877
|
+
if (bar >= 0) {
|
|
13878
|
+
g_header_ctx.textAlign = "left";
|
|
13879
|
+
g_header_ctx.textBaseline = "middle";
|
|
13880
|
+
g_header_ctx.fillText(`${bar + 1}`, screenX + 5, HEADER_HEIGHT / 2);
|
|
13881
|
+
}
|
|
13882
|
+
}
|
|
13883
|
+
g_header_ctx.restore();
|
|
13884
|
+
};
|
|
13885
|
+
const drawGrid = (noteLengthSteps = 1) => {
|
|
13886
|
+
drawKeyboard();
|
|
13887
|
+
drawHeader();
|
|
13888
|
+
g_grid_ctx.clearRect(0, 0, g_grid_canvas.width, g_grid_canvas.height);
|
|
13889
|
+
const {
|
|
13890
|
+
keyHeight,
|
|
13891
|
+
keyCount,
|
|
13892
|
+
stepWidth,
|
|
13893
|
+
stepsPerBar,
|
|
13894
|
+
pitchRangeStart,
|
|
13895
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE,
|
|
13896
|
+
edo = 12
|
|
13897
|
+
} = g_config;
|
|
13898
|
+
const startY = Math.floor(g_draw_offset_y / keyHeight) * keyHeight;
|
|
13899
|
+
const endY = g_draw_offset_y + g_grid_canvas.height;
|
|
13900
|
+
for (let y = startY; y < endY; y += keyHeight) {
|
|
13901
|
+
const rowIndex = keyCount - 1 - y / keyHeight;
|
|
13902
|
+
const units2 = pitchRangeStart + rowIndex * upr;
|
|
13903
|
+
const step = Math.round(
|
|
13904
|
+
(units2 % UNITS_PER_OCTAVE + UNITS_PER_OCTAVE) % UNITS_PER_OCTAVE / upr % edo
|
|
13905
|
+
);
|
|
13906
|
+
const tier = keyTier(step, edo);
|
|
13907
|
+
const isC = step === 0;
|
|
13908
|
+
const octave = Math.floor(units2 / UNITS_PER_OCTAVE) - 1;
|
|
13909
|
+
const isC4Range = octave === 4;
|
|
13910
|
+
const screenY = y - g_draw_offset_y;
|
|
13911
|
+
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";
|
|
13912
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13913
|
+
if (isC4Range) {
|
|
13914
|
+
g_grid_ctx.fillStyle = "rgba(41,173,255,0.05)";
|
|
13915
|
+
g_grid_ctx.fillRect(0, screenY, g_grid_canvas.width, keyHeight);
|
|
13916
|
+
}
|
|
13917
|
+
g_grid_ctx.beginPath();
|
|
13918
|
+
g_grid_ctx.strokeStyle = isC ? "#3d405b" : "#1a1d30";
|
|
13919
|
+
g_grid_ctx.lineWidth = 1;
|
|
13920
|
+
const lineY = screenY + keyHeight;
|
|
13921
|
+
g_grid_ctx.moveTo(0, lineY);
|
|
13922
|
+
g_grid_ctx.lineTo(g_grid_canvas.width, lineY);
|
|
13923
|
+
g_grid_ctx.stroke();
|
|
13924
|
+
}
|
|
13925
|
+
const gridStep = noteLengthSteps || 48;
|
|
13926
|
+
const startX = Math.floor(g_draw_offset_x / (stepWidth * gridStep)) * stepWidth * gridStep;
|
|
13927
|
+
const endX = g_draw_offset_x + g_grid_canvas.width;
|
|
13928
|
+
const lineStep = stepWidth * gridStep;
|
|
13929
|
+
for (let x2 = startX; x2 <= endX; x2 += lineStep) {
|
|
13930
|
+
const step = x2 / stepWidth;
|
|
13931
|
+
const isBarLine = step % stepsPerBar === 0;
|
|
13932
|
+
const isNoteLine = step % gridStep === 0;
|
|
13933
|
+
const screenX = x2 - g_draw_offset_x;
|
|
13934
|
+
g_grid_ctx.beginPath();
|
|
13935
|
+
g_grid_ctx.strokeStyle = isBarLine ? "#3d405b" : isNoteLine ? "#242840" : "#1a1d30";
|
|
13936
|
+
g_grid_ctx.lineWidth = isBarLine ? 2 : 1;
|
|
13937
|
+
g_grid_ctx.moveTo(screenX, 0);
|
|
13938
|
+
g_grid_ctx.lineTo(screenX, g_grid_canvas.height);
|
|
13939
|
+
g_grid_ctx.stroke();
|
|
13940
|
+
}
|
|
13941
|
+
};
|
|
13942
|
+
const drawNotes = (notes, color = [59, 130, 246, 1], isActive = true) => {
|
|
13943
|
+
const {
|
|
13944
|
+
keyHeight,
|
|
13945
|
+
stepWidth,
|
|
13946
|
+
keyCount,
|
|
13947
|
+
pitchRangeStart,
|
|
13948
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13949
|
+
} = g_config;
|
|
13950
|
+
const canvasWidth = g_grid_canvas.width;
|
|
13951
|
+
const canvasHeight = g_grid_canvas.height;
|
|
13952
|
+
const [r, g, b, a] = color;
|
|
13953
|
+
for (const note of notes) {
|
|
13954
|
+
const logicalX = note.startStep * stepWidth;
|
|
13955
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
13956
|
+
const logicalY = yIndex * keyHeight;
|
|
13957
|
+
const w = note.durationSteps * stepWidth;
|
|
13958
|
+
const h = keyHeight;
|
|
13959
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
13960
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
13961
|
+
if (renderX + w < 0 || renderX > canvasWidth) continue;
|
|
13962
|
+
if (renderY + h < 0 || renderY > canvasHeight) continue;
|
|
13963
|
+
if (isActive) {
|
|
13964
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.6 + note.velocity / 127 * 0.4 : 1;
|
|
13965
|
+
const finalOpacity = a * velocityOpacity;
|
|
13966
|
+
g_grid_ctx.fillStyle = `rgba(${r},${g},${b},${finalOpacity})`;
|
|
13967
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13968
|
+
if (w >= 4 && h >= 4) {
|
|
13969
|
+
g_grid_ctx.fillStyle = "rgba(255,255,255,0.4)";
|
|
13970
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, 1);
|
|
13971
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, 1, h - 2);
|
|
13972
|
+
g_grid_ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
13973
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + h - 2, w - 2, 1);
|
|
13974
|
+
g_grid_ctx.fillRect(renderX + w - 2, renderY + 1, 1, h - 2);
|
|
13975
|
+
}
|
|
13976
|
+
} else {
|
|
13977
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.7 + note.velocity / 127 * 0.3 : 1;
|
|
13978
|
+
const finalOpacity = Math.min(0.25, a * 0.22) * velocityOpacity;
|
|
13979
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
13980
|
+
const ghostR = Math.round(r * 0.6 + gray * 0.4);
|
|
13981
|
+
const ghostG = Math.round(g * 0.6 + gray * 0.4);
|
|
13982
|
+
const ghostB = Math.round(b * 0.6 + gray * 0.4);
|
|
13983
|
+
g_grid_ctx.fillStyle = `rgba(${ghostR},${ghostG},${ghostB},${finalOpacity})`;
|
|
13984
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
13985
|
+
}
|
|
13986
|
+
}
|
|
13987
|
+
};
|
|
13988
|
+
const drawNoteLyrics = (notes, syllables) => {
|
|
13989
|
+
if (syllables.length === 0) return;
|
|
13990
|
+
const {
|
|
13991
|
+
keyHeight,
|
|
13992
|
+
stepWidth,
|
|
13993
|
+
keyCount,
|
|
13994
|
+
pitchRangeStart,
|
|
13995
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
13996
|
+
} = g_config;
|
|
13997
|
+
if (keyHeight < 7) return;
|
|
13998
|
+
const fontSize = Math.min(12, Math.floor(keyHeight * 0.85));
|
|
13999
|
+
const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
|
|
14000
|
+
const count = Math.min(sorted.length, syllables.length);
|
|
14001
|
+
g_grid_ctx.save();
|
|
14002
|
+
g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
|
|
14003
|
+
g_grid_ctx.textAlign = "left";
|
|
14004
|
+
g_grid_ctx.textBaseline = "middle";
|
|
14005
|
+
g_grid_ctx.lineWidth = 3;
|
|
14006
|
+
g_grid_ctx.lineJoin = "round";
|
|
14007
|
+
for (let i2 = 0; i2 < count; i2++) {
|
|
14008
|
+
const kana = syllables[i2];
|
|
14009
|
+
if (!kana) continue;
|
|
14010
|
+
const note = sorted[i2];
|
|
14011
|
+
const renderX = note.startStep * stepWidth - g_draw_offset_x;
|
|
14012
|
+
const renderY = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
|
|
14013
|
+
const w = note.durationSteps * stepWidth;
|
|
14014
|
+
if (w < 6) continue;
|
|
14015
|
+
if (renderX + w < 0 || renderX > g_grid_canvas.width) continue;
|
|
14016
|
+
if (renderY + keyHeight < 0 || renderY > g_grid_canvas.height) continue;
|
|
14017
|
+
const textX = renderX + 2;
|
|
14018
|
+
const textY = renderY + keyHeight / 2;
|
|
14019
|
+
g_grid_ctx.save();
|
|
14020
|
+
g_grid_ctx.beginPath();
|
|
14021
|
+
g_grid_ctx.rect(renderX + 1, renderY + 1, w - 2, keyHeight - 2);
|
|
14022
|
+
g_grid_ctx.clip();
|
|
14023
|
+
g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
|
|
14024
|
+
g_grid_ctx.strokeText(kana, textX, textY);
|
|
14025
|
+
g_grid_ctx.fillStyle = "#fff1e8";
|
|
14026
|
+
g_grid_ctx.fillText(kana, textX, textY);
|
|
14027
|
+
g_grid_ctx.restore();
|
|
14028
|
+
}
|
|
14029
|
+
g_grid_ctx.restore();
|
|
14030
|
+
};
|
|
14031
|
+
const drawSelectionRect = (rect) => {
|
|
14032
|
+
if (!rect) return;
|
|
14033
|
+
g_grid_ctx.save();
|
|
14034
|
+
g_grid_ctx.strokeStyle = "#ffec27";
|
|
14035
|
+
g_grid_ctx.lineWidth = 2;
|
|
14036
|
+
g_grid_ctx.setLineDash([4, 4]);
|
|
14037
|
+
g_grid_ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
|
14038
|
+
g_grid_ctx.fillStyle = "rgba(255,236,39,0.08)";
|
|
14039
|
+
g_grid_ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
14040
|
+
g_grid_ctx.restore();
|
|
14041
|
+
};
|
|
14042
|
+
const drawSelectedNotes = (notes, selectedIds, baseColor = [59, 130, 246, 1]) => {
|
|
14043
|
+
const {
|
|
14044
|
+
keyHeight,
|
|
14045
|
+
stepWidth,
|
|
14046
|
+
keyCount,
|
|
14047
|
+
pitchRangeStart,
|
|
14048
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
14049
|
+
} = g_config;
|
|
14050
|
+
for (const note of notes) {
|
|
14051
|
+
if (!selectedIds.has(note.id)) continue;
|
|
14052
|
+
const logicalX = note.startStep * stepWidth;
|
|
14053
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14054
|
+
const logicalY = yIndex * keyHeight;
|
|
14055
|
+
const w = note.durationSteps * stepWidth;
|
|
14056
|
+
const h = keyHeight;
|
|
14057
|
+
const renderX = logicalX - g_draw_offset_x;
|
|
14058
|
+
const renderY = logicalY - g_draw_offset_y;
|
|
14059
|
+
const velocityOpacity = note.velocity !== void 0 ? 0.5 + note.velocity / 127 * 0.5 : 1;
|
|
14060
|
+
const [r, g, b, a] = baseColor;
|
|
14061
|
+
const darkenFactor = 1.3;
|
|
14062
|
+
const darkerR = Math.min(255, r * darkenFactor);
|
|
14063
|
+
const darkerG = Math.min(255, g * darkenFactor);
|
|
14064
|
+
const darkerB = Math.min(255, b * darkenFactor);
|
|
14065
|
+
const finalOpacity = a * velocityOpacity;
|
|
14066
|
+
g_grid_ctx.fillStyle = `rgba(${darkerR},${darkerG},${darkerB},${finalOpacity})`;
|
|
14067
|
+
g_grid_ctx.fillRect(renderX + 1, renderY + 1, w - 2, h - 2);
|
|
14068
|
+
}
|
|
14069
|
+
};
|
|
14070
|
+
const getXY = (e) => {
|
|
14071
|
+
const { clientX, clientY } = e;
|
|
14072
|
+
const rect = g_grid_canvas.getBoundingClientRect();
|
|
14073
|
+
const x2 = Math.floor(clientX - rect.left);
|
|
14074
|
+
const y = Math.floor(clientY - rect.top);
|
|
14075
|
+
return [x2, y, e.buttons];
|
|
14076
|
+
};
|
|
14077
|
+
const getGridPosition = (e) => {
|
|
14078
|
+
const [x2, y] = getXY(e);
|
|
14079
|
+
const {
|
|
14080
|
+
keyCount,
|
|
14081
|
+
pitchRangeStart,
|
|
14082
|
+
keyHeight,
|
|
14083
|
+
stepWidth,
|
|
14084
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
14085
|
+
} = g_config;
|
|
14086
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
14087
|
+
const absoluteY = y + g_draw_offset_y;
|
|
14088
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
14089
|
+
const pitch = units(pitchRangeStart + (keyCount - 1 - yIndex) * upr);
|
|
14090
|
+
return { step, pitch, x: x2, y };
|
|
14091
|
+
};
|
|
14092
|
+
const onClick = (callback) => {
|
|
14093
|
+
g_grid_canvas.addEventListener(
|
|
14094
|
+
"click",
|
|
14095
|
+
(e) => {
|
|
14096
|
+
const [x2, y] = getXY(e);
|
|
14097
|
+
const {
|
|
14098
|
+
keyCount,
|
|
14099
|
+
pitchRangeStart,
|
|
14100
|
+
keyHeight,
|
|
14101
|
+
stepWidth,
|
|
14102
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
14103
|
+
} = g_config;
|
|
14104
|
+
const step = Math.floor((x2 + g_draw_offset_x) / stepWidth);
|
|
14105
|
+
const absoluteY = y + g_draw_offset_y;
|
|
14106
|
+
const yIndex = Math.floor(absoluteY / keyHeight);
|
|
14107
|
+
const pitch = units(pitchRangeStart + (keyCount - 1 - yIndex) * upr);
|
|
14108
|
+
if (pitch >= pitchRangeStart && pitch < pitchRangeStart + keyCount) {
|
|
14109
|
+
requestAnimationFrame(() => callback(step, pitch));
|
|
14110
|
+
}
|
|
14111
|
+
},
|
|
14112
|
+
{ passive: true }
|
|
14113
|
+
);
|
|
14114
|
+
g_grid_canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
14115
|
+
};
|
|
14116
|
+
const setDrawOffset = (x2, y) => {
|
|
14117
|
+
g_draw_offset_x = x2;
|
|
14118
|
+
g_draw_offset_y = y;
|
|
14119
|
+
drawKeyboard();
|
|
14120
|
+
drawHeader();
|
|
14121
|
+
};
|
|
14122
|
+
setup();
|
|
14123
|
+
return {
|
|
14124
|
+
getRenderConfig,
|
|
14125
|
+
setBackgroundActive,
|
|
14126
|
+
getDrawOffset,
|
|
14127
|
+
getGridCanvas,
|
|
14128
|
+
getGridContext,
|
|
14129
|
+
getHeaderCanvas,
|
|
14130
|
+
drawKeyboard,
|
|
14131
|
+
drawHeader,
|
|
14132
|
+
drawGrid,
|
|
14133
|
+
drawNotes,
|
|
14134
|
+
drawNoteLyrics,
|
|
14135
|
+
drawSelectionRect,
|
|
14136
|
+
drawSelectedNotes,
|
|
14137
|
+
getXY,
|
|
14138
|
+
getGridPosition,
|
|
14139
|
+
onClick,
|
|
14140
|
+
setDrawOffset,
|
|
14141
|
+
destroy: () => {
|
|
14142
|
+
mountTarget.innerHTML = "";
|
|
14143
|
+
}
|
|
14144
|
+
};
|
|
14145
|
+
};
|
|
14146
|
+
|
|
13756
14147
|
// src/daw.ts
|
|
13757
14148
|
var CHORD_INFO_HTML2 = `
|
|
13758
14149
|
<div class="dtm-modal-body-content">
|
|
@@ -14039,6 +14430,27 @@ var MIDI_INFO_HTML = `
|
|
|
14039
14430
|
<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
14431
|
</div>
|
|
14041
14432
|
`;
|
|
14433
|
+
var EDO_INFO_HTML = `
|
|
14434
|
+
<div class="dtm-modal-section">
|
|
14435
|
+
<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>
|
|
14436
|
+
<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>
|
|
14437
|
+
<p style="margin-top:8px;"><strong>31\u5E73\u5747\u5F8B\u306E\u7279\u5FB4</strong></p>
|
|
14438
|
+
<ul>
|
|
14439
|
+
<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>
|
|
14440
|
+
<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>
|
|
14441
|
+
<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>
|
|
14442
|
+
</ul>
|
|
14443
|
+
<p style="margin-top:8px;"><strong>MML\u3067\u306E\u66F8\u304D\u65B9</strong></p>
|
|
14444
|
+
<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>
|
|
14445
|
+
<ul>
|
|
14446
|
+
<li><code>#</code> \u2026 \u534A\u97F3\u4E0A\u3052\uFF082\u5EA6\uFF09\u3000<code>-</code> \u2026 \u534A\u97F3\u4E0B\u3052\uFF082\u5EA6\uFF09</li>
|
|
14447
|
+
<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>
|
|
14448
|
+
</ul>
|
|
14449
|
+
<p>\u4F8B: <code>#edo=31 @0 o4 c c+ c# d- d_ d;</code></p>
|
|
14450
|
+
<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>
|
|
14451
|
+
<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>
|
|
14452
|
+
</div>
|
|
14453
|
+
`;
|
|
14042
14454
|
var KOE_INFO_HTML = `
|
|
14043
14455
|
<div class="dtm-modal-body-content">
|
|
14044
14456
|
<h4>1. UTAU\u97F3\u6E90\u3092 .koe \u306B\u5909\u63DB\u3059\u308B</h4>
|
|
@@ -14256,7 +14668,7 @@ var AUTO_ROLE_BASE_VOLUME = {
|
|
|
14256
14668
|
chord: 72
|
|
14257
14669
|
};
|
|
14258
14670
|
var computeAutoRoleStats = (notes) => {
|
|
14259
|
-
const avgPitch = notes.reduce((s, n) => s + n.
|
|
14671
|
+
const avgPitch = notes.reduce((s, n) => s + n.pitchUnits, 0) / notes.length;
|
|
14260
14672
|
const avgDur = notes.reduce((s, n) => s + n.durationSteps, 0) / notes.length;
|
|
14261
14673
|
const byStart = /* @__PURE__ */ new Map();
|
|
14262
14674
|
for (const n of notes)
|
|
@@ -14270,8 +14682,9 @@ var computeAutoRoleStats = (notes) => {
|
|
|
14270
14682
|
const notesPerBeat = notes.length / spanBeats;
|
|
14271
14683
|
return { avgPitch, avgDur, avgPoly, maxPoly, notesPerBeat };
|
|
14272
14684
|
};
|
|
14685
|
+
var AUTO_ROLE_BASS_UNITS = 48 * UNITS_PER_SEMITONE;
|
|
14273
14686
|
var classifyTrackRole = (stats) => {
|
|
14274
|
-
if (stats.avgPitch <
|
|
14687
|
+
if (stats.avgPitch < AUTO_ROLE_BASS_UNITS) return "bass";
|
|
14275
14688
|
if (stats.maxPoly >= 2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT)
|
|
14276
14689
|
return "chord";
|
|
14277
14690
|
if (stats.notesPerBeat < 1.2 && stats.avgDur >= AUTO_ROLE_STEPS_PER_BEAT * 1.5)
|
|
@@ -14283,7 +14696,10 @@ var computeAutoRoleVolume = (role, stats) => {
|
|
|
14283
14696
|
if (role === "chord" && stats.avgPoly > 1) {
|
|
14284
14697
|
vol /= Math.sqrt(stats.avgPoly);
|
|
14285
14698
|
} else if (role === "bass") {
|
|
14286
|
-
const depthSemitones = Math.max(
|
|
14699
|
+
const depthSemitones = Math.max(
|
|
14700
|
+
0,
|
|
14701
|
+
(AUTO_ROLE_BASS_UNITS - stats.avgPitch) / UNITS_PER_SEMITONE
|
|
14702
|
+
);
|
|
14287
14703
|
vol += Math.min(12, depthSemitones * 0.5);
|
|
14288
14704
|
}
|
|
14289
14705
|
return clamp5(Math.round(vol), 1, 127);
|
|
@@ -14375,17 +14791,48 @@ var mountDAW = (target, options = {}) => {
|
|
|
14375
14791
|
refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
|
|
14376
14792
|
const renderConfig = {
|
|
14377
14793
|
stepsPerBar: 192,
|
|
14378
|
-
keyCount:
|
|
14379
|
-
pitchRangeStart:
|
|
14794
|
+
keyCount: KEY_COUNT,
|
|
14795
|
+
pitchRangeStart: PITCH_RANGE_START,
|
|
14796
|
+
unitsPerRow: unitsPerRow(12),
|
|
14797
|
+
edo: 12,
|
|
14380
14798
|
keyHeight: BASE_KEY_HEIGHT,
|
|
14381
14799
|
stepWidth: BASE_STEP_WIDTH * 2
|
|
14382
14800
|
// zoom100% 相当
|
|
14383
14801
|
};
|
|
14802
|
+
let renderer;
|
|
14384
14803
|
let zoomX = 100;
|
|
14385
14804
|
let zoomY = 100;
|
|
14386
14805
|
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
14387
14806
|
let masterVolume = options.masterVolume ?? 50;
|
|
14388
|
-
|
|
14807
|
+
const applyMasterVolume = (volume) => {
|
|
14808
|
+
masterVolume = clamp5(Math.round(volume), 0, 100);
|
|
14809
|
+
refs.masterVolume.value = String(masterVolume);
|
|
14810
|
+
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
14811
|
+
options.singingVoices?.setVolume(masterVolume / 100);
|
|
14812
|
+
};
|
|
14813
|
+
applyMasterVolume(masterVolume);
|
|
14814
|
+
const snapToEdoGrid = (u) => {
|
|
14815
|
+
const upr = renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE;
|
|
14816
|
+
return units(Math.round(u / upr) * upr);
|
|
14817
|
+
};
|
|
14818
|
+
const applyEdo = (edo, opts) => {
|
|
14819
|
+
const next = edo === 31 ? 31 : 12;
|
|
14820
|
+
if (renderConfig.edo === next) return;
|
|
14821
|
+
const upr = unitsPerRow(next);
|
|
14822
|
+
renderConfig.edo = next;
|
|
14823
|
+
renderConfig.unitsPerRow = upr;
|
|
14824
|
+
renderConfig.keyCount = keyCountFor(next);
|
|
14825
|
+
if (!opts?.snap) return;
|
|
14826
|
+
for (const t of trackStates) {
|
|
14827
|
+
t.core.beginBatch();
|
|
14828
|
+
for (const note of [...t.core.getNotes()]) {
|
|
14829
|
+
const snapped = units(Math.round(note.pitchUnits / upr) * upr);
|
|
14830
|
+
if (snapped !== note.pitchUnits)
|
|
14831
|
+
t.core.moveNote(note.id, note.startStep, snapped);
|
|
14832
|
+
}
|
|
14833
|
+
t.core.endBatch();
|
|
14834
|
+
}
|
|
14835
|
+
};
|
|
14389
14836
|
let reverbAmount = options.reverbAmount ?? 0;
|
|
14390
14837
|
let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
|
|
14391
14838
|
let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
|
|
@@ -14443,8 +14890,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14443
14890
|
let snapGridSteps = 12;
|
|
14444
14891
|
const gridLineSteps = 48;
|
|
14445
14892
|
let currentOffsetX = 0;
|
|
14446
|
-
const _initPitch = options.initialScrollPitch ?? 48;
|
|
14447
|
-
let currentOffsetY = (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215;
|
|
14893
|
+
const _initPitch = pitchV1ToUnits(options.initialScrollPitch ?? 48);
|
|
14894
|
+
let currentOffsetY = (renderConfig.keyCount - 1 - (_initPitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - 215;
|
|
14448
14895
|
let playStartStep = 0;
|
|
14449
14896
|
let isSolo = false;
|
|
14450
14897
|
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
@@ -14539,21 +14986,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
14539
14986
|
if (!ready) return;
|
|
14540
14987
|
if (!suppressPatch && options.onNotesPatch) {
|
|
14541
14988
|
const prevByKey = new Map(
|
|
14542
|
-
prevNotes.map((n) => [`${n.startStep}_${n.
|
|
14989
|
+
prevNotes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14543
14990
|
);
|
|
14544
14991
|
const currByKey = new Map(
|
|
14545
|
-
notes.map((n) => [`${n.startStep}_${n.
|
|
14992
|
+
notes.map((n) => [`${n.startStep}_${n.pitchUnits}`, n])
|
|
14546
14993
|
);
|
|
14547
14994
|
const added = notes.filter((n) => {
|
|
14548
|
-
const prev = prevByKey.get(
|
|
14995
|
+
const prev = prevByKey.get(
|
|
14996
|
+
`${n.startStep}_${n.pitchUnits}`
|
|
14997
|
+
);
|
|
14549
14998
|
return !prev || prev.durationSteps !== n.durationSteps || prev.velocity !== n.velocity;
|
|
14550
14999
|
}).map((n) => ({
|
|
14551
15000
|
startStep: n.startStep,
|
|
14552
|
-
|
|
15001
|
+
pitchUnits: n.pitchUnits,
|
|
14553
15002
|
durationSteps: n.durationSteps,
|
|
14554
15003
|
velocity: n.velocity
|
|
14555
15004
|
}));
|
|
14556
|
-
const removed = prevNotes.filter(
|
|
15005
|
+
const removed = prevNotes.filter(
|
|
15006
|
+
(n) => !currByKey.has(`${n.startStep}_${n.pitchUnits}`)
|
|
15007
|
+
).map((n) => ({
|
|
15008
|
+
startStep: n.startStep,
|
|
15009
|
+
pitchUnits: n.pitchUnits
|
|
15010
|
+
}));
|
|
14557
15011
|
if (added.length > 0 || removed.length > 0) {
|
|
14558
15012
|
options.onNotesPatch(config.id, added, removed);
|
|
14559
15013
|
}
|
|
@@ -14563,7 +15017,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14563
15017
|
updateUndoRedo();
|
|
14564
15018
|
}
|
|
14565
15019
|
},
|
|
14566
|
-
config.volume
|
|
15020
|
+
config.volume,
|
|
15021
|
+
() => renderConfig
|
|
14567
15022
|
),
|
|
14568
15023
|
volume: config.volume,
|
|
14569
15024
|
savedChordInput: "",
|
|
@@ -14658,18 +15113,18 @@ var mountDAW = (target, options = {}) => {
|
|
|
14658
15113
|
return (lastNoteMeasure + 2) * renderConfig.stepsPerBar;
|
|
14659
15114
|
};
|
|
14660
15115
|
const getMaxOffsetX = () => {
|
|
14661
|
-
const canvas = getGridCanvas();
|
|
15116
|
+
const canvas = renderer.getGridCanvas();
|
|
14662
15117
|
const maxNoteStep = getMaxNoteStep();
|
|
14663
15118
|
const totalContentWidth = maxNoteStep * renderConfig.stepWidth;
|
|
14664
15119
|
return Math.max(0, totalContentWidth - canvas.width);
|
|
14665
15120
|
};
|
|
14666
15121
|
const getMaxOffsetY = () => {
|
|
14667
15122
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
14668
|
-
return Math.max(0, totalHeight - getGridCanvas().height);
|
|
15123
|
+
return Math.max(0, totalHeight - renderer.getGridCanvas().height);
|
|
14669
15124
|
};
|
|
14670
15125
|
const drawStartLine = () => {
|
|
14671
|
-
const ctx = getGridContext();
|
|
14672
|
-
const canvas = getGridCanvas();
|
|
15126
|
+
const ctx = renderer.getGridContext();
|
|
15127
|
+
const canvas = renderer.getGridCanvas();
|
|
14673
15128
|
if (!ctx) return;
|
|
14674
15129
|
const x2 = playStartStep * renderConfig.stepWidth - currentOffsetX;
|
|
14675
15130
|
if (x2 < -10 || x2 > canvas.width + 10) return;
|
|
@@ -14684,8 +15139,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14684
15139
|
ctx.restore();
|
|
14685
15140
|
};
|
|
14686
15141
|
const drawPlayhead = () => {
|
|
14687
|
-
const ctx = getGridContext();
|
|
14688
|
-
const canvas = getGridCanvas();
|
|
15142
|
+
const ctx = renderer.getGridContext();
|
|
15143
|
+
const canvas = renderer.getGridCanvas();
|
|
14689
15144
|
if (!ctx) return;
|
|
14690
15145
|
const x2 = currentPlayStep * renderConfig.stepWidth - currentOffsetX;
|
|
14691
15146
|
if (x2 < 0 || x2 > canvas.width) return;
|
|
@@ -14699,7 +15154,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14699
15154
|
ctx.restore();
|
|
14700
15155
|
};
|
|
14701
15156
|
const redrawAll = () => {
|
|
14702
|
-
drawGrid(gridLineSteps);
|
|
15157
|
+
renderer.drawGrid(gridLineSteps);
|
|
14703
15158
|
let lyricTargetNotes = null;
|
|
14704
15159
|
let activeTrackState = null;
|
|
14705
15160
|
for (const t of trackStates) {
|
|
@@ -14711,16 +15166,16 @@ var mountDAW = (target, options = {}) => {
|
|
|
14711
15166
|
}
|
|
14712
15167
|
const [r, g, b] = t.config.color;
|
|
14713
15168
|
const notes = t.core.getNotes();
|
|
14714
|
-
drawNotes(notes, [r, g, b, 1], false);
|
|
15169
|
+
renderer.drawNotes(notes, [r, g, b, 1], false);
|
|
14715
15170
|
}
|
|
14716
15171
|
if (activeTrackState) {
|
|
14717
15172
|
const [r, g, b] = activeTrackState.config.color;
|
|
14718
15173
|
const notes = activeTrackState.core.getNotes();
|
|
14719
|
-
drawNotes(notes, [r, g, b, 1], true);
|
|
15174
|
+
renderer.drawNotes(notes, [r, g, b, 1], true);
|
|
14720
15175
|
lyricTargetNotes = notes;
|
|
14721
15176
|
}
|
|
14722
15177
|
if (activeToolMode === "select" && selectionRect) {
|
|
14723
|
-
const ctx = getGridContext();
|
|
15178
|
+
const ctx = renderer.getGridContext();
|
|
14724
15179
|
ctx.save();
|
|
14725
15180
|
ctx.strokeStyle = "#ffec27";
|
|
14726
15181
|
ctx.lineWidth = 2;
|
|
@@ -14743,19 +15198,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
14743
15198
|
if (activeToolMode === "select" && selectedNotes.length > 0) {
|
|
14744
15199
|
const ids = new Set(selectedNotes.map((n) => n.id));
|
|
14745
15200
|
const active = getActive();
|
|
14746
|
-
drawSelectedNotes(active.core.getNotes(), ids, [
|
|
15201
|
+
renderer.drawSelectedNotes(active.core.getNotes(), ids, [
|
|
14747
15202
|
...active.config.color,
|
|
14748
15203
|
1
|
|
14749
15204
|
]);
|
|
14750
15205
|
}
|
|
14751
15206
|
if (lyricTargetNotes)
|
|
14752
|
-
drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
15207
|
+
renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
|
|
14753
15208
|
drawStartLine();
|
|
14754
15209
|
if (playbackState === "playing") drawPlayhead();
|
|
14755
15210
|
updateScrollbars();
|
|
14756
15211
|
};
|
|
14757
15212
|
const updateScrollbars = () => {
|
|
14758
|
-
const canvas = getGridCanvas();
|
|
15213
|
+
const canvas = renderer.getGridCanvas();
|
|
14759
15214
|
const maxOffsetX = getMaxOffsetX();
|
|
14760
15215
|
const sbW = refs.hScroll.clientWidth;
|
|
14761
15216
|
if (maxOffsetX <= 0) {
|
|
@@ -14839,7 +15294,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14839
15294
|
const x2 = clamp5(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
14840
15295
|
const ratio = x2 / (rect.width - thumbW);
|
|
14841
15296
|
currentOffsetX = clamp5(ratio * maxOffsetX, 0, maxOffsetX);
|
|
14842
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15297
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14843
15298
|
redrawAll();
|
|
14844
15299
|
};
|
|
14845
15300
|
const moveV = (clientY) => {
|
|
@@ -14850,7 +15305,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14850
15305
|
const y = clamp5(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
14851
15306
|
const ratio = y / (rect.height - thumbH);
|
|
14852
15307
|
currentOffsetY = clamp5(ratio * maxOffset, 0, maxOffset);
|
|
14853
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15308
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14854
15309
|
redrawAll();
|
|
14855
15310
|
};
|
|
14856
15311
|
};
|
|
@@ -14889,8 +15344,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
14889
15344
|
const autoScrollTick = () => {
|
|
14890
15345
|
autoScrollRAF = null;
|
|
14891
15346
|
if (!isSelecting || !lastMoveEvent) return;
|
|
14892
|
-
const canvas = getGridCanvas();
|
|
14893
|
-
const { x: x2, y } = getGridPosition(lastMoveEvent);
|
|
15347
|
+
const canvas = renderer.getGridCanvas();
|
|
15348
|
+
const { x: x2, y } = renderer.getGridPosition(lastMoveEvent);
|
|
14894
15349
|
const dx = computeEdgeSpeed(x2, canvas.width);
|
|
14895
15350
|
const dy = computeEdgeSpeed(y, canvas.height);
|
|
14896
15351
|
if (dx !== 0 || dy !== 0) {
|
|
@@ -14898,7 +15353,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14898
15353
|
const maxOffsetY = getMaxOffsetY();
|
|
14899
15354
|
currentOffsetX = clamp5(currentOffsetX + dx, 0, maxOffsetX);
|
|
14900
15355
|
currentOffsetY = clamp5(currentOffsetY + dy, 0, maxOffsetY);
|
|
14901
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15356
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
14902
15357
|
onPointerMove(lastMoveEvent);
|
|
14903
15358
|
}
|
|
14904
15359
|
if (isSelecting) {
|
|
@@ -14919,11 +15374,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
14919
15374
|
};
|
|
14920
15375
|
const findActiveNoteAt = (x2, y, margin = 0) => {
|
|
14921
15376
|
const active = getActive();
|
|
14922
|
-
const {
|
|
14923
|
-
|
|
15377
|
+
const {
|
|
15378
|
+
stepWidth,
|
|
15379
|
+
keyHeight,
|
|
15380
|
+
keyCount,
|
|
15381
|
+
pitchRangeStart,
|
|
15382
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15383
|
+
} = renderConfig;
|
|
15384
|
+
const offset = renderer.getDrawOffset();
|
|
14924
15385
|
for (const note of active.core.getNotes()) {
|
|
14925
15386
|
const logicalX = note.startStep * stepWidth;
|
|
14926
|
-
const yIndex = keyCount - 1 - (note.
|
|
15387
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
14927
15388
|
const logicalY = yIndex * keyHeight;
|
|
14928
15389
|
const w = note.durationSteps * stepWidth;
|
|
14929
15390
|
const renderX = logicalX - offset.x;
|
|
@@ -14936,7 +15397,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14936
15397
|
const hasNoteAt = (step, pitch, excludeId) => {
|
|
14937
15398
|
const active = getActive();
|
|
14938
15399
|
return active.core.getNotes().some(
|
|
14939
|
-
(n) => n.id !== excludeId && n.
|
|
15400
|
+
(n) => n.id !== excludeId && n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
14940
15401
|
);
|
|
14941
15402
|
};
|
|
14942
15403
|
const snapToGrid = (duration) => Math.max(
|
|
@@ -14947,7 +15408,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14947
15408
|
const onGridPointerDown = (event) => {
|
|
14948
15409
|
event.preventDefault();
|
|
14949
15410
|
options.onResumeAudio?.();
|
|
14950
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15411
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
14951
15412
|
const active = getActive();
|
|
14952
15413
|
if (activeToolMode === "eraser") {
|
|
14953
15414
|
if (isActiveLocked()) return;
|
|
@@ -14962,7 +15423,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14962
15423
|
selectedOriginal = selectedNotes.map((n) => ({
|
|
14963
15424
|
id: n.id,
|
|
14964
15425
|
startStep: n.startStep,
|
|
14965
|
-
pitch: n.
|
|
15426
|
+
pitch: n.pitchUnits
|
|
14966
15427
|
}));
|
|
14967
15428
|
isSelecting = true;
|
|
14968
15429
|
dragMode = "move";
|
|
@@ -14981,7 +15442,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
14981
15442
|
{
|
|
14982
15443
|
id: clicked.id,
|
|
14983
15444
|
startStep: clicked.startStep,
|
|
14984
|
-
pitch: clicked.
|
|
15445
|
+
pitch: clicked.pitchUnits
|
|
14985
15446
|
}
|
|
14986
15447
|
];
|
|
14987
15448
|
isSelecting = true;
|
|
@@ -14999,9 +15460,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
14999
15460
|
hasDragged = false;
|
|
15000
15461
|
const existing = findActiveNoteAt(x2, y, TOUCH_HIT_MARGIN);
|
|
15001
15462
|
if (existing) {
|
|
15002
|
-
playPreview(existing.
|
|
15463
|
+
playPreview(existing.pitchUnits);
|
|
15003
15464
|
const { stepWidth } = renderConfig;
|
|
15004
|
-
const offset = getDrawOffset();
|
|
15465
|
+
const offset = renderer.getDrawOffset();
|
|
15005
15466
|
const renderX = existing.startStep * stepWidth - offset.x;
|
|
15006
15467
|
const w = existing.durationSteps * stepWidth;
|
|
15007
15468
|
if (x2 >= renderX + w - resizeHandleWidth && x2 <= renderX + w) {
|
|
@@ -15012,17 +15473,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
15012
15473
|
dragOffsetPitch: 0,
|
|
15013
15474
|
startStep: existing.startStep,
|
|
15014
15475
|
durationSteps: existing.durationSteps,
|
|
15015
|
-
lastPreviewPitch: existing.
|
|
15476
|
+
lastPreviewPitch: existing.pitchUnits
|
|
15016
15477
|
};
|
|
15017
15478
|
} else {
|
|
15018
15479
|
dragState = {
|
|
15019
15480
|
noteId: existing.id,
|
|
15020
15481
|
mode: "move",
|
|
15021
15482
|
dragOffsetStep: step - existing.startStep,
|
|
15022
|
-
dragOffsetPitch: pitch - existing.
|
|
15483
|
+
dragOffsetPitch: pitch - existing.pitchUnits,
|
|
15023
15484
|
startStep: existing.startStep,
|
|
15024
15485
|
durationSteps: existing.durationSteps,
|
|
15025
|
-
lastPreviewPitch: existing.
|
|
15486
|
+
lastPreviewPitch: existing.pitchUnits
|
|
15026
15487
|
};
|
|
15027
15488
|
}
|
|
15028
15489
|
suppressClick = true;
|
|
@@ -15033,14 +15494,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15033
15494
|
const newStart = snappedStep;
|
|
15034
15495
|
const newEnd = newStart + currentInsertLength;
|
|
15035
15496
|
const overlapping = active.core.getNotes().some(
|
|
15036
|
-
(n) => n.
|
|
15497
|
+
(n) => n.pitchUnits === pitch && newStart < n.startStep + n.durationSteps && newEnd > n.startStep
|
|
15037
15498
|
);
|
|
15038
15499
|
if (!overlapping) {
|
|
15039
15500
|
active.core.addNote(snappedStep, pitch, {
|
|
15040
15501
|
noteLengthSteps: currentInsertLength
|
|
15041
15502
|
});
|
|
15042
15503
|
playPreview(pitch);
|
|
15043
|
-
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.
|
|
15504
|
+
const newNote = active.core.getNotes().find((n) => n.startStep === snappedStep && n.pitchUnits === pitch);
|
|
15044
15505
|
if (newNote) {
|
|
15045
15506
|
dragState = {
|
|
15046
15507
|
noteId: newNote.id,
|
|
@@ -15049,7 +15510,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15049
15510
|
dragOffsetPitch: 0,
|
|
15050
15511
|
startStep: newNote.startStep,
|
|
15051
15512
|
durationSteps: newNote.durationSteps,
|
|
15052
|
-
lastPreviewPitch: newNote.
|
|
15513
|
+
lastPreviewPitch: newNote.pitchUnits
|
|
15053
15514
|
};
|
|
15054
15515
|
hasDragged = true;
|
|
15055
15516
|
}
|
|
@@ -15060,12 +15521,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
15060
15521
|
const active = getActive();
|
|
15061
15522
|
if (activeToolMode === "pen") {
|
|
15062
15523
|
if (!dragState) return;
|
|
15063
|
-
const { step, pitch } = getGridPosition(event);
|
|
15524
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15064
15525
|
hasDragged = true;
|
|
15065
15526
|
if (dragState.mode === "move") {
|
|
15066
15527
|
const nextStart = step - dragState.dragOffsetStep;
|
|
15067
15528
|
const snappedStart = Math.round(nextStart / snapGridSteps) * snapGridSteps;
|
|
15068
|
-
const nextPitch = pitch - dragState.dragOffsetPitch;
|
|
15529
|
+
const nextPitch = units(pitch - dragState.dragOffsetPitch);
|
|
15069
15530
|
if (hasNoteAt(snappedStart, nextPitch, dragState.noteId)) return;
|
|
15070
15531
|
active.core.moveNote(dragState.noteId, snappedStart, nextPitch);
|
|
15071
15532
|
if (nextPitch !== dragState.lastPreviewPitch) {
|
|
@@ -15084,7 +15545,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15084
15545
|
}
|
|
15085
15546
|
if (activeToolMode === "select" && isSelecting && selectionStart) {
|
|
15086
15547
|
ensureAutoScroll(event);
|
|
15087
|
-
const { x: x2, y, step, pitch } = getGridPosition(event);
|
|
15548
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(event);
|
|
15088
15549
|
if (dragMode === "rect") {
|
|
15089
15550
|
const rect = {
|
|
15090
15551
|
x: Math.min(x2, selectionStart.x),
|
|
@@ -15093,11 +15554,17 @@ var mountDAW = (target, options = {}) => {
|
|
|
15093
15554
|
height: Math.abs(y - selectionStart.y)
|
|
15094
15555
|
};
|
|
15095
15556
|
selectionRect = rect;
|
|
15096
|
-
const {
|
|
15097
|
-
|
|
15557
|
+
const {
|
|
15558
|
+
stepWidth,
|
|
15559
|
+
keyHeight,
|
|
15560
|
+
keyCount,
|
|
15561
|
+
pitchRangeStart,
|
|
15562
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
15563
|
+
} = renderConfig;
|
|
15564
|
+
const offset = renderer.getDrawOffset();
|
|
15098
15565
|
selectedNotes = active.core.getNotes().filter((note) => {
|
|
15099
15566
|
const logicalX = note.startStep * stepWidth;
|
|
15100
|
-
const yIndex = keyCount - 1 - (note.
|
|
15567
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
15101
15568
|
const logicalY = yIndex * keyHeight;
|
|
15102
15569
|
const nx = logicalX - offset.x;
|
|
15103
15570
|
const ny = logicalY - offset.y;
|
|
@@ -15115,8 +15582,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
15115
15582
|
for (const note of selectedNotes) {
|
|
15116
15583
|
const orig = selectedOriginal.find((o) => o.id === note.id);
|
|
15117
15584
|
if (!orig) continue;
|
|
15118
|
-
const newPitch = orig.pitch + deltaPitch;
|
|
15119
|
-
if (newPitch >=
|
|
15585
|
+
const newPitch = units(orig.pitch + deltaPitch);
|
|
15586
|
+
if (newPitch >= PITCH_RANGE_START && newPitch <= PITCH_RANGE_END)
|
|
15120
15587
|
active.core.moveNote(
|
|
15121
15588
|
note.id,
|
|
15122
15589
|
orig.startStep + snappedDelta,
|
|
@@ -15127,7 +15594,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15127
15594
|
const grab = selectedNotes[0];
|
|
15128
15595
|
const orig = selectedOriginal.find((o) => o.id === grab.id);
|
|
15129
15596
|
if (orig) {
|
|
15130
|
-
const newGrab = orig.pitch + deltaPitch;
|
|
15597
|
+
const newGrab = units(orig.pitch + deltaPitch);
|
|
15131
15598
|
if (newGrab !== lastMultiPreviewPitch && newGrab >= 0 && newGrab < 128) {
|
|
15132
15599
|
lastMultiPreviewPitch = newGrab;
|
|
15133
15600
|
playPreview(newGrab);
|
|
@@ -15264,22 +15731,23 @@ var mountDAW = (target, options = {}) => {
|
|
|
15264
15731
|
refs.bgOpacityRow.classList.add("dtm-hidden");
|
|
15265
15732
|
}
|
|
15266
15733
|
refs.bgRemoveBtn.classList.toggle("dtm-hidden", !blob2);
|
|
15267
|
-
setBackgroundActive(!!blob2);
|
|
15734
|
+
renderer.setBackgroundActive(!!blob2);
|
|
15268
15735
|
redrawAll();
|
|
15269
15736
|
};
|
|
15270
15737
|
const setupCanvas = () => {
|
|
15271
15738
|
const w = refs.rollContainer.clientWidth || 800;
|
|
15272
15739
|
const h = refs.rollContainer.clientHeight || 450;
|
|
15273
|
-
|
|
15274
|
-
|
|
15740
|
+
renderer?.destroy();
|
|
15741
|
+
renderer = createRenderer(refs.wrapper, w, h, renderConfig);
|
|
15742
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
15275
15743
|
gridCanvas.addEventListener("pointerdown", onGridPointerDown);
|
|
15276
15744
|
gridCanvas.addEventListener("dblclick", (event) => {
|
|
15277
15745
|
event.preventDefault();
|
|
15278
15746
|
if (isActiveLocked()) return;
|
|
15279
|
-
const { step, pitch } = getGridPosition(event);
|
|
15747
|
+
const { step, pitch } = renderer.getGridPosition(event);
|
|
15280
15748
|
const active = getActive();
|
|
15281
15749
|
const note = active.core.getNotes().find(
|
|
15282
|
-
(n) => n.
|
|
15750
|
+
(n) => n.pitchUnits === pitch && step >= n.startStep && step < n.startStep + n.durationSteps
|
|
15283
15751
|
);
|
|
15284
15752
|
if (note) active.core.deleteNoteById(note.id);
|
|
15285
15753
|
});
|
|
@@ -15297,7 +15765,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15297
15765
|
0,
|
|
15298
15766
|
getMaxOffsetX()
|
|
15299
15767
|
);
|
|
15300
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15768
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15301
15769
|
redrawAll();
|
|
15302
15770
|
},
|
|
15303
15771
|
{ passive: false }
|
|
@@ -15307,7 +15775,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15307
15775
|
suppressClick = false;
|
|
15308
15776
|
}
|
|
15309
15777
|
});
|
|
15310
|
-
const headerCanvas = getHeaderCanvas();
|
|
15778
|
+
const headerCanvas = renderer.getHeaderCanvas();
|
|
15311
15779
|
headerCanvas.addEventListener("click", (event) => {
|
|
15312
15780
|
if (playbackState === "playing") return;
|
|
15313
15781
|
const rect = headerCanvas.getBoundingClientRect();
|
|
@@ -15323,11 +15791,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15323
15791
|
}
|
|
15324
15792
|
redrawAll();
|
|
15325
15793
|
});
|
|
15326
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15794
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15327
15795
|
redrawAll();
|
|
15328
15796
|
};
|
|
15329
15797
|
const applyZoomX = () => {
|
|
15330
|
-
const canvas = getGridCanvas();
|
|
15798
|
+
const canvas = renderer.getGridCanvas();
|
|
15331
15799
|
const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
|
|
15332
15800
|
renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
|
|
15333
15801
|
refs.zoomXLabel.textContent = `${zoomX}%`;
|
|
@@ -15336,11 +15804,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
15336
15804
|
0,
|
|
15337
15805
|
getMaxOffsetX()
|
|
15338
15806
|
);
|
|
15339
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15807
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15340
15808
|
redrawAll();
|
|
15341
15809
|
};
|
|
15342
15810
|
const applyZoomY = () => {
|
|
15343
|
-
const canvas = getGridCanvas();
|
|
15811
|
+
const canvas = renderer.getGridCanvas();
|
|
15344
15812
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
15345
15813
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
15346
15814
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
@@ -15349,7 +15817,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15349
15817
|
0,
|
|
15350
15818
|
getMaxOffsetY()
|
|
15351
15819
|
);
|
|
15352
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15820
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15353
15821
|
redrawAll();
|
|
15354
15822
|
};
|
|
15355
15823
|
const getViewState = () => ({
|
|
@@ -15361,7 +15829,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
15361
15829
|
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
15362
15830
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
15363
15831
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
15364
|
-
options.onPlayNote?.({
|
|
15832
|
+
options.onPlayNote?.({
|
|
15833
|
+
trackId,
|
|
15834
|
+
pitchUnits: pitch,
|
|
15835
|
+
velocity,
|
|
15836
|
+
volume,
|
|
15837
|
+
when,
|
|
15838
|
+
duration
|
|
15839
|
+
});
|
|
15365
15840
|
};
|
|
15366
15841
|
const sequencer = createSequencer({
|
|
15367
15842
|
getTracks: () => trackStates.map((t) => ({
|
|
@@ -15389,7 +15864,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15389
15864
|
},
|
|
15390
15865
|
onTick: (step) => {
|
|
15391
15866
|
currentPlayStep = step;
|
|
15392
|
-
const canvas = getGridCanvas();
|
|
15867
|
+
const canvas = renderer.getGridCanvas();
|
|
15393
15868
|
const visibleSteps = canvas.width / renderConfig.stepWidth;
|
|
15394
15869
|
const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
|
|
15395
15870
|
if (currentPlayStep > threshold) {
|
|
@@ -15399,7 +15874,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15399
15874
|
0,
|
|
15400
15875
|
getMaxOffsetX()
|
|
15401
15876
|
);
|
|
15402
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15877
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15403
15878
|
}
|
|
15404
15879
|
redrawAll();
|
|
15405
15880
|
},
|
|
@@ -15430,7 +15905,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15430
15905
|
(a, b) => a.startStep - b.startStep
|
|
15431
15906
|
);
|
|
15432
15907
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
15433
|
-
const semis = (lt.octave ?? 0) *
|
|
15908
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
15434
15909
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
15435
15910
|
const notes = [];
|
|
15436
15911
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -15438,7 +15913,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
15438
15913
|
if (n.startStep < fromStep) continue;
|
|
15439
15914
|
notes.push({
|
|
15440
15915
|
syllable: lt.syllables[i2],
|
|
15441
|
-
pitch: n.
|
|
15916
|
+
pitch: units(n.pitchUnits + semis),
|
|
15442
15917
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
15443
15918
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
15444
15919
|
});
|
|
@@ -15481,13 +15956,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
15481
15956
|
}
|
|
15482
15957
|
}
|
|
15483
15958
|
if (playbackState !== "paused") {
|
|
15484
|
-
const canvas = getGridCanvas();
|
|
15959
|
+
const canvas = renderer.getGridCanvas();
|
|
15485
15960
|
currentOffsetX = clamp5(
|
|
15486
15961
|
playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
|
|
15487
15962
|
0,
|
|
15488
15963
|
getMaxOffsetX()
|
|
15489
15964
|
);
|
|
15490
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15965
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
15491
15966
|
}
|
|
15492
15967
|
playbackState = "playing";
|
|
15493
15968
|
sequencer.start(fromStep);
|
|
@@ -16474,6 +16949,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16474
16949
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16475
16950
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16476
16951
|
mode,
|
|
16952
|
+
edo: renderConfig.edo,
|
|
16477
16953
|
trackInstruments: trackInstMeta,
|
|
16478
16954
|
trackCompression: trackCompMeta,
|
|
16479
16955
|
trackWidth: trackWidthMeta,
|
|
@@ -16502,6 +16978,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16502
16978
|
fadeIn: Math.round(fadeInSec * 10),
|
|
16503
16979
|
fadeOut: Math.round(fadeOutSec * 10),
|
|
16504
16980
|
mode,
|
|
16981
|
+
edo: renderConfig.edo,
|
|
16505
16982
|
trackInstruments: trackInstMeta,
|
|
16506
16983
|
trackCompression: trackCompMeta,
|
|
16507
16984
|
trackWidth: trackWidthMeta,
|
|
@@ -16641,19 +17118,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
16641
17118
|
}
|
|
16642
17119
|
}
|
|
16643
17120
|
if (candidateNotes.length === 0) return null;
|
|
16644
|
-
const sum = candidateNotes.reduce((acc, note) => acc + note.
|
|
16645
|
-
return Math.round(sum / candidateNotes.length);
|
|
17121
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitchUnits, 0);
|
|
17122
|
+
return units(Math.round(sum / candidateNotes.length));
|
|
16646
17123
|
};
|
|
16647
17124
|
const centerPitch = (pitch) => {
|
|
16648
|
-
const canvas = getGridCanvas();
|
|
16649
|
-
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
17125
|
+
const canvas = renderer.getGridCanvas();
|
|
17126
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE);
|
|
16650
17127
|
const logicalY = yIndex * renderConfig.keyHeight;
|
|
16651
17128
|
currentOffsetY = clamp5(
|
|
16652
17129
|
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
16653
17130
|
0,
|
|
16654
17131
|
getMaxOffsetY()
|
|
16655
17132
|
);
|
|
16656
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
17133
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
16657
17134
|
};
|
|
16658
17135
|
const clearAll = () => {
|
|
16659
17136
|
for (const t of trackStates) {
|
|
@@ -16711,10 +17188,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
16711
17188
|
if (!meta.drumFont) applyDrumPatternFont(meta.drum);
|
|
16712
17189
|
}
|
|
16713
17190
|
if (meta.volume !== void 0) {
|
|
16714
|
-
|
|
16715
|
-
refs.masterVolume.value = String(meta.volume);
|
|
16716
|
-
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
17191
|
+
applyMasterVolume(meta.volume);
|
|
16717
17192
|
}
|
|
17193
|
+
applyEdo(meta.edo ?? 12);
|
|
17194
|
+
refs.edoSelect.value = String(renderConfig.edo ?? 12);
|
|
16718
17195
|
if (meta.drumVolume !== void 0) {
|
|
16719
17196
|
drumVolume = meta.drumVolume;
|
|
16720
17197
|
refs.drumVolume.value = String(meta.drumVolume);
|
|
@@ -16879,7 +17356,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16879
17356
|
if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
|
|
16880
17357
|
const t = trackStates[p.trackIndex];
|
|
16881
17358
|
if (!t) continue;
|
|
16882
|
-
t.core.addNote(p.startStep, p.
|
|
17359
|
+
t.core.addNote(p.startStep, p.pitchUnits, {
|
|
16883
17360
|
noteLengthSteps: p.durationSteps,
|
|
16884
17361
|
velocity: DEFAULT_VELOCITY
|
|
16885
17362
|
});
|
|
@@ -16896,7 +17373,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16896
17373
|
if (firstPitch !== null) {
|
|
16897
17374
|
centerPitch(firstPitch);
|
|
16898
17375
|
} else {
|
|
16899
|
-
centerPitch(48);
|
|
17376
|
+
centerPitch(pitchV1ToUnits(48));
|
|
16900
17377
|
}
|
|
16901
17378
|
redrawAll();
|
|
16902
17379
|
updateTrackPanel();
|
|
@@ -16914,6 +17391,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
16914
17391
|
const chordTrack = trackStates.find((t) => t.config.id === "chord");
|
|
16915
17392
|
if (!chordTrack) return;
|
|
16916
17393
|
const placements = buildChordPlacements({
|
|
17394
|
+
// 和音は五度連鎖経由で音律の格子へ写す(31平均律では長3度が10度になる)
|
|
17395
|
+
edo: renderConfig.edo,
|
|
16917
17396
|
chordStr: active.savedChordInput,
|
|
16918
17397
|
patternType: active.savedChordPattern,
|
|
16919
17398
|
rootShift: active.savedChordRoot,
|
|
@@ -16923,7 +17402,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16923
17402
|
chordTrack.core.clearNotesWithoutHistory();
|
|
16924
17403
|
chordTrack.core.beginBatch();
|
|
16925
17404
|
for (const p of placements) {
|
|
16926
|
-
chordTrack.core.addNote(p.startStep, p.
|
|
17405
|
+
chordTrack.core.addNote(p.startStep, p.pitchUnits, {
|
|
16927
17406
|
noteLengthSteps: Math.max(1, p.durationSteps),
|
|
16928
17407
|
velocity: p.velocity
|
|
16929
17408
|
});
|
|
@@ -16987,7 +17466,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
16987
17466
|
if (applyActiveOnly && p.trackId !== activeTrackId) continue;
|
|
16988
17467
|
const t = trackStates.find((ts) => ts.config.id === p.trackId);
|
|
16989
17468
|
if (!t) continue;
|
|
16990
|
-
t.core.addNote(p.startStep, p.pitch, {
|
|
17469
|
+
t.core.addNote(p.startStep, snapToEdoGrid(pitchV1ToUnits(p.pitch)), {
|
|
16991
17470
|
noteLengthSteps: p.durationSteps,
|
|
16992
17471
|
velocity: p.velocity
|
|
16993
17472
|
});
|
|
@@ -17004,7 +17483,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17004
17483
|
if (firstPitch !== null) {
|
|
17005
17484
|
centerPitch(firstPitch);
|
|
17006
17485
|
} else {
|
|
17007
|
-
centerPitch(48);
|
|
17486
|
+
centerPitch(pitchV1ToUnits(48));
|
|
17008
17487
|
}
|
|
17009
17488
|
redrawAll();
|
|
17010
17489
|
updateTrackPanel();
|
|
@@ -17068,18 +17547,21 @@ var mountDAW = (target, options = {}) => {
|
|
|
17068
17547
|
}
|
|
17069
17548
|
}, 30);
|
|
17070
17549
|
};
|
|
17071
|
-
const showConfirmModal = (message) => new Promise((resolve) => {
|
|
17550
|
+
const showConfirmModal = (message, opts) => new Promise((resolve) => {
|
|
17551
|
+
const title = opts?.title ?? "\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D";
|
|
17552
|
+
const yes = opts?.yes ?? "\u306F\u3044\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u308B\uFF09";
|
|
17553
|
+
const no = opts?.no ?? "\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09";
|
|
17072
17554
|
const overlay = document.createElement("div");
|
|
17073
17555
|
overlay.className = "dtm-modal-overlay";
|
|
17074
17556
|
overlay.innerHTML = `
|
|
17075
17557
|
<div class="dtm-modal">
|
|
17076
17558
|
<div class="dtm-modal-header">
|
|
17077
|
-
<span class="dtm-modal-title"
|
|
17559
|
+
<span class="dtm-modal-title">${title}</span>
|
|
17078
17560
|
</div>
|
|
17079
17561
|
<div class="dtm-modal-body"><p>${message}</p></div>
|
|
17080
17562
|
<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"
|
|
17563
|
+
<button class="dtm-btn dtm-btn--ghost dtm-confirm-no">${no}</button>
|
|
17564
|
+
<button class="dtm-btn dtm-btn--primary dtm-confirm-yes">${yes}</button>
|
|
17083
17565
|
</div>
|
|
17084
17566
|
</div>`;
|
|
17085
17567
|
const close = (result) => {
|
|
@@ -17187,10 +17669,45 @@ var mountDAW = (target, options = {}) => {
|
|
|
17187
17669
|
});
|
|
17188
17670
|
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
17189
17671
|
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
17672
|
+
refs.edoSelect.addEventListener("change", async () => {
|
|
17673
|
+
const next = Number.parseInt(refs.edoSelect.value, 10) === 31 ? 31 : 12;
|
|
17674
|
+
const prev = renderConfig.edo ?? 12;
|
|
17675
|
+
if (next === prev) return;
|
|
17676
|
+
const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
|
|
17677
|
+
if (next === 12 && prev === 31 && hasNotes) {
|
|
17678
|
+
const okToDrop = await showConfirmModal(
|
|
17679
|
+
"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",
|
|
17680
|
+
{
|
|
17681
|
+
title: "\u97F3\u5F8B\u306E\u78BA\u8A8D",
|
|
17682
|
+
yes: "\u5207\u308A\u66FF\u3048\u308B",
|
|
17683
|
+
no: "\u3084\u3081\u308B"
|
|
17684
|
+
}
|
|
17685
|
+
);
|
|
17686
|
+
if (!okToDrop) {
|
|
17687
|
+
refs.edoSelect.value = String(prev);
|
|
17688
|
+
return;
|
|
17689
|
+
}
|
|
17690
|
+
}
|
|
17691
|
+
overlayDuring(() => {
|
|
17692
|
+
applyEdo(next, { snap: true });
|
|
17693
|
+
if (next === 31 && prev === 12) {
|
|
17694
|
+
zoomY = Math.max(50, Math.round(zoomY / 2 / 25) * 25);
|
|
17695
|
+
} else if (next === 12 && prev === 31) {
|
|
17696
|
+
zoomY = Math.min(200, Math.round(zoomY * 2 / 25) * 25);
|
|
17697
|
+
}
|
|
17698
|
+
applyZoomY();
|
|
17699
|
+
const first = getFirstDetectedPitch();
|
|
17700
|
+
centerPitch(first ?? pitchV1ToUnits(48));
|
|
17701
|
+
redrawAll();
|
|
17702
|
+
updateUndoRedo();
|
|
17703
|
+
notifyViewState();
|
|
17704
|
+
});
|
|
17705
|
+
});
|
|
17706
|
+
refs.edoInfoBtn.addEventListener("click", () => {
|
|
17707
|
+
showModal("\u97F3\u5F8B\u306E\u89E3\u8AAC", EDO_INFO_HTML);
|
|
17708
|
+
});
|
|
17190
17709
|
refs.masterVolume.addEventListener("input", () => {
|
|
17191
|
-
|
|
17192
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17193
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17710
|
+
applyMasterVolume(Number.parseInt(refs.masterVolume.value, 10) || 0);
|
|
17194
17711
|
});
|
|
17195
17712
|
refs.masterComp.addEventListener("input", () => {
|
|
17196
17713
|
masterCompression = Number.parseInt(refs.masterComp.value, 10) || 0;
|
|
@@ -17254,10 +17771,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17254
17771
|
const scale = targetPeak / observedPeakMax;
|
|
17255
17772
|
const suggested = clamp5(Math.round(masterVolume * scale), 10, 100);
|
|
17256
17773
|
if (suggested !== masterVolume) {
|
|
17257
|
-
|
|
17258
|
-
refs.masterVolume.value = String(masterVolume);
|
|
17259
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
17260
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
17774
|
+
applyMasterVolume(suggested);
|
|
17261
17775
|
}
|
|
17262
17776
|
observedPeakMax = 0;
|
|
17263
17777
|
observedPlayMs = 0;
|
|
@@ -17417,7 +17931,8 @@ var mountDAW = (target, options = {}) => {
|
|
|
17417
17931
|
generateRandomPattern(getActive().core, {
|
|
17418
17932
|
stepsPerBar: renderConfig.stepsPerBar,
|
|
17419
17933
|
startStep: playStartStep,
|
|
17420
|
-
pitchRangeStart: renderConfig.pitchRangeStart
|
|
17934
|
+
pitchRangeStart: renderConfig.pitchRangeStart,
|
|
17935
|
+
edo: renderConfig.edo
|
|
17421
17936
|
});
|
|
17422
17937
|
redrawAll();
|
|
17423
17938
|
});
|
|
@@ -17754,7 +18269,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
17754
18269
|
id: 0,
|
|
17755
18270
|
startStep: p.startStep,
|
|
17756
18271
|
durationSteps: p.durationSteps,
|
|
17757
|
-
|
|
18272
|
+
pitchUnits: snapToEdoGrid(pitchV1ToUnits(p.pitch)),
|
|
17758
18273
|
velocity: p.velocity
|
|
17759
18274
|
}));
|
|
17760
18275
|
const mml = refCore.getMMLFromNotes(asNotes, tempo, 100).trim();
|
|
@@ -18209,10 +18724,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18209
18724
|
const newStart = playStartStep + (note.startStep - minStart);
|
|
18210
18725
|
const newEnd = newStart + note.durationSteps;
|
|
18211
18726
|
const overlap = notes.some(
|
|
18212
|
-
(ex) => ex.
|
|
18727
|
+
(ex) => ex.pitchUnits === note.pitchUnits && newStart < ex.startStep + ex.durationSteps && newEnd > ex.startStep
|
|
18213
18728
|
);
|
|
18214
18729
|
if (!overlap)
|
|
18215
|
-
core.addNote(newStart, note.
|
|
18730
|
+
core.addNote(newStart, note.pitchUnits, {
|
|
18216
18731
|
noteLengthSteps: note.durationSteps,
|
|
18217
18732
|
velocity: note.velocity
|
|
18218
18733
|
});
|
|
@@ -18285,13 +18800,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
18285
18800
|
pausedPlayStep = step;
|
|
18286
18801
|
currentPlayStep = step;
|
|
18287
18802
|
playbackState = "paused";
|
|
18288
|
-
const canvas = getGridCanvas();
|
|
18803
|
+
const canvas = renderer.getGridCanvas();
|
|
18289
18804
|
currentOffsetX = clamp5(
|
|
18290
18805
|
step * renderConfig.stepWidth - canvas.width * 0.5,
|
|
18291
18806
|
0,
|
|
18292
18807
|
getMaxOffsetX()
|
|
18293
18808
|
);
|
|
18294
|
-
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18809
|
+
renderer.setDrawOffset(currentOffsetX, currentOffsetY);
|
|
18295
18810
|
updateTransport();
|
|
18296
18811
|
redrawAll();
|
|
18297
18812
|
};
|
|
@@ -18359,16 +18874,10 @@ var mountDAW = (target, options = {}) => {
|
|
|
18359
18874
|
forcePauseAt,
|
|
18360
18875
|
setLoading,
|
|
18361
18876
|
setMasterVolume: (volume) => {
|
|
18362
|
-
|
|
18363
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18364
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18365
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18877
|
+
applyMasterVolume(volume);
|
|
18366
18878
|
},
|
|
18367
18879
|
setVolume: (volume) => {
|
|
18368
|
-
|
|
18369
|
-
refs.masterVolume.value = String(masterVolume);
|
|
18370
|
-
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
18371
|
-
options.singingVoices?.setVolume(masterVolume / 100);
|
|
18880
|
+
applyMasterVolume(volume);
|
|
18372
18881
|
},
|
|
18373
18882
|
setDrumVolume: (volume) => {
|
|
18374
18883
|
drumVolume = clamp5(volume, 0, 100);
|
|
@@ -18411,15 +18920,19 @@ var mountDAW = (target, options = {}) => {
|
|
|
18411
18920
|
suppressPatch = true;
|
|
18412
18921
|
track.core.beginBatch();
|
|
18413
18922
|
for (const n of added) {
|
|
18414
|
-
const existing = track.core.getNotes().find(
|
|
18923
|
+
const existing = track.core.getNotes().find(
|
|
18924
|
+
(e) => e.startStep === n.startStep && e.pitchUnits === n.pitchUnits
|
|
18925
|
+
);
|
|
18415
18926
|
if (existing) track.core.deleteNoteById(existing.id);
|
|
18416
|
-
track.core.addNote(n.startStep, n.
|
|
18927
|
+
track.core.addNote(n.startStep, n.pitchUnits, {
|
|
18417
18928
|
noteLengthSteps: n.durationSteps,
|
|
18418
18929
|
velocity: n.velocity
|
|
18419
18930
|
});
|
|
18420
18931
|
}
|
|
18421
18932
|
for (const r of removed) {
|
|
18422
|
-
const note = track.core.getNotes().find(
|
|
18933
|
+
const note = track.core.getNotes().find(
|
|
18934
|
+
(n) => n.startStep === r.startStep && n.pitchUnits === r.pitchUnits
|
|
18935
|
+
);
|
|
18423
18936
|
if (note) track.core.deleteNoteById(note.id);
|
|
18424
18937
|
}
|
|
18425
18938
|
track.core.endBatch();
|
|
@@ -18461,9 +18974,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
18461
18974
|
if (t.config.id === activeTrackId) updateTrackPanel();
|
|
18462
18975
|
},
|
|
18463
18976
|
noteToCanvas: (step, pitch) => {
|
|
18464
|
-
const canvas = getGridCanvas();
|
|
18977
|
+
const canvas = renderer.getGridCanvas();
|
|
18465
18978
|
const x2 = step * renderConfig.stepWidth - currentOffsetX;
|
|
18466
|
-
const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
|
|
18979
|
+
const y = (renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart) / (renderConfig.unitsPerRow ?? UNITS_PER_SEMITONE)) * renderConfig.keyHeight - currentOffsetY;
|
|
18467
18980
|
const onScreen = x2 >= 0 && x2 <= canvas.width && y >= 0 && y <= canvas.height;
|
|
18468
18981
|
let side = null;
|
|
18469
18982
|
if (!onScreen) {
|
|
@@ -18527,7 +19040,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18527
19040
|
id: id++,
|
|
18528
19041
|
startStep: p.startStep,
|
|
18529
19042
|
durationSteps: p.durationSteps,
|
|
18530
|
-
|
|
19043
|
+
pitchUnits: p.pitchUnits,
|
|
18531
19044
|
velocity: p.velocity
|
|
18532
19045
|
}));
|
|
18533
19046
|
return {
|
|
@@ -18553,7 +19066,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18553
19066
|
(a, b) => a.startStep - b.startStep
|
|
18554
19067
|
);
|
|
18555
19068
|
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
18556
|
-
const semis = (lt.octave ?? 0) *
|
|
19069
|
+
const semis = (lt.octave ?? 0) * UNITS_PER_OCTAVE;
|
|
18557
19070
|
const count = Math.min(sorted.length, lt.syllables.length);
|
|
18558
19071
|
const notes = [];
|
|
18559
19072
|
for (let i2 = 0; i2 < count; i2++) {
|
|
@@ -18561,7 +19074,7 @@ var playSingingMML = async (mml, options = {}) => {
|
|
|
18561
19074
|
if (n.startStep < fromStep) continue;
|
|
18562
19075
|
notes.push({
|
|
18563
19076
|
syllable: lt.syllables[i2],
|
|
18564
|
-
pitch: n.
|
|
19077
|
+
pitch: units(n.pitchUnits + semis),
|
|
18565
19078
|
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
18566
19079
|
durationSec: n.durationSteps * secondsPerStep * gate
|
|
18567
19080
|
});
|
|
@@ -18749,24 +19262,28 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18749
19262
|
config,
|
|
18750
19263
|
noteLengthSteps = 1
|
|
18751
19264
|
} = options;
|
|
18752
|
-
|
|
19265
|
+
const renderer = createRenderer(mountTarget, width, height, config);
|
|
18753
19266
|
let currentNoteLengthSteps = noteLengthSteps;
|
|
18754
19267
|
let selectionRect = null;
|
|
18755
19268
|
let isSelecting = false;
|
|
18756
19269
|
let selectionStart = null;
|
|
18757
19270
|
let selectedNotes = [];
|
|
18758
19271
|
let copiedNotes = [];
|
|
18759
|
-
const core = new MMLCore(
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
19272
|
+
const core = new MMLCore(
|
|
19273
|
+
{
|
|
19274
|
+
onMMLGenerated: handlers.onMMLGenerated,
|
|
19275
|
+
onNotesChanged: (notes) => {
|
|
19276
|
+
handlers.onNotesChanged(notes);
|
|
19277
|
+
}
|
|
19278
|
+
},
|
|
19279
|
+
80,
|
|
19280
|
+
() => config
|
|
19281
|
+
);
|
|
18765
19282
|
const getAddNoteOptions = () => ({
|
|
18766
19283
|
noteLengthSteps: currentNoteLengthSteps
|
|
18767
19284
|
});
|
|
18768
19285
|
let suppressClick = false;
|
|
18769
|
-
onClick((step, pitch) => {
|
|
19286
|
+
renderer.onClick((step, pitch) => {
|
|
18770
19287
|
if (suppressClick) {
|
|
18771
19288
|
suppressClick = false;
|
|
18772
19289
|
return;
|
|
@@ -18778,7 +19295,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18778
19295
|
} else if (mode === "eraser") {
|
|
18779
19296
|
const notes = core.getNotes();
|
|
18780
19297
|
const note = notes.find(
|
|
18781
|
-
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.
|
|
19298
|
+
(n) => n.startStep <= step && step < n.startStep + n.durationSteps && n.pitchUnits === pitch
|
|
18782
19299
|
);
|
|
18783
19300
|
if (note) {
|
|
18784
19301
|
core.deleteNoteById(note.id);
|
|
@@ -18786,17 +19303,23 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18786
19303
|
}
|
|
18787
19304
|
}
|
|
18788
19305
|
});
|
|
18789
|
-
const gridCanvas = getGridCanvas();
|
|
19306
|
+
const gridCanvas = renderer.getGridCanvas();
|
|
18790
19307
|
const resizeHandleWidth = 6;
|
|
18791
19308
|
let dragState = null;
|
|
18792
19309
|
let hasDragged = false;
|
|
18793
19310
|
let lastPreviewPitch = null;
|
|
18794
19311
|
const findNoteAtPosition = (x2, y) => {
|
|
18795
|
-
const {
|
|
18796
|
-
|
|
19312
|
+
const {
|
|
19313
|
+
stepWidth,
|
|
19314
|
+
keyHeight,
|
|
19315
|
+
keyCount,
|
|
19316
|
+
pitchRangeStart,
|
|
19317
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19318
|
+
} = renderer.getRenderConfig();
|
|
19319
|
+
const offset = renderer.getDrawOffset();
|
|
18797
19320
|
for (const note of core.getNotes()) {
|
|
18798
19321
|
const logicalX = note.startStep * stepWidth;
|
|
18799
|
-
const yIndex = keyCount - 1 - (note.
|
|
19322
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18800
19323
|
const logicalY = yIndex * keyHeight;
|
|
18801
19324
|
const w = note.durationSteps * stepWidth;
|
|
18802
19325
|
const h = keyHeight;
|
|
@@ -18810,7 +19333,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18810
19333
|
};
|
|
18811
19334
|
const handlePointerMove = (e) => {
|
|
18812
19335
|
if (core.getToolMode() === "select" && isSelecting && selectionStart) {
|
|
18813
|
-
const { x: x2, y } = getGridPosition(e);
|
|
19336
|
+
const { x: x2, y } = renderer.getGridPosition(e);
|
|
18814
19337
|
const minX = Math.min(x2, selectionStart.x);
|
|
18815
19338
|
const minY = Math.min(y, selectionStart.y);
|
|
18816
19339
|
const width2 = Math.abs(x2 - selectionStart.x);
|
|
@@ -18822,7 +19345,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18822
19345
|
}
|
|
18823
19346
|
if (!dragState) return;
|
|
18824
19347
|
hasDragged = true;
|
|
18825
|
-
const { step, pitch } = getGridPosition(e);
|
|
19348
|
+
const { step, pitch } = renderer.getGridPosition(e);
|
|
18826
19349
|
if (dragState.mode === "move") {
|
|
18827
19350
|
if (dragState.selectedNotes && dragState.selectedNotes.length > 0) {
|
|
18828
19351
|
const noteId = dragState.noteId;
|
|
@@ -18831,10 +19354,10 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18831
19354
|
const nextStart2 = step - dragState.dragOffsetStep;
|
|
18832
19355
|
const nextPitch2 = pitch - dragState.dragOffsetPitch;
|
|
18833
19356
|
const stepDelta = nextStart2 - baseNote.startStep;
|
|
18834
|
-
const pitchDelta = nextPitch2 - baseNote.
|
|
19357
|
+
const pitchDelta = nextPitch2 - baseNote.pitchUnits;
|
|
18835
19358
|
for (const note of dragState.selectedNotes) {
|
|
18836
19359
|
const newStart = note.startStep + stepDelta;
|
|
18837
|
-
const newPitch = note.
|
|
19360
|
+
const newPitch = units(note.pitchUnits + pitchDelta);
|
|
18838
19361
|
core.moveNote(note.id, newStart, newPitch);
|
|
18839
19362
|
}
|
|
18840
19363
|
if (options.onPreviewSound && pitch !== lastPreviewPitch) {
|
|
@@ -18845,7 +19368,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18845
19368
|
return;
|
|
18846
19369
|
}
|
|
18847
19370
|
const nextStart = step - dragState.dragOffsetStep;
|
|
18848
|
-
const nextPitch = pitch - dragState.dragOffsetPitch;
|
|
19371
|
+
const nextPitch = units(pitch - dragState.dragOffsetPitch);
|
|
18849
19372
|
core.moveNote(dragState.noteId, nextStart, nextPitch);
|
|
18850
19373
|
return;
|
|
18851
19374
|
}
|
|
@@ -18875,7 +19398,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18875
19398
|
}
|
|
18876
19399
|
};
|
|
18877
19400
|
gridCanvas.addEventListener("pointerdown", (e) => {
|
|
18878
|
-
const { x: x2, y, step, pitch } = getGridPosition(e);
|
|
19401
|
+
const { x: x2, y, step, pitch } = renderer.getGridPosition(e);
|
|
18879
19402
|
const currentMode = core.getToolMode();
|
|
18880
19403
|
if (currentMode === "select") {
|
|
18881
19404
|
const clickedNote = findNoteAtPosition(x2, y);
|
|
@@ -18886,7 +19409,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18886
19409
|
noteId: clickedNote.id,
|
|
18887
19410
|
mode: "move",
|
|
18888
19411
|
dragOffsetStep: step - clickedNote.startStep,
|
|
18889
|
-
dragOffsetPitch: pitch - clickedNote.
|
|
19412
|
+
dragOffsetPitch: pitch - clickedNote.pitchUnits,
|
|
18890
19413
|
startStep: clickedNote.startStep,
|
|
18891
19414
|
selectedNotes: notesInRect
|
|
18892
19415
|
// 複数選択ノートを保存
|
|
@@ -18904,10 +19427,16 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18904
19427
|
}
|
|
18905
19428
|
const note = findNoteAtPosition(x2, y);
|
|
18906
19429
|
if (!note) return;
|
|
18907
|
-
const {
|
|
18908
|
-
|
|
19430
|
+
const {
|
|
19431
|
+
stepWidth,
|
|
19432
|
+
keyHeight,
|
|
19433
|
+
keyCount,
|
|
19434
|
+
pitchRangeStart,
|
|
19435
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19436
|
+
} = renderer.getRenderConfig();
|
|
19437
|
+
const offset = renderer.getDrawOffset();
|
|
18909
19438
|
const logicalX = note.startStep * stepWidth;
|
|
18910
|
-
const yIndex = keyCount - 1 - (note.
|
|
19439
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18911
19440
|
const logicalY = yIndex * keyHeight;
|
|
18912
19441
|
const renderX = logicalX - offset.x;
|
|
18913
19442
|
const renderY = logicalY - offset.y;
|
|
@@ -18926,7 +19455,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18926
19455
|
noteId: note.id,
|
|
18927
19456
|
mode: "move",
|
|
18928
19457
|
dragOffsetStep: step - note.startStep,
|
|
18929
|
-
dragOffsetPitch: pitch - note.
|
|
19458
|
+
dragOffsetPitch: pitch - note.pitchUnits,
|
|
18930
19459
|
startStep: note.startStep
|
|
18931
19460
|
};
|
|
18932
19461
|
});
|
|
@@ -18937,41 +19466,47 @@ var createPianoRoll = (options, handlers) => {
|
|
|
18937
19466
|
"wheel",
|
|
18938
19467
|
(e) => {
|
|
18939
19468
|
e.preventDefault();
|
|
18940
|
-
const configValues = getRenderConfig();
|
|
19469
|
+
const configValues = renderer.getRenderConfig();
|
|
18941
19470
|
const gridHeight = gridCanvas.height;
|
|
18942
19471
|
const maxOffsetY = Math.max(
|
|
18943
19472
|
0,
|
|
18944
19473
|
configValues.keyCount * configValues.keyHeight - gridHeight
|
|
18945
19474
|
);
|
|
18946
|
-
const currentOffset = getDrawOffset();
|
|
19475
|
+
const currentOffset = renderer.getDrawOffset();
|
|
18947
19476
|
const nextOffsetY = Math.min(
|
|
18948
19477
|
Math.max(currentOffset.y + e.deltaY, 0),
|
|
18949
19478
|
maxOffsetY
|
|
18950
19479
|
);
|
|
18951
|
-
setDrawOffset(currentOffset.x, nextOffsetY);
|
|
18952
|
-
drawGrid();
|
|
18953
|
-
drawNotes(core.getNotes());
|
|
19480
|
+
renderer.setDrawOffset(currentOffset.x, nextOffsetY);
|
|
19481
|
+
renderer.drawGrid();
|
|
19482
|
+
renderer.drawNotes(core.getNotes());
|
|
18954
19483
|
},
|
|
18955
19484
|
{ passive: false }
|
|
18956
19485
|
);
|
|
18957
19486
|
const redraw = () => {
|
|
18958
|
-
drawGrid();
|
|
18959
|
-
drawNotes(core.getNotes());
|
|
19487
|
+
renderer.drawGrid();
|
|
19488
|
+
renderer.drawNotes(core.getNotes());
|
|
18960
19489
|
if (core.getToolMode() === "select") {
|
|
18961
|
-
drawSelectionRect(selectionRect);
|
|
19490
|
+
renderer.drawSelectionRect(selectionRect);
|
|
18962
19491
|
if (selectedNotes.length > 0) {
|
|
18963
19492
|
const selectedIds = new Set(selectedNotes.map((n) => n.id));
|
|
18964
|
-
drawSelectedNotes(core.getNotes(), selectedIds);
|
|
19493
|
+
renderer.drawSelectedNotes(core.getNotes(), selectedIds);
|
|
18965
19494
|
}
|
|
18966
19495
|
}
|
|
18967
19496
|
};
|
|
18968
19497
|
const getNotesInRect = (rect) => {
|
|
18969
|
-
const {
|
|
18970
|
-
|
|
19498
|
+
const {
|
|
19499
|
+
stepWidth,
|
|
19500
|
+
keyHeight,
|
|
19501
|
+
keyCount,
|
|
19502
|
+
pitchRangeStart,
|
|
19503
|
+
unitsPerRow: upr = UNITS_PER_SEMITONE
|
|
19504
|
+
} = renderer.getRenderConfig();
|
|
19505
|
+
const offset = renderer.getDrawOffset();
|
|
18971
19506
|
const notes = [];
|
|
18972
19507
|
for (const note of core.getNotes()) {
|
|
18973
19508
|
const logicalX = note.startStep * stepWidth;
|
|
18974
|
-
const yIndex = keyCount - 1 - (note.
|
|
19509
|
+
const yIndex = keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr;
|
|
18975
19510
|
const logicalY = yIndex * keyHeight;
|
|
18976
19511
|
const noteRect = {
|
|
18977
19512
|
x: logicalX - offset.x,
|
|
@@ -19018,7 +19553,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
19018
19553
|
const minStart = Math.min(...copiedNotes.map((n) => n.startStep));
|
|
19019
19554
|
copiedNotes.forEach((note) => {
|
|
19020
19555
|
const newStep = startStep + (note.startStep - minStart);
|
|
19021
|
-
core.addNote(newStep, note.
|
|
19556
|
+
core.addNote(newStep, note.pitchUnits, {
|
|
19022
19557
|
noteLengthSteps: note.durationSteps,
|
|
19023
19558
|
velocity: note.velocity
|
|
19024
19559
|
});
|
|
@@ -19860,10 +20395,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
19860
20395
|
);
|
|
19861
20396
|
}
|
|
19862
20397
|
if (!sfInst) return;
|
|
20398
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
19863
20399
|
sfInst.play({
|
|
19864
20400
|
ctx: audioCtx,
|
|
19865
20401
|
destination: getChannelStrip(e.trackId).input,
|
|
19866
|
-
pitch:
|
|
20402
|
+
pitch: midi,
|
|
20403
|
+
detuneCents,
|
|
19867
20404
|
volume: e.volume,
|
|
19868
20405
|
velocity: e.velocity,
|
|
19869
20406
|
when: e.when,
|
|
@@ -20179,10 +20716,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20179
20716
|
);
|
|
20180
20717
|
}
|
|
20181
20718
|
if (!sfInst) return;
|
|
20719
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20182
20720
|
sfInst.play({
|
|
20183
20721
|
ctx: audioCtx,
|
|
20184
20722
|
destination: getChannelStrip(e.trackId).input,
|
|
20185
|
-
pitch:
|
|
20723
|
+
pitch: midi,
|
|
20724
|
+
detuneCents,
|
|
20186
20725
|
volume: e.volume,
|
|
20187
20726
|
velocity: e.velocity,
|
|
20188
20727
|
when: e.when,
|
|
@@ -20268,10 +20807,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20268
20807
|
);
|
|
20269
20808
|
}
|
|
20270
20809
|
if (!sfInst) return;
|
|
20810
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20271
20811
|
sfInst.play({
|
|
20272
20812
|
ctx: audioCtx,
|
|
20273
20813
|
destination: getChannelStrip(e.trackId).input,
|
|
20274
|
-
pitch:
|
|
20814
|
+
pitch: midi,
|
|
20815
|
+
detuneCents,
|
|
20275
20816
|
volume: e.volume,
|
|
20276
20817
|
velocity: e.velocity,
|
|
20277
20818
|
when: e.when,
|
|
@@ -20350,10 +20891,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20350
20891
|
);
|
|
20351
20892
|
}
|
|
20352
20893
|
if (!sfInst) return;
|
|
20894
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20353
20895
|
sfInst.play({
|
|
20354
20896
|
ctx: audioCtx,
|
|
20355
20897
|
destination: getChannelStrip(e.trackId).input,
|
|
20356
|
-
pitch:
|
|
20898
|
+
pitch: midi,
|
|
20899
|
+
detuneCents,
|
|
20357
20900
|
volume: e.volume,
|
|
20358
20901
|
velocity: e.velocity,
|
|
20359
20902
|
when: e.when,
|
|
@@ -20390,10 +20933,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20390
20933
|
sfInst = resolveSoundFont(defaultPreset, role, "simple");
|
|
20391
20934
|
}
|
|
20392
20935
|
if (!sfInst) return;
|
|
20936
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20393
20937
|
sfInst.play({
|
|
20394
20938
|
ctx: audioCtx,
|
|
20395
20939
|
destination: getChannelStrip(e.trackId).input,
|
|
20396
|
-
pitch:
|
|
20940
|
+
pitch: midi,
|
|
20941
|
+
detuneCents,
|
|
20397
20942
|
volume: e.volume,
|
|
20398
20943
|
velocity: e.velocity,
|
|
20399
20944
|
when: e.when,
|
|
@@ -20416,10 +20961,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20416
20961
|
if (audioCtx.state === "suspended") {
|
|
20417
20962
|
await audioCtx.resume();
|
|
20418
20963
|
}
|
|
20964
|
+
const { midi, detuneCents } = unitsToMidiDetune(options2.pitchUnits);
|
|
20419
20965
|
sfInst.play({
|
|
20420
20966
|
ctx: audioCtx,
|
|
20421
20967
|
destination: masterGain,
|
|
20422
|
-
pitch:
|
|
20968
|
+
pitch: midi,
|
|
20969
|
+
detuneCents,
|
|
20423
20970
|
volume: vol / 100,
|
|
20424
20971
|
when: 0,
|
|
20425
20972
|
duration: dur
|
|
@@ -20439,7 +20986,7 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20439
20986
|
// 伴奏トラック
|
|
20440
20987
|
startStep: p.startStep,
|
|
20441
20988
|
durationSteps: p.durationSteps,
|
|
20442
|
-
|
|
20989
|
+
pitchUnits: p.pitchUnits,
|
|
20443
20990
|
velocity: p.velocity
|
|
20444
20991
|
}));
|
|
20445
20992
|
const playerPreset = defaultPreset;
|
|
@@ -20447,10 +20994,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20447
20994
|
const playPlayerNote = (e) => {
|
|
20448
20995
|
const sfInst = resolveSoundFont(playerPreset, "chord");
|
|
20449
20996
|
if (!sfInst) return;
|
|
20997
|
+
const { midi, detuneCents } = unitsToMidiDetune(e.pitchUnits);
|
|
20450
20998
|
sfInst.play({
|
|
20451
20999
|
ctx: audioCtx,
|
|
20452
21000
|
destination: getChannelStrip("chord").input,
|
|
20453
|
-
pitch:
|
|
21001
|
+
pitch: midi,
|
|
21002
|
+
detuneCents,
|
|
20454
21003
|
volume: e.volume,
|
|
20455
21004
|
velocity: e.velocity,
|
|
20456
21005
|
when: e.when,
|
|
@@ -20589,8 +21138,12 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20589
21138
|
};
|
|
20590
21139
|
// Annotate the CommonJS export names for ESM import in node:
|
|
20591
21140
|
0 && (module.exports = {
|
|
21141
|
+
A4_HZ,
|
|
21142
|
+
A4_UNITS,
|
|
21143
|
+
CENTS_PER_UNIT,
|
|
20592
21144
|
DAW_CSS,
|
|
20593
21145
|
DEFAULT_BPM,
|
|
21146
|
+
DEFAULT_EDO,
|
|
20594
21147
|
DEFAULT_GATE,
|
|
20595
21148
|
DEFAULT_PAN,
|
|
20596
21149
|
DEFAULT_PLAYBACK_VELOCITY,
|
|
@@ -20600,30 +21153,41 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20600
21153
|
DRUM_FONT,
|
|
20601
21154
|
DRUM_KEYS,
|
|
20602
21155
|
DRUM_PATTERNS,
|
|
21156
|
+
EDO31_NAMES,
|
|
20603
21157
|
GM_INSTRUMENT_NAMES,
|
|
20604
21158
|
INSTRUMENT_PRESETS,
|
|
21159
|
+
KEY_COUNT,
|
|
20605
21160
|
KOE_BASE_URL,
|
|
20606
21161
|
KOE_VOICEBANKS,
|
|
20607
21162
|
KOE_VOICEBANK_LABELS,
|
|
20608
21163
|
KOE_VOICEBANK_TERMS,
|
|
20609
21164
|
LinkedList,
|
|
20610
21165
|
MAX_VOCAL_VOLUME,
|
|
21166
|
+
MICRO_STEP,
|
|
20611
21167
|
MMLCore,
|
|
20612
21168
|
MML_END_MARKER,
|
|
20613
21169
|
MidiSearchClient,
|
|
21170
|
+
PITCH_ENCODING_VERSION,
|
|
20614
21171
|
PITCH_MAP,
|
|
21172
|
+
PITCH_RANGE_END,
|
|
21173
|
+
PITCH_RANGE_START,
|
|
20615
21174
|
PREWARM_NOTES,
|
|
20616
21175
|
TRACKS_ADVANCED,
|
|
20617
21176
|
TRACKS_SIMPLE,
|
|
21177
|
+
UNITS_PER_EDO31_DEGREE,
|
|
21178
|
+
UNITS_PER_OCTAVE,
|
|
21179
|
+
UNITS_PER_SEMITONE,
|
|
20618
21180
|
VIBRATO_MIN_SEC,
|
|
20619
21181
|
VOICE_IMAGES,
|
|
20620
21182
|
VOICE_IMAGE_KEY,
|
|
21183
|
+
addUnits,
|
|
20621
21184
|
analyzeMidiTracks,
|
|
20622
21185
|
applyHarmonicFilter,
|
|
20623
21186
|
applyMonophonic,
|
|
20624
21187
|
buildChordPlacements,
|
|
20625
21188
|
buildDrumPatternJson,
|
|
20626
21189
|
buildNameToKeyMapping,
|
|
21190
|
+
chromaticStep,
|
|
20627
21191
|
collectPitchTokens,
|
|
20628
21192
|
concatFloat32,
|
|
20629
21193
|
createAudioContext,
|
|
@@ -20632,19 +21196,13 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20632
21196
|
createKoeVoice,
|
|
20633
21197
|
createLyricsConductor,
|
|
20634
21198
|
createPianoRoll,
|
|
21199
|
+
createRenderer,
|
|
20635
21200
|
createSequencer,
|
|
20636
21201
|
createSingingVoices,
|
|
20637
21202
|
createSynth,
|
|
20638
21203
|
createVoiceRegistry,
|
|
20639
21204
|
decodeMml,
|
|
20640
21205
|
decomposeToMonophonic,
|
|
20641
|
-
drawGrid,
|
|
20642
|
-
drawHeader,
|
|
20643
|
-
drawKeyboard,
|
|
20644
|
-
drawNoteLyrics,
|
|
20645
|
-
drawNotes,
|
|
20646
|
-
drawSelectedNotes,
|
|
20647
|
-
drawSelectionRect,
|
|
20648
21206
|
encodeMml,
|
|
20649
21207
|
encodeWavPCM16,
|
|
20650
21208
|
exportMIDI,
|
|
@@ -20652,36 +21210,35 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20652
21210
|
extractMidiDrumPattern,
|
|
20653
21211
|
extractMidiPlacements,
|
|
20654
21212
|
extractMidiPlacementsByTrack,
|
|
21213
|
+
fifthToStep,
|
|
21214
|
+
fifthToUnits,
|
|
20655
21215
|
formatMmlMeta,
|
|
20656
21216
|
freqFromPitch,
|
|
20657
21217
|
generateRandomPattern,
|
|
20658
|
-
getDrawOffset,
|
|
20659
21218
|
getDrumPatternKeys,
|
|
20660
|
-
getGridCanvas,
|
|
20661
|
-
getGridContext,
|
|
20662
|
-
getGridPosition,
|
|
20663
|
-
getHeaderCanvas,
|
|
20664
21219
|
getMidiBPM,
|
|
20665
|
-
getRenderConfig,
|
|
20666
|
-
getXY,
|
|
20667
21220
|
icon,
|
|
20668
|
-
init,
|
|
20669
21221
|
injectStyles,
|
|
20670
21222
|
isChordHeavyTrack,
|
|
21223
|
+
isNaturalLetter,
|
|
20671
21224
|
isPlausibleMidiTranscription,
|
|
20672
21225
|
isValidHttpUrl,
|
|
21226
|
+
keyCountFor,
|
|
20673
21227
|
koeUrl,
|
|
21228
|
+
midiNote,
|
|
21229
|
+
midiToUnits,
|
|
20674
21230
|
mountChordPlayer,
|
|
20675
21231
|
mountDAW,
|
|
20676
21232
|
mountMmlPlayer,
|
|
21233
|
+
naturalStep,
|
|
20677
21234
|
normalizeDrumPatterns,
|
|
20678
21235
|
normalizeLyrics,
|
|
20679
|
-
onClick,
|
|
20680
21236
|
panToStereo,
|
|
20681
21237
|
parseCustomVocals,
|
|
20682
21238
|
parseLyrics,
|
|
20683
21239
|
parseMML,
|
|
20684
21240
|
parseMmlMeta,
|
|
21241
|
+
pitchV1ToUnits,
|
|
20685
21242
|
playChords,
|
|
20686
21243
|
playMML,
|
|
20687
21244
|
playNote,
|
|
@@ -20689,13 +21246,19 @@ var createDtmStudio = async (options = {}) => {
|
|
|
20689
21246
|
playSingingMML,
|
|
20690
21247
|
resolveDrumPattern,
|
|
20691
21248
|
resolveLoopPoint,
|
|
20692
|
-
setBackgroundActive,
|
|
20693
|
-
setDrawOffset,
|
|
20694
21249
|
shiftNotes,
|
|
20695
21250
|
showLoadingOverlay,
|
|
21251
|
+
spellingToUnits,
|
|
20696
21252
|
stripCustomVocals,
|
|
20697
21253
|
stripLyrics,
|
|
20698
21254
|
stripMmlMeta,
|
|
20699
21255
|
transposeNotes,
|
|
21256
|
+
units,
|
|
21257
|
+
unitsPerRow,
|
|
21258
|
+
unitsPerStep,
|
|
21259
|
+
unitsToHz,
|
|
21260
|
+
unitsToMidi,
|
|
21261
|
+
unitsToMidiDetune,
|
|
21262
|
+
unitsToPitchV1,
|
|
20700
21263
|
vocalVolumeToGain
|
|
20701
21264
|
});
|