chord-synth 1.0.0

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/mcp-server.js ADDED
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * chord-synth MCP Server
4
+ *
5
+ * Model Context Protocol server over stdio (JSON-RPC 2.0).
6
+ * Zero external dependencies — uses raw stdio transport.
7
+ *
8
+ * Exposes two tools:
9
+ * - generate_wav: Render a chord progression to a WAV file
10
+ * - list_options: Return all available presets, patterns, instruments, drums
11
+ *
12
+ * Add to your MCP client config:
13
+ *
14
+ * claude_desktop_config.json:
15
+ * {
16
+ * "mcpServers": {
17
+ * "chord-synth": {
18
+ * "command": "node",
19
+ * "args": ["/path/to/chord-synth/mcp-server.js"]
20
+ * }
21
+ * }
22
+ * }
23
+ *
24
+ * Claude Code:
25
+ * claude mcp add chord-synth -- node /path/to/chord-synth/mcp-server.js
26
+ */
27
+
28
+ 'use strict';
29
+
30
+ const fs = require('fs');
31
+ const path = require('path');
32
+ const engine = require('./lib/synth-engine');
33
+
34
+ // ── MCP Tool Definitions ──
35
+
36
+ const TOOLS = [
37
+ {
38
+ name: "generate_wav",
39
+ description: "Generate a chord progression WAV file. Supports 28 named presets (e.g. 'I-V-vi-IV (Pop)', 'Autumn Leaves', 'ii-V-I (Jazz)') or custom chord strings (e.g. 'C G Am/E F', 'Dm7 | G7 | Cmaj7'). Slash chords like Am/E set the bass note. Returns the file path of the rendered WAV.",
40
+ inputSchema: {
41
+ type: "object",
42
+ required: ["preset"],
43
+ properties: {
44
+ ...engine.SCHEMA.properties,
45
+ output_path: { type: "string", description: "File path to write the WAV. Defaults to CHORD_SYNTH_OUTPUT_DIR env var or current directory. If a directory, uses filename param or auto-generates." },
46
+ filename: { type: "string", description: "WAV filename when output_path is a directory. Default: chord_synth_output.wav" }
47
+ }
48
+ }
49
+ },
50
+ {
51
+ name: "list_options",
52
+ description: "List all available chord presets, arpeggio patterns, instruments, drum patterns, and note rates. Use this before generate_wav to discover options.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ category: {
57
+ type: "string",
58
+ description: "Optional: filter to one category.",
59
+ enum: ["presets", "patterns", "instruments", "drums", "rates", "all"]
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ];
65
+
66
+ // ── Tool Handlers ──
67
+
68
+ function handleGenerateWav(params) {
69
+ const defaultDir = process.env.CHORD_SYNTH_OUTPUT_DIR || '.';
70
+ const outputPath = params.output_path || defaultDir;
71
+ if (!outputPath) return { error: "output_path is required" };
72
+
73
+ let filePath = outputPath;
74
+ if (fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory()) {
75
+ const fname = params.filename || 'chord_synth_output.wav';
76
+ filePath = path.join(outputPath, fname);
77
+ }
78
+
79
+ fs.mkdirSync(path.dirname(path.resolve(filePath)), { recursive: true });
80
+
81
+ // Remove non-engine params before passing to renderJob
82
+ const job = { ...params };
83
+ delete job.output_path;
84
+ delete job.filename;
85
+
86
+ const wavData = engine.renderJob(job);
87
+ fs.writeFileSync(filePath, wavData);
88
+
89
+ const durationSec = (wavData.length - 44) / (engine.SAMPLE_RATE * 2);
90
+
91
+ return {
92
+ content: [{
93
+ type: "text",
94
+ text: JSON.stringify({
95
+ status: "success",
96
+ file: path.resolve(filePath),
97
+ size_bytes: wavData.length,
98
+ size_mb: (wavData.length / 1048576).toFixed(1),
99
+ duration_seconds: durationSec.toFixed(1),
100
+ preset: params.preset,
101
+ bpm: params.bpm || 120,
102
+ instrument: params.instrument || "piano",
103
+ transpose: params.transpose || 0
104
+ }, null, 2)
105
+ }]
106
+ };
107
+ }
108
+
109
+ function handleListOptions(params) {
110
+ const cat = (params && params.category) || "all";
111
+ const data = {};
112
+
113
+ if (cat === "all" || cat === "presets") data.presets = engine.PRESETS;
114
+ if (cat === "all" || cat === "patterns") data.patterns = engine.PATTERNS;
115
+ if (cat === "all" || cat === "instruments") data.instruments = engine.INSTRUMENTS;
116
+ if (cat === "all" || cat === "drums") data.drums = engine.DRUMS;
117
+ if (cat === "all" || cat === "rates") data.rates = engine.RATES;
118
+
119
+ return {
120
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
121
+ };
122
+ }
123
+
124
+ // ── MCP JSON-RPC Transport (stdio) ──
125
+
126
+ const SERVER_INFO = {
127
+ name: "chord-synth",
128
+ version: require('./package.json').version
129
+ };
130
+
131
+ const CAPABILITIES = {
132
+ tools: {}
133
+ };
134
+
135
+ function handleRequest(msg) {
136
+ switch (msg.method) {
137
+ case "initialize":
138
+ return { protocolVersion: "2024-11-05", capabilities: CAPABILITIES, serverInfo: SERVER_INFO };
139
+
140
+ case "notifications/initialized":
141
+ return null; // no response needed
142
+
143
+ case "tools/list":
144
+ return { tools: TOOLS };
145
+
146
+ case "tools/call": {
147
+ const name = msg.params && msg.params.name;
148
+ const args = (msg.params && msg.params.arguments) || {};
149
+ try {
150
+ if (name === "generate_wav") return handleGenerateWav(args);
151
+ if (name === "list_options") return handleListOptions(args);
152
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
153
+ } catch (err) {
154
+ return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
155
+ }
156
+ }
157
+
158
+ default:
159
+ throw { code: -32601, message: `Method not found: ${msg.method}` };
160
+ }
161
+ }
162
+
163
+ // ── Stdio Line Reader ──
164
+
165
+ let buffer = '';
166
+
167
+ process.stdin.setEncoding('utf8');
168
+ process.stdin.on('data', (chunk) => {
169
+ buffer += chunk;
170
+ // MCP uses newline-delimited JSON
171
+ const lines = buffer.split('\n');
172
+ buffer = lines.pop(); // keep incomplete last line
173
+ for (const line of lines) {
174
+ if (!line.trim()) continue;
175
+ try {
176
+ const msg = JSON.parse(line);
177
+ const result = handleRequest(msg);
178
+ if (result !== null && msg.id !== undefined) {
179
+ const response = { jsonrpc: "2.0", id: msg.id, result };
180
+ process.stdout.write(JSON.stringify(response) + '\n');
181
+ }
182
+ } catch (err) {
183
+ if (err.code) {
184
+ // JSON-RPC error
185
+ const parsed = (() => { try { return JSON.parse(line); } catch { return {}; } })();
186
+ if (parsed.id !== undefined) {
187
+ process.stdout.write(JSON.stringify({
188
+ jsonrpc: "2.0", id: parsed.id,
189
+ error: { code: err.code, message: err.message }
190
+ }) + '\n');
191
+ }
192
+ } else {
193
+ process.stderr.write(`MCP error: ${err.message}\n`);
194
+ }
195
+ }
196
+ }
197
+ });
198
+
199
+ process.stderr.write('chord-synth MCP server started on stdio\n');
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "chord-synth",
3
+ "version": "1.0.0",
4
+ "description": "Generate chord progression WAV files. 28 presets, 22 arp patterns, 7 instruments, slash chords, transposition. CLI + MCP server + Node.js module. Zero dependencies.",
5
+ "keywords": [
6
+ "chord",
7
+ "progression",
8
+ "arpeggiator",
9
+ "synthesizer",
10
+ "wav",
11
+ "audio",
12
+ "midi",
13
+ "music",
14
+ "generator",
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "ai-tools",
18
+ "logic-pro",
19
+ "agent-tools",
20
+ "llm-tools"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "main": "lib/synth-engine.js",
25
+ "type": "commonjs",
26
+ "bin": {
27
+ "chord-synth": "bin/chord-synth.js"
28
+ },
29
+ "files": [
30
+ "lib/",
31
+ "bin/",
32
+ "mcp-server.js",
33
+ "smithery.yaml",
34
+ "render_jobs.json",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "scripts": {
39
+ "start": "node bin/chord-synth.js",
40
+ "mcp": "node mcp-server.js",
41
+ "batch": "node bin/chord-synth.js --batch render_jobs.json",
42
+ "demo": "node bin/chord-synth.js --preset 'C G Am F' --bpm 120 --output demo.wav",
43
+ "schema": "node bin/chord-synth.js --schema",
44
+ "prepublishOnly": "node -e \"require('./lib/synth-engine'); console.log('Engine OK');\""
45
+ },
46
+ "engines": {
47
+ "node": ">=14.0.0"
48
+ },
49
+ "os": [
50
+ "darwin",
51
+ "linux",
52
+ "win32"
53
+ ],
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "registry": "https://registry.npmjs.org/"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/Misfits-Rebels-Outcasts/chord-synth.git"
61
+ },
62
+ "homepage": "https://github.com/Misfits-Rebels-Outcasts/chord-synth#readme",
63
+ "bugs": {
64
+ "url": "https://github.com/Misfits-Rebels-Outcasts/chord-synth/issues"
65
+ },
66
+ "directories": {
67
+ "lib": "lib"
68
+ },
69
+ "dependencies": {
70
+ "chord-synth": "github:Misfits-Rebels-Outcasts/chord-synth"
71
+ }
72
+ }
@@ -0,0 +1,122 @@
1
+ [
2
+ {
3
+ "filename": "001_Pop_Piano_C.wav",
4
+ "preset": "I-V-vi-IV (Pop)",
5
+ "bpm": 120,
6
+ "pattern": 1,
7
+ "rate": "1/8",
8
+ "instrument": "piano",
9
+ "octaveRange": 2,
10
+ "drumPattern": "poprock",
11
+ "loops": 2
12
+ },
13
+ {
14
+ "filename": "002_Jazz_Alberti_Bb.wav",
15
+ "preset": "ii-V-I (Jazz)",
16
+ "bpm": 100,
17
+ "pattern": 17,
18
+ "rate": "1/8",
19
+ "instrument": "piano",
20
+ "transpose": 10,
21
+ "swing": 0.62,
22
+ "drumPattern": "swing",
23
+ "loops": 3
24
+ },
25
+ {
26
+ "filename": "003_Custom_SlashChords.wav",
27
+ "preset": "Dm9 | G7/B | Cmaj7 | Am7/E",
28
+ "beatsPerChord": 4,
29
+ "bpm": 92,
30
+ "pattern": 13,
31
+ "rate": "1/8T",
32
+ "instrument": "piano",
33
+ "swing": 0.6,
34
+ "drumPattern": "bossanova",
35
+ "loops": 2
36
+ },
37
+ {
38
+ "filename": "004_NeoSoul_Guitar.wav",
39
+ "preset": "Neo-Soul I",
40
+ "bpm": 88,
41
+ "pattern": 5,
42
+ "rate": "1/8T",
43
+ "instrument": "guitar",
44
+ "octaveRange": 2,
45
+ "velRandom": 10,
46
+ "drumPattern": "rnb",
47
+ "loops": 2
48
+ },
49
+ {
50
+ "filename": "005_Ambient_NoDrums.wav",
51
+ "preset": "Cmaj9 Am9 Fmaj9 Gsus4",
52
+ "beatsPerChord": 8,
53
+ "bpm": 55,
54
+ "pattern": 1,
55
+ "rate": "1/4",
56
+ "instrument": "piano",
57
+ "enableDrums": false,
58
+ "padVolume": 0.24,
59
+ "reverbMix": 0.35,
60
+ "loops": 2
61
+ },
62
+ {
63
+ "filename": "006_Blues_Swing.wav",
64
+ "preset": "12-Bar Blues",
65
+ "bpm": 108,
66
+ "pattern": 18,
67
+ "rate": "1/4",
68
+ "instrument": "piano",
69
+ "swing": 0.66,
70
+ "drumPattern": "swing",
71
+ "enablePad": false,
72
+ "loops": 1
73
+ },
74
+ {
75
+ "filename": "007_Trance_16th.wav",
76
+ "preset": "Trance",
77
+ "bpm": 140,
78
+ "pattern": 5,
79
+ "rate": "1/16",
80
+ "instrument": "piano",
81
+ "octaveRange": 3,
82
+ "drumPattern": "8beat",
83
+ "loops": 2
84
+ },
85
+ {
86
+ "filename": "008_Custom_Waltz.wav",
87
+ "preset": "Am | Dm/F | E7 | Am",
88
+ "beatsPerChord": 6,
89
+ "bpm": 75,
90
+ "pattern": 3,
91
+ "rate": "1/4",
92
+ "instrument": "violin",
93
+ "drumPattern": "waltz",
94
+ "padVolume": 0.16,
95
+ "loops": 2
96
+ },
97
+ {
98
+ "filename": "009_Cinematic_Violin.wav",
99
+ "preset": "Am | Am/G | F | Fmaj7 | Dm7 | Dm7/C | E7 | E7",
100
+ "beatsPerChord": 4,
101
+ "bpm": 60,
102
+ "pattern": 3,
103
+ "rate": "1/8",
104
+ "instrument": "violin",
105
+ "enableDrums": false,
106
+ "padVolume": 0.2,
107
+ "reverbMix": 0.32,
108
+ "loops": 2
109
+ },
110
+ {
111
+ "filename": "010_Transposed_Pop_G.wav",
112
+ "preset": "I-V-vi-IV (Pop)",
113
+ "bpm": 120,
114
+ "pattern": 1,
115
+ "rate": "1/8",
116
+ "instrument": "guitar",
117
+ "transpose": 7,
118
+ "octaveRange": 2,
119
+ "drumPattern": "acousticpop",
120
+ "loops": 2
121
+ }
122
+ ]
package/smithery.yaml ADDED
@@ -0,0 +1,23 @@
1
+ # Smithery configuration file: https://smithery.ai/docs/build/project-config/smithery-yaml
2
+ # chord-synth: Generate chord progression WAV files via MCP
3
+
4
+ startCommand:
5
+ type: stdio
6
+ configSchema:
7
+ # No API keys or auth needed — chord-synth is fully local
8
+ type: object
9
+ properties:
10
+ outputDir:
11
+ type: string
12
+ title: "Output Directory"
13
+ description: "Directory where WAV files will be written. Defaults to current working directory."
14
+ default: "."
15
+ required: []
16
+ commandFunction: |-
17
+ (config) => ({
18
+ command: 'node',
19
+ args: ['mcp-server.js'],
20
+ env: {
21
+ CHORD_SYNTH_OUTPUT_DIR: config.outputDir || '.'
22
+ }
23
+ })