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.
Files changed (3) hide show
  1. package/README.md +70 -1
  2. package/dist/cli.cjs +82 -11
  3. 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 0123 --outfile arp-explicit.mid
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
- subdiv: parsed.subdiv || subdiv,
753
- ...baseClipParams(parsed)
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 || "0123"
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
- subdiv: parsed.subdiv || subdiv,
773
- ...baseClipParams(parsed)
842
+ pattern: resolvedPattern,
843
+ ...baseClipParams(parsed),
844
+ subdiv: parsed.subdiv || subdiv
774
845
  });
775
846
  };
776
847
  var runCli = (argv, deps) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scribbletune",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "description": "Create music with JavaScript and Node.js!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",