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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # chord-synth
2
+
3
+ Generate chord progression WAV files. Zero dependencies. Node.js only.
4
+
5
+ 28 named presets, 22 arpeggio patterns, 7 instruments, slash chords (`Am/E`), transposition, pad/bass/drum tracks, reverb.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Install globally
11
+ npm install -g chord-synth
12
+
13
+ # Generate a WAV file
14
+ chord-synth --preset "C G Am F" --bpm 120 --output pop.wav
15
+
16
+ # Custom jazz with slash chords
17
+ chord-synth --preset "Dm9 | G7/B | Cmaj7 | Am7/E" --instrument piano --drums bossanova --output jazz.wav
18
+
19
+ # Named preset transposed to key of G
20
+ chord-synth --preset "I-V-vi-IV (Pop)" --transpose 7 --output pop_in_G.wav
21
+
22
+ # Batch render from JSON file
23
+ chord-synth --batch render_jobs.json --output ./wavs/
24
+ ```
25
+
26
+ ## Use as MCP Server
27
+
28
+ Exposes `generate_wav` and `list_options` tools to any MCP-compatible agent (Claude Desktop, Claude Code, Cursor, Windsurf, etc).
29
+
30
+ **Claude Desktop** — add to `claude_desktop_config.json`:
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "chord-synth": {
35
+ "command": "node",
36
+ "args": ["/path/to/chord-synth/mcp-server.js"]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ **Claude Code:**
43
+ ```bash
44
+ claude mcp add chord-synth -- node /path/to/chord-synth/mcp-server.js
45
+ ```
46
+
47
+ Then ask Claude: *"Generate a sad violin arpeggio over Am F C G at 80 BPM"* — it will call `generate_wav` directly.
48
+
49
+ ## Use as Node.js Module
50
+
51
+ ```javascript
52
+ const { renderJob, PRESETS, SCHEMA } = require('chord-synth');
53
+ const fs = require('fs');
54
+
55
+ // Render a WAV buffer
56
+ const wav = renderJob({
57
+ preset: "C G Am/E F",
58
+ bpm: 120,
59
+ instrument: "piano",
60
+ pattern: 1, // Up
61
+ enablePad: true,
62
+ drumPattern: "poprock"
63
+ });
64
+
65
+ fs.writeFileSync('output.wav', wav);
66
+ ```
67
+
68
+ ## Agent Interface
69
+
70
+ Agents should call `chord-synth --schema` to get the full JSON tool specification:
71
+
72
+ ```bash
73
+ chord-synth --schema
74
+ ```
75
+
76
+ Returns:
77
+ ```json
78
+ {
79
+ "tool": "chord-synth",
80
+ "description": "Generate chord progression WAV files...",
81
+ "input_schema": {
82
+ "type": "object",
83
+ "required": ["preset"],
84
+ "properties": {
85
+ "preset": { "type": "string", "description": "Named preset or custom chords..." },
86
+ "bpm": { "type": "integer", "default": 120 },
87
+ "instrument": { "type": "string", "enum": ["piano","guitar","violin",...] },
88
+ ...
89
+ }
90
+ },
91
+ "examples": [...]
92
+ }
93
+ ```
94
+
95
+ List all available options:
96
+ ```bash
97
+ chord-synth --list
98
+ ```
99
+
100
+ ## Parameters
101
+
102
+ | Parameter | Type | Default | Description |
103
+ |-----------|------|---------|-------------|
104
+ | `preset` | string | *required* | Named preset (`"I-V-vi-IV (Pop)"`) or custom chords (`"C G Am/E F"`) |
105
+ | `bpm` | int | 120 | Tempo |
106
+ | `transpose` | int | 0 | Semitones (-11 to +11) |
107
+ | `pattern` | int | 1 | Arp pattern index (see below) |
108
+ | `rate` | string | `"1/8"` | Note rate: `1/1` `1/2` `1/4` `1/8` `1/16` `1/32` (add T for triplet) |
109
+ | `instrument` | string | `"piano"` | `piano` `guitar` `violin` `flute` `clarinet` `cello` `bass` |
110
+ | `octaveRange` | int | 1 | 1-4 octave span |
111
+ | `baseOctave` | int | 3 | Starting MIDI octave |
112
+ | `velocity` | int | 90 | MIDI velocity 1-127 |
113
+ | `swing` | float | 0.5 | 0.5=straight, 0.66=medium, 0.75=heavy |
114
+ | `loops` | int | 2 | Progression repetitions |
115
+ | `enablePad` | bool | true | Sustained pad chord track |
116
+ | `padOctave` | int | 4 | Pad voicing octave |
117
+ | `padVolume` | float | 0.12 | Pad volume (0.0-0.3) |
118
+ | `enableBass` | bool | true | Auto bass (slash chords set bass note) |
119
+ | `enableDrums` | bool | true | Drum track |
120
+ | `drumPattern` | string | `"poprock"` | See drum patterns below |
121
+ | `reverbMix` | float | 0.18 | Reverb amount (0.0-0.5) |
122
+
123
+ ## Named Presets
124
+
125
+ | Category | Presets |
126
+ |----------|---------|
127
+ | Pop/Rock | `I-V-vi-IV (Pop)`, `I-vi-IV-V (50s)`, `vi-IV-I-V (Sad)`, `I-IV-V-IV (Rock)`, `I-V-vi-iii-IV (Canon)` |
128
+ | Jazz | `ii-V-I (Jazz)`, `I-vi-ii-V (Jazz Turnaround)`, `iii-vi-ii-V (Coltrane)`, `Autumn Leaves`, `So What`, `Steely Dan` |
129
+ | Neo-Soul/R&B | `Neo-Soul I`, `Neo-Soul II`, `R&B Smooth` |
130
+ | Blues | `12-Bar Blues`, `Minor Blues` |
131
+ | Latin | `Bossa Nova`, `Girl from Ipanema`, `Andalusian Cadence`, `Royal Road (JP)` |
132
+ | Modal/Ambient | `Dorian Vamp`, `Lydian Float`, `Ambient Pads` |
133
+ | EDM | `EDM Anthem`, `Trance`, `House` |
134
+ | Classic | `Pachelbel Canon`, `Gospel` |
135
+
136
+ Or use any custom chord string: `"Fmaj7 Em7 Dm9 Cmaj7"`, `"Am | Dm/F | E7 | Am"`, etc.
137
+
138
+ ## Arp Patterns
139
+
140
+ | Index | Name | Index | Name |
141
+ |-------|------|-------|------|
142
+ | 0 | Chord (Block) | 11 | Pinky-Thumb |
143
+ | 1 | Up | 12 | Thumb-Pinky |
144
+ | 2 | Down | 13 | 1-3-5-3 |
145
+ | 3 | Up-Down | 14 | 1-5-3-5 |
146
+ | 4 | Down-Up | 15 | 1-5-8-5 |
147
+ | 5 | Up-Down (No Repeat) | 16 | 1-3-5-8-5-3 |
148
+ | 6 | Down-Up (No Repeat) | 17 | Alberti Bass |
149
+ | 7 | Random | 18 | Stride |
150
+ | 8 | Random Walk | 19 | Broken 3rds |
151
+ | 9 | Outside-In | 20 | Pedal Tone |
152
+ | 10 | Inside-Out | 21 | Alternating Bass |
153
+
154
+ ## Drum Patterns
155
+
156
+ `8beat` `16beat` `poprock` `acousticpop` `bossanova` `swing` `funk` `rnb` `waltz` `arpeggio` `none`
157
+
158
+ ## Chord Notation
159
+
160
+ Roots: `C C# Db D D# Eb E F F# Gb G G# Ab A A# Bb B`
161
+
162
+ Qualities: `(major)` `m` `7` `maj7` `m7` `dim` `aug` `sus2` `sus4` `9` `m9` `maj9` `11` `m11` `13` `6` `m6` `dim7` `m7b5` `add9` `5` `power`
163
+
164
+ Slash chords: `Am/E` (Am chord, E bass note), `C/G`, `Dm7/A`
165
+
166
+ Separator: spaces or `|` both work. `C G Am F` = `C | G | Am | F`
167
+
168
+ ## Output
169
+
170
+ WAV PCM 16-bit mono 44100 Hz. Compatible with all DAWs, audio players, and audio processing tools.
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * chord-synth CLI
4
+ *
5
+ * Self-describing command-line interface for autonomous agents and humans.
6
+ * Run with --schema to get full JSON parameter spec.
7
+ *
8
+ * Usage:
9
+ * chord-synth --schema # JSON schema (agent-readable)
10
+ * chord-synth --list # all options as JSON
11
+ * chord-synth --preset "C G Am/E F" --bpm 120 # render single file
12
+ * chord-synth --json '{"preset":"Am F C G"}' # render from inline JSON
13
+ * chord-synth --batch render_jobs.json # batch render from file
14
+ * chord-synth --batch render_jobs.json --dry-run # validate without rendering
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const engine = require('../lib/synth-engine');
22
+
23
+ const args = process.argv.slice(2);
24
+
25
+ // ── Subcommand: "mcp" launches the MCP server ──
26
+ if (args[0] === 'mcp') {
27
+ require('../mcp-server.js');
28
+ } else {
29
+
30
+ function flag(name) { return args.includes('--' + name); }
31
+ function flagVal(name) {
32
+ const i = args.indexOf('--' + name);
33
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : null;
34
+ }
35
+ function flagNum(name, def) { const v = flagVal(name); return v !== null ? Number(v) : def; }
36
+
37
+ // ── --schema: machine-readable parameter specification ──
38
+ if (flag('schema')) {
39
+ const spec = {
40
+ tool: "chord-synth",
41
+ version: require('../package.json').version,
42
+ description: "Generate chord progression WAV files. Renders arpeggio patterns with configurable instruments, drums, pads, and effects. Supports 28 named presets, 22 arp patterns, 7 instruments, slash chords (Am/E), and transposition.",
43
+ input_schema: engine.SCHEMA,
44
+ output: { type: "file", format: "WAV PCM 16-bit mono 44100Hz" },
45
+ examples: [
46
+ { description: "Pop piano arpeggio", args: { preset: "I-V-vi-IV (Pop)", bpm: 120, instrument: "piano", pattern: 1 } },
47
+ { description: "Custom jazz with slash chords", args: { preset: "Dm9 | G7/B | Cmaj7 | Am7/E", bpm: 92, instrument: "piano", pattern: 17, swing: 0.62 } },
48
+ { description: "Ambient pad wash, no drums", args: { preset: "Cmaj9 Am9 Fmaj9 Gsus4", bpm: 55, enableDrums: false, padVolume: 0.24, reverbMix: 0.35 } },
49
+ { description: "Transposed to key of G", args: { preset: "I-V-vi-IV (Pop)", transpose: 7, bpm: 120 } }
50
+ ]
51
+ };
52
+ process.stdout.write(JSON.stringify(spec, null, 2) + '\n');
53
+ process.exit(0);
54
+ }
55
+
56
+ // ── --list: all available options as JSON ──
57
+ if (flag('list')) {
58
+ process.stdout.write(JSON.stringify({
59
+ presets: engine.PRESETS,
60
+ patterns: engine.PATTERNS,
61
+ instruments: engine.INSTRUMENTS,
62
+ drums: engine.DRUMS,
63
+ rates: engine.RATES
64
+ }, null, 2) + '\n');
65
+ process.exit(0);
66
+ }
67
+
68
+ // ── --help: human-readable ──
69
+ if (flag('help') || flag('h') || args.length === 0) {
70
+ console.log(`
71
+ chord-synth — Generate chord progression WAV files
72
+
73
+ AGENT COMMANDS (machine-readable output):
74
+ --schema Full JSON tool specification with parameter schema
75
+ --list All available presets, patterns, instruments, drums as JSON
76
+ mcp Launch as MCP server (stdio JSON-RPC for Claude/Cursor/etc)
77
+
78
+ RENDER COMMANDS:
79
+ --preset "chords" Named preset or custom chords (required for single render)
80
+ --bpm N Tempo (default: 120)
81
+ --instrument NAME piano|guitar|violin|flute|clarinet|cello (default: piano)
82
+ --pattern N Arp pattern 0-21 (default: 1=Up)
83
+ --rate RATE 1/8, 1/16, 1/4, etc (default: 1/8)
84
+ --transpose N Semitones -11 to +11 (default: 0)
85
+ --drums PATTERN poprock|swing|bossanova|funk|rnb|8beat|none (default: poprock)
86
+ --loops N Repetitions (default: 2)
87
+ --output FILE Output WAV path (default: ./output.wav)
88
+ --json '{"key":"val"}' Full job as inline JSON
89
+ --batch FILE Batch render from JSON array file
90
+ --dry-run Validate batch file without rendering
91
+
92
+ QUICK EXAMPLES:
93
+ chord-synth --preset "C G Am F" --bpm 120 --output pop.wav
94
+ chord-synth --preset "ii-V-I (Jazz)" --instrument piano --drums swing --swing 0.62
95
+ chord-synth --json '{"preset":"Am F C/G G","bpm":100,"instrument":"violin"}'
96
+ chord-synth --batch render_jobs.json --output ./wavs/
97
+ chord-synth --schema | jq .input_schema # agent: read parameter spec
98
+ chord-synth mcp # start MCP server on stdio
99
+ `);
100
+ process.exit(0);
101
+ }
102
+
103
+ // ── --batch: batch render from JSON file ──
104
+ const batchFile = flagVal('batch');
105
+ if (batchFile) {
106
+ if (!fs.existsSync(batchFile)) { console.error(`File not found: ${batchFile}`); process.exit(1); }
107
+ const jobs = JSON.parse(fs.readFileSync(batchFile, 'utf8'));
108
+ const arr = Array.isArray(jobs) ? jobs : (jobs.RENDER_JOBS || jobs.jobs || [jobs]);
109
+ const outDir = flagVal('output') || (jobs.OUTPUT_DIR || './output');
110
+ const dryRun = flag('dry-run');
111
+
112
+ if (dryRun) {
113
+ console.log(JSON.stringify({ valid: true, jobs: arr.length, output_dir: outDir }));
114
+ process.exit(0);
115
+ }
116
+
117
+ fs.mkdirSync(outDir, { recursive: true });
118
+ console.error(`Rendering ${arr.length} jobs to ${outDir}...`);
119
+ const results = [];
120
+ for (let i = 0; i < arr.length; i++) {
121
+ const job = arr[i];
122
+ const filename = job.filename || `${String(i + 1).padStart(3, '0')}_output.wav`;
123
+ console.error(`[${i + 1}/${arr.length}] ${filename}`);
124
+ const wavData = engine.renderJob(job);
125
+ const outPath = path.join(outDir, filename);
126
+ fs.writeFileSync(outPath, wavData);
127
+ results.push({ filename, path: outPath, size: wavData.length });
128
+ }
129
+ // Output structured result to stdout (agent reads this)
130
+ process.stdout.write(JSON.stringify({ rendered: results.length, files: results }) + '\n');
131
+ process.exit(0);
132
+ }
133
+
134
+ // ── --json: inline JSON render ──
135
+ const jsonStr = flagVal('json');
136
+ if (jsonStr) {
137
+ const job = JSON.parse(jsonStr);
138
+ const outFile = flagVal('output') || 'output.wav';
139
+ fs.mkdirSync(path.dirname(path.resolve(outFile)), { recursive: true });
140
+ const wavData = engine.renderJob(job);
141
+ fs.writeFileSync(outFile, wavData);
142
+ process.stdout.write(JSON.stringify({ file: outFile, size: wavData.length }) + '\n');
143
+ process.exit(0);
144
+ }
145
+
146
+ // ── --preset: single render from flags ──
147
+ const preset = flagVal('preset');
148
+ if (preset) {
149
+ const job = {
150
+ preset,
151
+ bpm: flagNum('bpm', 120),
152
+ transpose: flagNum('transpose', 0),
153
+ pattern: flagNum('pattern', 1),
154
+ rate: flagVal('rate') || '1/8',
155
+ instrument: flagVal('instrument') || 'piano',
156
+ octaveRange: flagNum('octave-range', 1),
157
+ baseOctave: flagNum('base-octave', 3),
158
+ gatePercent: flagNum('gate', 0.9),
159
+ velocity: flagNum('velocity', 90),
160
+ velRandom: flagNum('vel-random', 5),
161
+ accentEvery: flagNum('accent-every', 4),
162
+ accentAmount: flagNum('accent-amount', 20),
163
+ firstBeatAccent: !flag('no-first-accent'),
164
+ swing: flagNum('swing', 0.5),
165
+ loops: flagNum('loops', 2),
166
+ enablePad: !flag('no-pad'),
167
+ padOctave: flagNum('pad-octave', 4),
168
+ padVolume: flagNum('pad-volume', 0.12),
169
+ enableBass: !flag('no-bass'),
170
+ enableDrums: !flag('no-drums'),
171
+ drumPattern: flagVal('drums') || 'poprock',
172
+ reverbMix: flagNum('reverb', 0.18),
173
+ masterVolume: flagNum('volume', 0.85),
174
+ beatsPerChord: flagNum('beats-per-chord', undefined),
175
+ };
176
+
177
+ const outFile = flagVal('output') || 'output.wav';
178
+ fs.mkdirSync(path.dirname(path.resolve(outFile)), { recursive: true });
179
+ const wavData = engine.renderJob(job);
180
+ fs.writeFileSync(outFile, wavData);
181
+ process.stdout.write(JSON.stringify({ file: outFile, size: wavData.length, preset, bpm: job.bpm }) + '\n');
182
+ process.exit(0);
183
+ }
184
+
185
+ console.error('No action specified. Run with --help or --schema.');
186
+ process.exit(1);
187
+
188
+ } // end else (not mcp)