@hidechae/eztab-mcp 0.6.0 → 0.6.1
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 +2 -0
- package/dist/server.js +162 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,8 @@ Run standalone for testing: `task mcp` (or `npx tsx mcp/server.ts`).
|
|
|
125
125
|
- 値は `clean`(既定)/ `crunch` / `distortion` / `acoustic` / `piano` / `synth` / `strings`
|
|
126
126
|
- 値の意味はパートの楽器で変わる。名前かチューニングでベースと判定されるパートでは、
|
|
127
127
|
`crunch` はオーバードライブ、`acoustic` はウッドベース、`synth` はシンセベースとして鳴る
|
|
128
|
+
- ベースと判定されるパートに設定できるのは `clean` / `crunch` / `acoustic` / `synth` だけ(0.6.1以降。
|
|
129
|
+
それ以外は `set_tone` / `add_track` がエラーを返す)
|
|
128
130
|
- `get_tab` はクリーン以外の音色をパート一覧に `[distortion]` のように表示する
|
|
129
131
|
(1パートのタブでは `tone=distortion`)
|
|
130
132
|
- 0.5.2以前のMCPは、クリーン以外の音色を持つタブを編集・送信できない
|
package/dist/server.js
CHANGED
|
@@ -503,6 +503,31 @@ function setBeatSubdivision(measure, beat, subdivision) {
|
|
|
503
503
|
return remapToSubdivisions(measure, subs);
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
// ../src/lib/music.ts
|
|
507
|
+
var LETTER_PC = {
|
|
508
|
+
C: 0,
|
|
509
|
+
D: 2,
|
|
510
|
+
E: 4,
|
|
511
|
+
F: 5,
|
|
512
|
+
G: 7,
|
|
513
|
+
A: 9,
|
|
514
|
+
B: 11
|
|
515
|
+
};
|
|
516
|
+
function noteToMidi(note) {
|
|
517
|
+
const m = /^([A-Ga-g])([#♯b♭]*)(-?\d+)?$/.exec(note.trim());
|
|
518
|
+
if (!m) return null;
|
|
519
|
+
let pc = LETTER_PC[m[1].toUpperCase()];
|
|
520
|
+
for (const ch of m[2]) {
|
|
521
|
+
if (ch === "#" || ch === "\u266F") pc += 1;
|
|
522
|
+
if (ch === "b" || ch === "\u266D") pc -= 1;
|
|
523
|
+
}
|
|
524
|
+
const octave = m[3] != null ? parseInt(m[3], 10) : 4;
|
|
525
|
+
return (octave + 1) * 12 + pc;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ../src/lib/playbackRate.ts
|
|
529
|
+
var PLAYBACK_RATES = Array.from({ length: 36 }, (_, i) => (5 + i) / 20);
|
|
530
|
+
|
|
506
531
|
// ../src/lib/tracks.ts
|
|
507
532
|
function newUid() {
|
|
508
533
|
return newTabId();
|
|
@@ -742,6 +767,128 @@ function storedSize(doc) {
|
|
|
742
767
|
return new TextEncoder().encode(JSON.stringify(closeWorkspace(doc))).length;
|
|
743
768
|
}
|
|
744
769
|
|
|
770
|
+
// ../src/lib/player.ts
|
|
771
|
+
function instrumentOf(tuning, name = "") {
|
|
772
|
+
if (/bass|ベース/i.test(name)) return "bass";
|
|
773
|
+
const open = tuning.map(noteToMidi).filter((m) => m != null);
|
|
774
|
+
return open.length > 0 && Math.max(...open) < 50 ? "bass" : "guitar";
|
|
775
|
+
}
|
|
776
|
+
var BUFFER_BUDGET = 12e6;
|
|
777
|
+
var PREPARE_CAPACITY = BUFFER_BUDGET / 2;
|
|
778
|
+
var OPEN = 2e4;
|
|
779
|
+
var GUITAR_CLEAN = {
|
|
780
|
+
pickupHz: 4500,
|
|
781
|
+
pickupQ: 4,
|
|
782
|
+
tightHz: 20,
|
|
783
|
+
pushDb: 0,
|
|
784
|
+
drive: 1,
|
|
785
|
+
stage1: null,
|
|
786
|
+
drive2: 1,
|
|
787
|
+
stage2: null,
|
|
788
|
+
interHpHz: 20,
|
|
789
|
+
interLpHz: OPEN,
|
|
790
|
+
bodyHz: 110,
|
|
791
|
+
bodyDb: 0,
|
|
792
|
+
cabHz: OPEN,
|
|
793
|
+
cabQ: 0,
|
|
794
|
+
lowCutHz: 70,
|
|
795
|
+
level: 1
|
|
796
|
+
};
|
|
797
|
+
var BASS_CLEAN = { ...GUITAR_CLEAN, pickupHz: 2600, pickupQ: 1, lowCutHz: 30 };
|
|
798
|
+
var DIRECT = { ...GUITAR_CLEAN, pickupHz: 16e3, pickupQ: 0, lowCutHz: 30 };
|
|
799
|
+
var PIANO = { ...DIRECT, pickupHz: 7e3, bodyHz: 220, bodyDb: 3, level: 1.3 };
|
|
800
|
+
var CRUNCH = { gain: 16, bias: 0.3 };
|
|
801
|
+
var CHAINS = {
|
|
802
|
+
guitar: {
|
|
803
|
+
clean: GUITAR_CLEAN,
|
|
804
|
+
crunch: {
|
|
805
|
+
...GUITAR_CLEAN,
|
|
806
|
+
tightHz: 120,
|
|
807
|
+
pushDb: 2,
|
|
808
|
+
drive: 0.5,
|
|
809
|
+
stage1: CRUNCH,
|
|
810
|
+
bodyHz: 2800,
|
|
811
|
+
bodyDb: 2,
|
|
812
|
+
cabHz: 5200,
|
|
813
|
+
cabQ: 1,
|
|
814
|
+
lowCutHz: 80,
|
|
815
|
+
level: 0.26
|
|
816
|
+
},
|
|
817
|
+
distortion: {
|
|
818
|
+
...GUITAR_CLEAN,
|
|
819
|
+
pickupQ: 2,
|
|
820
|
+
tightHz: 250,
|
|
821
|
+
pushDb: 6,
|
|
822
|
+
drive: 0.5,
|
|
823
|
+
stage1: { gain: 24, bias: 0.25 },
|
|
824
|
+
// The next tube stage is inverted, so it clips the other half harder.
|
|
825
|
+
drive2: 1,
|
|
826
|
+
stage2: { gain: 5, bias: -0.35 },
|
|
827
|
+
interHpHz: 160,
|
|
828
|
+
interLpHz: 6500,
|
|
829
|
+
bodyHz: 2800,
|
|
830
|
+
bodyDb: 4,
|
|
831
|
+
cabHz: 4800,
|
|
832
|
+
cabQ: 3,
|
|
833
|
+
lowCutHz: 90,
|
|
834
|
+
level: 0.17
|
|
835
|
+
},
|
|
836
|
+
acoustic: { ...GUITAR_CLEAN, pickupHz: 9e3, pickupQ: 0, bodyDb: 5, level: 1.2 },
|
|
837
|
+
piano: PIANO,
|
|
838
|
+
synth: { ...DIRECT, level: 0.8 },
|
|
839
|
+
strings: { ...DIRECT, level: 0.8 }
|
|
840
|
+
},
|
|
841
|
+
bass: {
|
|
842
|
+
clean: BASS_CLEAN,
|
|
843
|
+
// Overdrive.
|
|
844
|
+
crunch: {
|
|
845
|
+
...BASS_CLEAN,
|
|
846
|
+
tightHz: 40,
|
|
847
|
+
pushDb: 3,
|
|
848
|
+
drive: 0.5,
|
|
849
|
+
stage1: { gain: 22, bias: 0.3 },
|
|
850
|
+
cabHz: 3500,
|
|
851
|
+
cabQ: 1,
|
|
852
|
+
level: 0.24
|
|
853
|
+
},
|
|
854
|
+
// The mixer offers a bass only the tones in tonesFor(); these three still
|
|
855
|
+
// play if a tab stores them (a guitar track retuned to a bass, say).
|
|
856
|
+
distortion: {
|
|
857
|
+
...BASS_CLEAN,
|
|
858
|
+
tightHz: 60,
|
|
859
|
+
pushDb: 4,
|
|
860
|
+
drive: 0.5,
|
|
861
|
+
stage1: { gain: 24, bias: 0.25 },
|
|
862
|
+
stage2: { gain: 5, bias: -0.35 },
|
|
863
|
+
interHpHz: 50,
|
|
864
|
+
interLpHz: 5e3,
|
|
865
|
+
cabHz: 3e3,
|
|
866
|
+
cabQ: 2,
|
|
867
|
+
lowCutHz: 35,
|
|
868
|
+
level: 0.15
|
|
869
|
+
},
|
|
870
|
+
// Upright bass.
|
|
871
|
+
acoustic: {
|
|
872
|
+
...BASS_CLEAN,
|
|
873
|
+
pickupHz: 3e3,
|
|
874
|
+
pickupQ: 0,
|
|
875
|
+
bodyHz: 100,
|
|
876
|
+
bodyDb: 6,
|
|
877
|
+
level: 1.4
|
|
878
|
+
},
|
|
879
|
+
piano: PIANO,
|
|
880
|
+
// Synth bass.
|
|
881
|
+
synth: { ...DIRECT, level: 0.8 },
|
|
882
|
+
strings: { ...DIRECT, level: 0.8 }
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
// ../src/lib/tones.ts
|
|
887
|
+
var BASS_TONES = ["clean", "crunch", "acoustic", "synth"];
|
|
888
|
+
function tonesFor(instrument) {
|
|
889
|
+
return instrument === "bass" ? BASS_TONES : TONES;
|
|
890
|
+
}
|
|
891
|
+
|
|
745
892
|
// ../src/lib/config.ts
|
|
746
893
|
var CLOUD_BASE_URL = "https://eztab-sync-736328841982.asia-northeast1.run.app";
|
|
747
894
|
|
|
@@ -1073,7 +1220,7 @@ var state = {
|
|
|
1073
1220
|
source: null
|
|
1074
1221
|
};
|
|
1075
1222
|
var server = new McpServer(
|
|
1076
|
-
{ name: "eztab", version: "0.6.
|
|
1223
|
+
{ name: "eztab", version: "0.6.1" },
|
|
1077
1224
|
{
|
|
1078
1225
|
instructions: "All indices are 0-based: measure 0 is the first measure, string 0 is the top (highest) string, and column 0 is the first column. Notes and chords share the same column grid. Subdivisions are per beat and can differ within a measure (e.g. [2,2,3,2] mixes a triplet beat into straight eighths), so columns per measure = the sum of its per-beat subdivisions; get_tab's ruler shows the column indices. add_measures `at` is a 0-based insert position too. No need to ask the user about indexing. Exception: write_measures uses guitar string numbers (1 = top line), matching the labels get_tab prints. To write whole measures (e.g. transcribing a score or an image), prefer write_measures over set_cells \u2014 it takes one string per beat and does the column arithmetic for you. For transcribing from an image, follow the import_tab_from_image prompt. To deliver a result to the EZTab app, call send_to_eztab; to fix an existing tab, list_tabs \u2192 open_tab \u2192 edit \u2192 send_to_eztab. A tab can hold up to 4 tracks (parts, e.g. guitar + bass); track 0 is the main track. Tools that edit notes take an optional 0-based `track` (default 0). Measures and time signatures are shared by all tracks (adding/removing a measure affects every track); subdivisions are per track; chords belong to track 0 and use its columns."
|
|
1079
1226
|
}
|
|
@@ -1563,17 +1710,27 @@ server.tool(
|
|
|
1563
1710
|
}, a.track)
|
|
1564
1711
|
);
|
|
1565
1712
|
var toneParam = z.enum(TONES).describe(
|
|
1566
|
-
"How the track sounds in playback.
|
|
1713
|
+
"How the track sounds in playback. Guitar tracks: clean, crunch (light overdrive), distortion, acoustic, piano, synth, strings. Bass tracks (named or tuned like a bass): clean, crunch (overdrive), acoustic (upright bass), synth (synth bass) only."
|
|
1567
1714
|
);
|
|
1715
|
+
function withTone(doc, track, tone) {
|
|
1716
|
+
const instrument = instrumentOf(trackBodies(doc)[track].tuning, trackMetas(doc)[track].name);
|
|
1717
|
+
const offered = tonesFor(instrument);
|
|
1718
|
+
if (!offered.includes(tone)) {
|
|
1719
|
+
throw new Error(
|
|
1720
|
+
`a ${instrument} track can't use the "${tone}" tone; choose one of: ${offered.join(", ")}`
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
return setTrackTone(doc, track, tone);
|
|
1724
|
+
}
|
|
1568
1725
|
server.tool(
|
|
1569
1726
|
"set_tone",
|
|
1570
|
-
"Set a track's playback tone
|
|
1727
|
+
"Set a track's playback tone. Only the sound changes; the notes stay as written.",
|
|
1571
1728
|
{ tone: toneParam, track: trackParam },
|
|
1572
1729
|
async (a) => {
|
|
1573
1730
|
const doc = edit(0, () => {
|
|
1574
1731
|
});
|
|
1575
1732
|
onTrack(doc, a.track);
|
|
1576
|
-
state.doc =
|
|
1733
|
+
state.doc = withTone(doc, a.track ?? 0, a.tone);
|
|
1577
1734
|
return text(render(state.doc));
|
|
1578
1735
|
}
|
|
1579
1736
|
);
|
|
@@ -1603,7 +1760,7 @@ server.tool(
|
|
|
1603
1760
|
if (trackMetas(added).length === before) {
|
|
1604
1761
|
throw new Error("a tab can hold at most 4 tracks");
|
|
1605
1762
|
}
|
|
1606
|
-
const next =
|
|
1763
|
+
const next = withTone(added, before, a.tone ?? "clean");
|
|
1607
1764
|
state.doc = next;
|
|
1608
1765
|
return text(`Added track ${before} "${a.name}".
|
|
1609
1766
|
|