@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 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.0" },
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. What a value means follows the track's instrument (a track named or tuned like a bass is a bass): clean; crunch = light overdrive (bass: overdrive); distortion; acoustic = acoustic guitar (bass: upright bass); piano; synth = synth lead (bass: synth bass); strings."
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 (clean, crunch, distortion, acoustic, piano, synth, strings). Only the sound changes; the notes stay as written.",
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 = setTrackTone(doc, a.track ?? 0, a.tone);
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 = setTrackTone(added, before, a.tone ?? "clean");
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hidechae/eztab-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "MCP server for EZTab — lets an AI agent build and edit guitar/bass tablature.",
5
5
  "type": "module",
6
6
  "bin": {