scribbletune 5.5.0 → 5.5.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 +70 -1
- package/dist/cli.cjs +82 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,19 @@ npm install scribbletune
|
|
|
39
39
|
npx scribbletune --help
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
Quick command examples:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Use built file directly from repo (before publish)
|
|
46
|
+
node dist/cli.cjs --help
|
|
47
|
+
|
|
48
|
+
# Use local package binary
|
|
49
|
+
npx scribbletune --help
|
|
50
|
+
|
|
51
|
+
# If installed globally
|
|
52
|
+
scribbletune --help
|
|
53
|
+
```
|
|
54
|
+
|
|
42
55
|
#### Command format
|
|
43
56
|
|
|
44
57
|
```bash
|
|
@@ -47,6 +60,20 @@ scribbletune --chord <root> <mode> <progression|random> <pattern> [subdiv] [opti
|
|
|
47
60
|
scribbletune --arp <root> <mode> <progression|random> <pattern> [subdiv] [options]
|
|
48
61
|
```
|
|
49
62
|
|
|
63
|
+
Progression input rules for `--chord` and `--arp`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
1645 # degree digits
|
|
67
|
+
"I IV vi V" # roman numerals (space separated)
|
|
68
|
+
I,IV,vi,V # roman numerals (comma separated)
|
|
69
|
+
random # generated progression
|
|
70
|
+
CM-FM-Am-GM # explicit chord names
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Notes:
|
|
74
|
+
- Hyphenated romans like `I-IV-vi-V` are not supported currently.
|
|
75
|
+
- For explicit chords (`CM-FM-Am-GM`), `root` and `mode` are currently ignored.
|
|
76
|
+
|
|
50
77
|
Common options:
|
|
51
78
|
|
|
52
79
|
```bash
|
|
@@ -58,6 +85,23 @@ Common options:
|
|
|
58
85
|
--amp <0-127>
|
|
59
86
|
--accent <x--x...>
|
|
60
87
|
--accent-low <0-127>
|
|
88
|
+
--fit-pattern # explicit enable (already enabled by default)
|
|
89
|
+
--no-fit-pattern # disable automatic pattern fitting
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Note: if your pattern uses `[` and `]` (for subdivisions), quote it in shell:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
scribbletune --arp C3 major 1 'x-x[xx]-x-[xx]' 16n
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Pattern helpers:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
x.repeat(4) # -> xxxx
|
|
102
|
+
'x-x[xx]'.repeat(2)
|
|
103
|
+
2(x-x[xx]) # prefix repeat shorthand
|
|
104
|
+
(x-x[xx])2 # suffix repeat shorthand
|
|
61
105
|
```
|
|
62
106
|
|
|
63
107
|
#### `--riff` examples
|
|
@@ -68,6 +112,9 @@ scribbletune --riff C3 phrygian x-xRx_RR --outfile riff.mid
|
|
|
68
112
|
|
|
69
113
|
# With octave shift and motif
|
|
70
114
|
scribbletune --riff C3 phrygian x-xRx_RR 0 AABC --sizzle sin 2 --outfile riff-aabc.mid
|
|
115
|
+
|
|
116
|
+
# Pattern with subdivisions (quote [] in shell)
|
|
117
|
+
scribbletune --riff C3 phrygian 'x-x[xx]-x-[xx]' 0 AABC --outfile riff-subdiv.mid
|
|
71
118
|
```
|
|
72
119
|
|
|
73
120
|
#### `--chord` examples
|
|
@@ -84,6 +131,9 @@ scribbletune --chord C3 major random xxxx 1m --outfile chords-random.mid
|
|
|
84
131
|
|
|
85
132
|
# Explicit chord names (root/mode currently ignored for this style)
|
|
86
133
|
scribbletune --chord C3 major CM-FM-Am-GM xxxx 1m --outfile chords-explicit.mid
|
|
134
|
+
|
|
135
|
+
# Subdivisions in pattern
|
|
136
|
+
scribbletune --chord C3 major I,IV,vi,V 'x-x[xx]-x-[xx]' 8n --outfile chords-subdiv.mid
|
|
87
137
|
```
|
|
88
138
|
|
|
89
139
|
#### `--arp` examples
|
|
@@ -92,10 +142,29 @@ scribbletune --chord C3 major CM-FM-Am-GM xxxx 1m --outfile chords-explicit.mid
|
|
|
92
142
|
# Arp from degree progression
|
|
93
143
|
scribbletune --arp C3 major 1736 xxxx 1m --sizzle cos 4 --outfile arp-1736.mid
|
|
94
144
|
|
|
145
|
+
# Single degree "1" means tonic chord in the chosen key/mode
|
|
146
|
+
scribbletune --arp C3 major 1 xxxx 4n --outfile arp-degree-1.mid
|
|
147
|
+
|
|
95
148
|
# Arp from explicit chords
|
|
96
|
-
scribbletune --arp C3 major CM-FM-Am-GM xxxx 1m --count 4 --order
|
|
149
|
+
scribbletune --arp C3 major CM-FM-Am-GM xxxx 1m --count 4 --order 1234 --outfile arp-explicit.mid
|
|
150
|
+
|
|
151
|
+
# Custom note order inside each arpeggiated chord (one-based)
|
|
152
|
+
scribbletune --arp C3 major 1 xxxx 4n --order 2143 --outfile arp-order-2143.mid
|
|
153
|
+
|
|
154
|
+
# Same custom order using local dist build
|
|
155
|
+
node dist/cli.cjs --arp C3 major 1 xxxx 4n --order 2143 --outfile arp-order-local.mid
|
|
156
|
+
|
|
157
|
+
# Auto-fit is default (single x expands to full generated arp length)
|
|
158
|
+
scribbletune --arp C3 major 1736 x 4n --outfile arp-fit-default.mid
|
|
159
|
+
|
|
160
|
+
# Disable auto-fit if you want a short clip
|
|
161
|
+
scribbletune --arp C3 major 1736 x 4n --no-fit-pattern --outfile arp-no-fit.mid
|
|
97
162
|
```
|
|
98
163
|
|
|
164
|
+
`--order` behavior:
|
|
165
|
+
- One-based order is supported (`1234`, `2143`) and is recommended.
|
|
166
|
+
- Zero-based order is also accepted for backward compatibility (`0123`, `1032`).
|
|
167
|
+
|
|
99
168
|
Run `scribbletune --help` to see the latest CLI usage text.
|
|
100
169
|
|
|
101
170
|
### Generate a MIDI file (Node.js)
|
package/dist/cli.cjs
CHANGED
|
@@ -568,6 +568,8 @@ Options:
|
|
|
568
568
|
--accent-low <0-127> Accent low level
|
|
569
569
|
--count <2-8> Arp note count (arp command only)
|
|
570
570
|
--order <digits> Arp order string (arp command only)
|
|
571
|
+
--fit-pattern Repeat pattern until it can consume all generated notes (default)
|
|
572
|
+
--no-fit-pattern Disable automatic pattern fitting
|
|
571
573
|
-h, --help Show this help
|
|
572
574
|
`;
|
|
573
575
|
var romanByDigit = (progDigits, mode) => {
|
|
@@ -606,6 +608,53 @@ var parseProgression = (root, mode, progressionInput) => {
|
|
|
606
608
|
}
|
|
607
609
|
return progressionInput.replace(/-/g, " ");
|
|
608
610
|
};
|
|
611
|
+
var normalizeArpOrder = (order) => {
|
|
612
|
+
if (!/^\d+$/.test(order)) {
|
|
613
|
+
throw new TypeError("Invalid value for --order");
|
|
614
|
+
}
|
|
615
|
+
if (order.includes("0")) {
|
|
616
|
+
return order;
|
|
617
|
+
}
|
|
618
|
+
return order.split("").map((ch) => {
|
|
619
|
+
const n = Number(ch);
|
|
620
|
+
if (n < 1) {
|
|
621
|
+
throw new TypeError("Invalid value for --order");
|
|
622
|
+
}
|
|
623
|
+
return String(n - 1);
|
|
624
|
+
}).join("");
|
|
625
|
+
};
|
|
626
|
+
var expandPatternSyntax = (pattern) => {
|
|
627
|
+
const jsRepeatQuoted = pattern.match(/^(['"])(.+)\1\.repeat\((\d+)\)$/);
|
|
628
|
+
if (jsRepeatQuoted) {
|
|
629
|
+
return jsRepeatQuoted[2].repeat(Number(jsRepeatQuoted[3]));
|
|
630
|
+
}
|
|
631
|
+
const jsRepeatUnquoted = pattern.match(/^(.+)\.repeat\((\d+)\)$/);
|
|
632
|
+
if (jsRepeatUnquoted) {
|
|
633
|
+
return jsRepeatUnquoted[1].repeat(Number(jsRepeatUnquoted[2]));
|
|
634
|
+
}
|
|
635
|
+
const prefixRepeat = pattern.match(/^(\d+)\((.+)\)$/);
|
|
636
|
+
if (prefixRepeat) {
|
|
637
|
+
return prefixRepeat[2].repeat(Number(prefixRepeat[1]));
|
|
638
|
+
}
|
|
639
|
+
const suffixRepeat = pattern.match(/^\((.+)\)(\d+)$/);
|
|
640
|
+
if (suffixRepeat) {
|
|
641
|
+
return suffixRepeat[1].repeat(Number(suffixRepeat[2]));
|
|
642
|
+
}
|
|
643
|
+
return pattern;
|
|
644
|
+
};
|
|
645
|
+
var countPatternSteps = (pattern) => (pattern.match(/[xR]/g) || []).length;
|
|
646
|
+
var fitPatternToNoteCount = (pattern, noteCount) => {
|
|
647
|
+
const stepCount = countPatternSteps(pattern);
|
|
648
|
+
if (!stepCount || stepCount >= noteCount) {
|
|
649
|
+
return pattern;
|
|
650
|
+
}
|
|
651
|
+
const reps = Math.ceil(noteCount / stepCount);
|
|
652
|
+
return pattern.repeat(reps);
|
|
653
|
+
};
|
|
654
|
+
var resolvePattern = (rawPattern, noteCount, fitPattern = false) => {
|
|
655
|
+
const expanded = expandPatternSyntax(rawPattern);
|
|
656
|
+
return fitPattern ? fitPatternToNoteCount(expanded, noteCount) : expanded;
|
|
657
|
+
};
|
|
609
658
|
var parseCliArgs = (argv) => {
|
|
610
659
|
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
|
|
611
660
|
return null;
|
|
@@ -623,7 +672,8 @@ var parseCliArgs = (argv) => {
|
|
|
623
672
|
const options = {
|
|
624
673
|
command: commandArg,
|
|
625
674
|
positionals,
|
|
626
|
-
outfile: "music.mid"
|
|
675
|
+
outfile: "music.mid",
|
|
676
|
+
fitPattern: true
|
|
627
677
|
};
|
|
628
678
|
let i = 1;
|
|
629
679
|
while (i < argv.length) {
|
|
@@ -701,6 +751,16 @@ var parseCliArgs = (argv) => {
|
|
|
701
751
|
i += 2;
|
|
702
752
|
continue;
|
|
703
753
|
}
|
|
754
|
+
if (token === "--fit-pattern") {
|
|
755
|
+
options.fitPattern = true;
|
|
756
|
+
i += 1;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (token === "--no-fit-pattern") {
|
|
760
|
+
options.fitPattern = false;
|
|
761
|
+
i += 1;
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
704
764
|
throw new TypeError(`Unknown option "${token}"`);
|
|
705
765
|
}
|
|
706
766
|
return options;
|
|
@@ -711,8 +771,7 @@ var baseClipParams = (parsed) => {
|
|
|
711
771
|
sizzleReps: parsed.sizzleReps,
|
|
712
772
|
amp: parsed.amp,
|
|
713
773
|
accent: parsed.accent,
|
|
714
|
-
accentLow: parsed.accentLow
|
|
715
|
-
subdiv: parsed.subdiv
|
|
774
|
+
accentLow: parsed.accentLow
|
|
716
775
|
};
|
|
717
776
|
};
|
|
718
777
|
var makeRiff = (parsed) => {
|
|
@@ -731,10 +790,15 @@ var makeRiff = (parsed) => {
|
|
|
731
790
|
}
|
|
732
791
|
return riffScale[idx % riffScale.length];
|
|
733
792
|
}) : riffScale;
|
|
793
|
+
const resolvedPattern = resolvePattern(
|
|
794
|
+
pattern,
|
|
795
|
+
riffNotes.length,
|
|
796
|
+
parsed.fitPattern
|
|
797
|
+
);
|
|
734
798
|
return clip({
|
|
735
799
|
notes: riffNotes,
|
|
736
800
|
randomNotes: riffScale,
|
|
737
|
-
pattern,
|
|
801
|
+
pattern: resolvedPattern,
|
|
738
802
|
...baseClipParams(parsed)
|
|
739
803
|
});
|
|
740
804
|
};
|
|
@@ -746,11 +810,13 @@ var makeChord = (parsed) => {
|
|
|
746
810
|
);
|
|
747
811
|
}
|
|
748
812
|
const chords = parseProgression(root, mode, progressionInput);
|
|
813
|
+
const chordCount = chords.trim().split(/\s+/).length;
|
|
814
|
+
const resolvedPattern = resolvePattern(pattern, chordCount, parsed.fitPattern);
|
|
749
815
|
return clip({
|
|
750
816
|
notes: chords,
|
|
751
|
-
pattern,
|
|
752
|
-
|
|
753
|
-
|
|
817
|
+
pattern: resolvedPattern,
|
|
818
|
+
...baseClipParams(parsed),
|
|
819
|
+
subdiv: parsed.subdiv || subdiv
|
|
754
820
|
});
|
|
755
821
|
};
|
|
756
822
|
var makeArp = (parsed) => {
|
|
@@ -764,13 +830,18 @@ var makeArp = (parsed) => {
|
|
|
764
830
|
const arpNotes = arp({
|
|
765
831
|
chords,
|
|
766
832
|
count: parsed.count || 4,
|
|
767
|
-
order: parsed.order
|
|
833
|
+
order: parsed.order ? normalizeArpOrder(parsed.order) : "0123"
|
|
768
834
|
});
|
|
835
|
+
const resolvedPattern = resolvePattern(
|
|
836
|
+
pattern,
|
|
837
|
+
arpNotes.length,
|
|
838
|
+
parsed.fitPattern
|
|
839
|
+
);
|
|
769
840
|
return clip({
|
|
770
841
|
notes: arpNotes,
|
|
771
|
-
pattern,
|
|
772
|
-
|
|
773
|
-
|
|
842
|
+
pattern: resolvedPattern,
|
|
843
|
+
...baseClipParams(parsed),
|
|
844
|
+
subdiv: parsed.subdiv || subdiv
|
|
774
845
|
});
|
|
775
846
|
};
|
|
776
847
|
var runCli = (argv, deps) => {
|