bitcomposer 1.0.0rc1__tar.gz

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 (45) hide show
  1. bitcomposer-1.0.0rc1/LICENSE +21 -0
  2. bitcomposer-1.0.0rc1/PKG-INFO +190 -0
  3. bitcomposer-1.0.0rc1/README.md +170 -0
  4. bitcomposer-1.0.0rc1/bitcomposer/__init__.py +6 -0
  5. bitcomposer-1.0.0rc1/bitcomposer/__main__.py +5 -0
  6. bitcomposer-1.0.0rc1/bitcomposer/arp.py +192 -0
  7. bitcomposer-1.0.0rc1/bitcomposer/bass.py +173 -0
  8. bitcomposer-1.0.0rc1/bitcomposer/builder.py +394 -0
  9. bitcomposer-1.0.0rc1/bitcomposer/cli.py +266 -0
  10. bitcomposer-1.0.0rc1/bitcomposer/composer.py +705 -0
  11. bitcomposer-1.0.0rc1/bitcomposer/drums.py +291 -0
  12. bitcomposer-1.0.0rc1/bitcomposer/echo.py +154 -0
  13. bitcomposer-1.0.0rc1/bitcomposer/gui.py +122 -0
  14. bitcomposer-1.0.0rc1/bitcomposer/harmony.py +330 -0
  15. bitcomposer-1.0.0rc1/bitcomposer/instruments.py +216 -0
  16. bitcomposer-1.0.0rc1/bitcomposer/it_format.py +383 -0
  17. bitcomposer-1.0.0rc1/bitcomposer/melody.py +523 -0
  18. bitcomposer-1.0.0rc1/bitcomposer/mix.py +65 -0
  19. bitcomposer-1.0.0rc1/bitcomposer/names.py +158 -0
  20. bitcomposer-1.0.0rc1/bitcomposer/packs/__init__.py +30 -0
  21. bitcomposer-1.0.0rc1/bitcomposer/packs/evermore.py +361 -0
  22. bitcomposer-1.0.0rc1/bitcomposer/packs/ffvi.py +478 -0
  23. bitcomposer-1.0.0rc1/bitcomposer/packs/mana.py +376 -0
  24. bitcomposer-1.0.0rc1/bitcomposer/packs/megamanx.py +400 -0
  25. bitcomposer-1.0.0rc1/bitcomposer/pattern.py +511 -0
  26. bitcomposer-1.0.0rc1/bitcomposer/samples/__init__.py +386 -0
  27. bitcomposer-1.0.0rc1/bitcomposer/samples/chip.py +475 -0
  28. bitcomposer-1.0.0rc1/bitcomposer/samples/dsp.py +461 -0
  29. bitcomposer-1.0.0rc1/bitcomposer/samples/genesis.py +372 -0
  30. bitcomposer-1.0.0rc1/bitcomposer/samples/snes.py +244 -0
  31. bitcomposer-1.0.0rc1/bitcomposer/structure.py +399 -0
  32. bitcomposer-1.0.0rc1/bitcomposer/theory.py +258 -0
  33. bitcomposer-1.0.0rc1/bitcomposer.egg-info/PKG-INFO +190 -0
  34. bitcomposer-1.0.0rc1/bitcomposer.egg-info/SOURCES.txt +43 -0
  35. bitcomposer-1.0.0rc1/bitcomposer.egg-info/dependency_links.txt +1 -0
  36. bitcomposer-1.0.0rc1/bitcomposer.egg-info/entry_points.txt +3 -0
  37. bitcomposer-1.0.0rc1/bitcomposer.egg-info/top_level.txt +1 -0
  38. bitcomposer-1.0.0rc1/pyproject.toml +43 -0
  39. bitcomposer-1.0.0rc1/setup.cfg +4 -0
  40. bitcomposer-1.0.0rc1/tests/test_arrangement.py +272 -0
  41. bitcomposer-1.0.0rc1/tests/test_format.py +149 -0
  42. bitcomposer-1.0.0rc1/tests/test_packs.py +152 -0
  43. bitcomposer-1.0.0rc1/tests/test_pattern.py +44 -0
  44. bitcomposer-1.0.0rc1/tests/test_samples.py +91 -0
  45. bitcomposer-1.0.0rc1/tests/test_theory.py +62 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CatDogBark
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.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: bitcomposer
3
+ Version: 1.0.0rc1
4
+ Summary: Procedural 16-bit chiptune music generator — creates Impulse Tracker (.it) files
5
+ Author-email: CatDogBark <186357707+CatDogBark@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/CatDogBark/bitComposer
8
+ Project-URL: Repository, https://github.com/CatDogBark/bitComposer
9
+ Project-URL: Issues, https://github.com/CatDogBark/bitComposer/issues
10
+ Project-URL: Changelog, https://github.com/CatDogBark/bitComposer/blob/main/CHANGELOG.md
11
+ Keywords: chiptune,tracker,music,procedural,16-bit,snes,genesis,impulse-tracker
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Multimedia :: Sound/Audio
15
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Sound Synthesis
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # bitComposer
22
+
23
+ [![CI](https://github.com/CatDogBark/bitComposer/actions/workflows/ci.yml/badge.svg)](https://github.com/CatDogBark/bitComposer/actions/workflows/ci.yml)
24
+ [![Release](https://img.shields.io/github/v/release/CatDogBark/bitComposer)](https://github.com/CatDogBark/bitComposer/releases/latest)
25
+ ![License](https://img.shields.io/badge/license-MIT-green)
26
+
27
+ Procedural 16-bit game music. Pure Python, no dependencies, writes Impulse
28
+ Tracker `.it` modules you can play in any tracker player, and a KDE Plasma
29
+ player that generates while you listen.
30
+
31
+ Every run composes a new song: a motif-based melody with call-and-response
32
+ phrasing, voice-led harmony, bass, arpeggio and drums, arranged into a form
33
+ (verse–chorus, AABA, rondo, through-composed) with a drawn intro, an energy
34
+ curve and a real ending, titled after what it drew, between 1:15 and 3:30
35
+ long. Seven sounds, picked with `--sound`:
36
+
37
+ - **snes** — sampled-style voices built the way the SNES made sound, through a BRR codec model and the SNES echo.
38
+ - **ffvi**, **mana**, **evermore**, **megamanx** — style packs: an instrument set rebuilt from each game's own sound chip (every sample profiled, nothing copied) plus the arrangement habits of its score. Details under [Sounds](#sounds).
39
+ - **genesis** — FM bells, brass, organ and bass.
40
+ - **chip** — square, pulse and triangle waves. Closest to NES and Game Boy.
41
+
42
+ **[Listen in your browser](https://catdogbark.github.io/bitComposer/)** — the actual modules, played live by libopenmpt in WebAssembly: one song per sound, straight from the generator, regenerated with each release.
43
+
44
+ **Reproducible, not frozen.** The same seed on the same version gives the same song, byte for byte. A new version may change what a seed draws, and the song message records the version that wrote the file.
45
+
46
+ ## Install
47
+
48
+ From PyPI, with [pipx](https://pipx.pypa.io/) so it lands on your PATH
49
+ without touching the system Python (Debian and Fedora refuse a plain
50
+ `pip install` there):
51
+
52
+ ```bash
53
+ pipx install bitcomposer
54
+ bitcomposer -d out # writes out/<Title>.it
55
+ bitcomposer -d out -s 42 -v # reproducible, with details
56
+ ```
57
+
58
+ On Windows, the release zip needs no Python at all; see [Windows](#windows).
59
+ From a clone, for development:
60
+
61
+ ```bash
62
+ git clone https://github.com/CatDogBark/bitComposer.git
63
+ cd bitComposer
64
+ python3 -m bitcomposer -d out
65
+ ```
66
+
67
+ Play the result with [OpenMPT](https://openmpt.org/) (Windows),
68
+ [Schism Tracker](https://schismtracker.org/), VLC, Audacious, `xmp`, or
69
+ anything built on libopenmpt.
70
+
71
+ Python 3.10 or newer, no dependencies. The window (`bitcomposer-gui`) uses
72
+ tkinter, which Debian and Ubuntu ship separately as `python3-tk`.
73
+
74
+ ## Sounds
75
+
76
+ A style pack pairs an instrument set with the arrangement habits of the score it imitates. Each is built from the game's SPC snapshots: every sample decoded and profiled, matched to the community's instrument tables where they exist, and re-synthesised; nothing from the games is in the repository.
77
+
78
+ - `ffvi`: every voice modelled on the game's own sample bank and named by its instrument table: the chorused string section, flutes in chords, the dark synth lead of Locke and the Returners, trumpet, trombone, oboe, Terra's and Cyan's pan flutes, cello, choir, pipe organ and rock organ, the Aria's soprano, the Battle theme's distortion guitar, piano, harp, picked string, acoustic guitar, tubular bell, xylophone, a bass guitar and a kit of its own (kick, snare, hats, cymbal, timpani, wood block), with ostinato strings, a brass counter-line in the chorus, timpani rolls into sections and the game's short, wet echo.
79
+ - `mana`: every voice modelled on the game's own sample bank: a soft slow-breathed flute, bright chorused strings, an 'ah' choir, a high whistle, soft and synth brass, marimba, vibraphone, electric piano, tubular bell, reed, accordion, glass and sine pads, fingered, electric and slap basses and a kit of its own (kick, snare, hats, crash, tom, castanet), with major and pentatonic keys, syncopated harmony, bouncy arps, light swing, the kick and snare set back and the game's one-row echo.
80
+ - `evermore`: every voice modelled on the game's own sample bank: a slow bright string swell, a warm pad, a dark pad, a glass bell, a breathy flute, a reed, an "oo" choir, harp, tinker music box, piano, pluck and kalimba, a textured drone and a wind loop, a bass, and a quiet kit (soft kick, frame drum, brushes, hat, cymbal, wood ping), with slow tempos, sustained harmony, a drone bass, sparse or no drums, long linear forms and the game's sparing 80 ms echo.
81
+ - `megamanx`: every voice modelled on the game's own sample bank and named by it: a driven, chorused lead guitar, a chugging rhythm guitar on power chords, the overdrive guitar for arps and counter-lines, synth brass, strings, a guitar swell pad, a saw pad, organ, the boomer synth lead, square and bright leads, a hollow wave, a bell synth, an acoustic pluck, the orchestra hit for stabs, slap and synth basses on a riff, and a tight kit, at fast tempos in minor keys with the game's 48 ms slapback echo.
82
+
83
+ `--sound` picks a palette or a pack in one word; left to chance, the four packs together weigh as much as plain snes.
84
+
85
+ - **snes** — sampled-style voices rendered the way the SNES actually made sound: strings, brass, a formant choir, flute, organ, e-piano, bells, plucks and a gated drum kit at 16 kHz with loop points, passed through a BRR codec model for the grain, with the lead, harmony and arp through an echo modelled on the SNES unit's result: fading, darkening repeats on a beat fraction, drawn per song.
86
+ - **genesis** — FM bells, brass, organ and bass.
87
+ - **chip** — square, pulse and triangle waves.
88
+
89
+ ## What you can steer
90
+
91
+ Everything defaults to `random`.
92
+
93
+ ```
94
+ --sound chip|snes|genesis|ffvi|mana|evermore|megamanx the sound in one word: a palette or a style pack (overrides the two below)
95
+ --style chip|snes|genesis sound palette
96
+ --pack ffvi|mana|evermore|megamanx|none|random style pack: an instrument set with the habits of the scores it imitates (a pack implies snes)
97
+ --echo on|off|random echo (random: 70% of snes songs, never chip or genesis)
98
+ --echo-wet light|normal|wet|random how much of the lead, harmony and arp the echo carries
99
+ --echo-delay eighth|dotted|quarter|slap|row|random beat fraction between repeats, a 48 ms slap, or one row (random: weighted by tempo)
100
+ --echo-stereo bounce|centre|random repeats alternate sides, or sit under their source
101
+ --keys moving|home moving: choruses/bridges may change key; home: stay put
102
+ --tempo slow|medium|fast 80–110 / 110–140 / 140–180 BPM
103
+ --energy chill|normal|intense density and arrangement weight
104
+ --scale minor|major|pentatonic
105
+ --form standard|aaba|rondo|short|linear
106
+ --drum-density sparse|normal|busy --no-fills --swing off|light|heavy
107
+ --melody simple|phrased phrased = motif-based, has a hook
108
+ --harmony-voicing thin|full two real voices, or three
109
+ --harmony-mode stabs|sustain|rhythmic
110
+ --bass-weight heavy|medium|light
111
+ --name title|timestamp file naming; --suffix adds a marker
112
+ -s SEED -n COUNT -o FILE -d DIR -v
113
+ ```
114
+
115
+ Some starting points:
116
+
117
+ ```bash
118
+ # Overworld — bright, sampled palette with echo
119
+ bitcomposer -d out --style snes --scale major --tempo medium --form standard
120
+
121
+ # FM boss fight
122
+ bitcomposer -d out --style genesis --tempo fast --energy intense --scale minor --drum-density busy --form short
123
+
124
+ # Game Boy flavour — square waves, thin harmony, no echo
125
+ bitcomposer -d out --style chip --echo off --harmony-voicing thin --bass-weight light --form short
126
+
127
+ # Credits roll
128
+ bitcomposer -d out --style snes --tempo slow --energy chill --scale major --drum-density sparse --no-fills --form linear
129
+ ```
130
+
131
+ ## How it works
132
+
133
+ 1. Pick a key and a scale weighted toward game music (minor pentatonic, dorian, harmonic minor, mixolydian…), then a progression for it. The tables include borrowed chords and secondary dominants — ♭VII, ♭VI, V/V — not just the diatonic set.
134
+ 2. Choose a form and its energy curve; decide which sections repeat. Repeated sections draw the same music from the same seed, so the hook comes back.
135
+ 3. Generate a motif (rhythm template plus contour) and vary it per bar in statement/answer pairs. Fourteen rhythm templates, six of them with 16th-note pushes and pickups.
136
+ 4. Each four-bar pattern holds a chord timeline; about half the time the back two bars move to a related chord. Melody resolves onto chord tones at each change. Harmony is two or three real voices placed near fixed anchors, so it steps rather than leaps and keeps the chord's third; the anchors are set per voice family (pads low and wide, brass and guitars mid and close), the top voice stays under the lead's line, and a stab holds for as long as the voice needs to speak. Bass and arp follow and rest with the section's energy.
137
+ 5. Drums come from one-bar templates by section and density, with fills, retriggered snare rolls and tick-level swing.
138
+ 6. Instruments are synthesized per song from the chosen palette and written as samples. SNES voices get attack-then-loop sustain and the BRR treatment.
139
+ 7. The echo: held lead notes and phrase endings come back as three fading, darkening repeats, the harmony and arp as lighter ones, each on return channels of their own, drums and bass dry. Everything is written as a 19-channel `.it` with the title and a song message (key, BPM, palette, form, seed, and a `layout:` line naming every channel's role) that trackers display.
140
+
141
+ Each song gets a seed-derived two-word title that leans with its scale:
142
+ *Haunted Orchard*, *Neon Causeway*, *Rusted Foundry*.
143
+
144
+ ## Measuring instead of guessing
145
+
146
+ `tools/analyze.py` parses `.it` files back from disk (stdlib only, independent
147
+ of the generator) and scores a batch on the things that correlate with
148
+ "sounds like a song": section reuse, motif repetition, rhythmic grid, chord
149
+ voicing variety, voice motion, effect vocabulary, drum density.
150
+
151
+ ```bash
152
+ python3 tools/analyze.py 'out/*.it' # metric table
153
+ python3 tools/analyze.py --grid out/song.it # tracker-style pattern dump
154
+ ```
155
+
156
+ [docs/ANALYSIS.md](docs/ANALYSIS.md) is the story of this project: the
157
+ output was once limited by three silent arrangement bugs that nine commits
158
+ of ear-checking never caught, and every improvement since has been measured
159
+ before and after. Read it before changing the generator.
160
+
161
+ Tests:
162
+
163
+ ```bash
164
+ python3 -m unittest discover -s tests -v
165
+ ```
166
+
167
+ ## Windows
168
+
169
+ Every release carries `bitComposer-windows.zip` with two executables built by GitHub Actions on every push and attached on the tag: `bitComposer.exe`, a small window (sound, echo, energy, tempo, how many, save folder, Generate), and `bitcomposer-cli.exe`, the command line. Songs are `.it` files; [OpenMPT](https://openmpt.org) plays them and will play a whole folder as a playlist. The executables are not code-signed, so SmartScreen asks once. With Python installed instead, `pipx install bitcomposer` gives the same two commands as `bitcomposer` and `bitcomposer-gui`. A Windows player with the Plasma widget's features is a 1.x item (see the roadmap).
170
+
171
+ ## Companion player (optional, Linux + KDE Plasma 6)
172
+
173
+ <img src="docs/images/widget.png" alt="The Plasma widget: now playing, transport, gain, generate and settings, playlist" width="420">
174
+
175
+ `companion/` holds a desktop player stack: a libopenmpt D-Bus daemon with
176
+ MPRIS2, a Plasma 6 panel widget with transport, gain, generator settings,
177
+ presets and a per-channel mixer, and an auto-generate "radio" mode that
178
+ composes the next track as the current one ends.
179
+
180
+ ```bash
181
+ ./companion/install.sh --check # see what it would do
182
+ ./companion/install.sh # install, enable the daemon
183
+ ```
184
+
185
+ See [docs/INSTALL.md](docs/INSTALL.md). The generator does not depend on
186
+ any of this.
187
+
188
+ ## License
189
+
190
+ MIT. Every instrument is synthesized; no samples were taken from any game.
@@ -0,0 +1,170 @@
1
+ # bitComposer
2
+
3
+ [![CI](https://github.com/CatDogBark/bitComposer/actions/workflows/ci.yml/badge.svg)](https://github.com/CatDogBark/bitComposer/actions/workflows/ci.yml)
4
+ [![Release](https://img.shields.io/github/v/release/CatDogBark/bitComposer)](https://github.com/CatDogBark/bitComposer/releases/latest)
5
+ ![License](https://img.shields.io/badge/license-MIT-green)
6
+
7
+ Procedural 16-bit game music. Pure Python, no dependencies, writes Impulse
8
+ Tracker `.it` modules you can play in any tracker player, and a KDE Plasma
9
+ player that generates while you listen.
10
+
11
+ Every run composes a new song: a motif-based melody with call-and-response
12
+ phrasing, voice-led harmony, bass, arpeggio and drums, arranged into a form
13
+ (verse–chorus, AABA, rondo, through-composed) with a drawn intro, an energy
14
+ curve and a real ending, titled after what it drew, between 1:15 and 3:30
15
+ long. Seven sounds, picked with `--sound`:
16
+
17
+ - **snes** — sampled-style voices built the way the SNES made sound, through a BRR codec model and the SNES echo.
18
+ - **ffvi**, **mana**, **evermore**, **megamanx** — style packs: an instrument set rebuilt from each game's own sound chip (every sample profiled, nothing copied) plus the arrangement habits of its score. Details under [Sounds](#sounds).
19
+ - **genesis** — FM bells, brass, organ and bass.
20
+ - **chip** — square, pulse and triangle waves. Closest to NES and Game Boy.
21
+
22
+ **[Listen in your browser](https://catdogbark.github.io/bitComposer/)** — the actual modules, played live by libopenmpt in WebAssembly: one song per sound, straight from the generator, regenerated with each release.
23
+
24
+ **Reproducible, not frozen.** The same seed on the same version gives the same song, byte for byte. A new version may change what a seed draws, and the song message records the version that wrote the file.
25
+
26
+ ## Install
27
+
28
+ From PyPI, with [pipx](https://pipx.pypa.io/) so it lands on your PATH
29
+ without touching the system Python (Debian and Fedora refuse a plain
30
+ `pip install` there):
31
+
32
+ ```bash
33
+ pipx install bitcomposer
34
+ bitcomposer -d out # writes out/<Title>.it
35
+ bitcomposer -d out -s 42 -v # reproducible, with details
36
+ ```
37
+
38
+ On Windows, the release zip needs no Python at all; see [Windows](#windows).
39
+ From a clone, for development:
40
+
41
+ ```bash
42
+ git clone https://github.com/CatDogBark/bitComposer.git
43
+ cd bitComposer
44
+ python3 -m bitcomposer -d out
45
+ ```
46
+
47
+ Play the result with [OpenMPT](https://openmpt.org/) (Windows),
48
+ [Schism Tracker](https://schismtracker.org/), VLC, Audacious, `xmp`, or
49
+ anything built on libopenmpt.
50
+
51
+ Python 3.10 or newer, no dependencies. The window (`bitcomposer-gui`) uses
52
+ tkinter, which Debian and Ubuntu ship separately as `python3-tk`.
53
+
54
+ ## Sounds
55
+
56
+ A style pack pairs an instrument set with the arrangement habits of the score it imitates. Each is built from the game's SPC snapshots: every sample decoded and profiled, matched to the community's instrument tables where they exist, and re-synthesised; nothing from the games is in the repository.
57
+
58
+ - `ffvi`: every voice modelled on the game's own sample bank and named by its instrument table: the chorused string section, flutes in chords, the dark synth lead of Locke and the Returners, trumpet, trombone, oboe, Terra's and Cyan's pan flutes, cello, choir, pipe organ and rock organ, the Aria's soprano, the Battle theme's distortion guitar, piano, harp, picked string, acoustic guitar, tubular bell, xylophone, a bass guitar and a kit of its own (kick, snare, hats, cymbal, timpani, wood block), with ostinato strings, a brass counter-line in the chorus, timpani rolls into sections and the game's short, wet echo.
59
+ - `mana`: every voice modelled on the game's own sample bank: a soft slow-breathed flute, bright chorused strings, an 'ah' choir, a high whistle, soft and synth brass, marimba, vibraphone, electric piano, tubular bell, reed, accordion, glass and sine pads, fingered, electric and slap basses and a kit of its own (kick, snare, hats, crash, tom, castanet), with major and pentatonic keys, syncopated harmony, bouncy arps, light swing, the kick and snare set back and the game's one-row echo.
60
+ - `evermore`: every voice modelled on the game's own sample bank: a slow bright string swell, a warm pad, a dark pad, a glass bell, a breathy flute, a reed, an "oo" choir, harp, tinker music box, piano, pluck and kalimba, a textured drone and a wind loop, a bass, and a quiet kit (soft kick, frame drum, brushes, hat, cymbal, wood ping), with slow tempos, sustained harmony, a drone bass, sparse or no drums, long linear forms and the game's sparing 80 ms echo.
61
+ - `megamanx`: every voice modelled on the game's own sample bank and named by it: a driven, chorused lead guitar, a chugging rhythm guitar on power chords, the overdrive guitar for arps and counter-lines, synth brass, strings, a guitar swell pad, a saw pad, organ, the boomer synth lead, square and bright leads, a hollow wave, a bell synth, an acoustic pluck, the orchestra hit for stabs, slap and synth basses on a riff, and a tight kit, at fast tempos in minor keys with the game's 48 ms slapback echo.
62
+
63
+ `--sound` picks a palette or a pack in one word; left to chance, the four packs together weigh as much as plain snes.
64
+
65
+ - **snes** — sampled-style voices rendered the way the SNES actually made sound: strings, brass, a formant choir, flute, organ, e-piano, bells, plucks and a gated drum kit at 16 kHz with loop points, passed through a BRR codec model for the grain, with the lead, harmony and arp through an echo modelled on the SNES unit's result: fading, darkening repeats on a beat fraction, drawn per song.
66
+ - **genesis** — FM bells, brass, organ and bass.
67
+ - **chip** — square, pulse and triangle waves.
68
+
69
+ ## What you can steer
70
+
71
+ Everything defaults to `random`.
72
+
73
+ ```
74
+ --sound chip|snes|genesis|ffvi|mana|evermore|megamanx the sound in one word: a palette or a style pack (overrides the two below)
75
+ --style chip|snes|genesis sound palette
76
+ --pack ffvi|mana|evermore|megamanx|none|random style pack: an instrument set with the habits of the scores it imitates (a pack implies snes)
77
+ --echo on|off|random echo (random: 70% of snes songs, never chip or genesis)
78
+ --echo-wet light|normal|wet|random how much of the lead, harmony and arp the echo carries
79
+ --echo-delay eighth|dotted|quarter|slap|row|random beat fraction between repeats, a 48 ms slap, or one row (random: weighted by tempo)
80
+ --echo-stereo bounce|centre|random repeats alternate sides, or sit under their source
81
+ --keys moving|home moving: choruses/bridges may change key; home: stay put
82
+ --tempo slow|medium|fast 80–110 / 110–140 / 140–180 BPM
83
+ --energy chill|normal|intense density and arrangement weight
84
+ --scale minor|major|pentatonic
85
+ --form standard|aaba|rondo|short|linear
86
+ --drum-density sparse|normal|busy --no-fills --swing off|light|heavy
87
+ --melody simple|phrased phrased = motif-based, has a hook
88
+ --harmony-voicing thin|full two real voices, or three
89
+ --harmony-mode stabs|sustain|rhythmic
90
+ --bass-weight heavy|medium|light
91
+ --name title|timestamp file naming; --suffix adds a marker
92
+ -s SEED -n COUNT -o FILE -d DIR -v
93
+ ```
94
+
95
+ Some starting points:
96
+
97
+ ```bash
98
+ # Overworld — bright, sampled palette with echo
99
+ bitcomposer -d out --style snes --scale major --tempo medium --form standard
100
+
101
+ # FM boss fight
102
+ bitcomposer -d out --style genesis --tempo fast --energy intense --scale minor --drum-density busy --form short
103
+
104
+ # Game Boy flavour — square waves, thin harmony, no echo
105
+ bitcomposer -d out --style chip --echo off --harmony-voicing thin --bass-weight light --form short
106
+
107
+ # Credits roll
108
+ bitcomposer -d out --style snes --tempo slow --energy chill --scale major --drum-density sparse --no-fills --form linear
109
+ ```
110
+
111
+ ## How it works
112
+
113
+ 1. Pick a key and a scale weighted toward game music (minor pentatonic, dorian, harmonic minor, mixolydian…), then a progression for it. The tables include borrowed chords and secondary dominants — ♭VII, ♭VI, V/V — not just the diatonic set.
114
+ 2. Choose a form and its energy curve; decide which sections repeat. Repeated sections draw the same music from the same seed, so the hook comes back.
115
+ 3. Generate a motif (rhythm template plus contour) and vary it per bar in statement/answer pairs. Fourteen rhythm templates, six of them with 16th-note pushes and pickups.
116
+ 4. Each four-bar pattern holds a chord timeline; about half the time the back two bars move to a related chord. Melody resolves onto chord tones at each change. Harmony is two or three real voices placed near fixed anchors, so it steps rather than leaps and keeps the chord's third; the anchors are set per voice family (pads low and wide, brass and guitars mid and close), the top voice stays under the lead's line, and a stab holds for as long as the voice needs to speak. Bass and arp follow and rest with the section's energy.
117
+ 5. Drums come from one-bar templates by section and density, with fills, retriggered snare rolls and tick-level swing.
118
+ 6. Instruments are synthesized per song from the chosen palette and written as samples. SNES voices get attack-then-loop sustain and the BRR treatment.
119
+ 7. The echo: held lead notes and phrase endings come back as three fading, darkening repeats, the harmony and arp as lighter ones, each on return channels of their own, drums and bass dry. Everything is written as a 19-channel `.it` with the title and a song message (key, BPM, palette, form, seed, and a `layout:` line naming every channel's role) that trackers display.
120
+
121
+ Each song gets a seed-derived two-word title that leans with its scale:
122
+ *Haunted Orchard*, *Neon Causeway*, *Rusted Foundry*.
123
+
124
+ ## Measuring instead of guessing
125
+
126
+ `tools/analyze.py` parses `.it` files back from disk (stdlib only, independent
127
+ of the generator) and scores a batch on the things that correlate with
128
+ "sounds like a song": section reuse, motif repetition, rhythmic grid, chord
129
+ voicing variety, voice motion, effect vocabulary, drum density.
130
+
131
+ ```bash
132
+ python3 tools/analyze.py 'out/*.it' # metric table
133
+ python3 tools/analyze.py --grid out/song.it # tracker-style pattern dump
134
+ ```
135
+
136
+ [docs/ANALYSIS.md](docs/ANALYSIS.md) is the story of this project: the
137
+ output was once limited by three silent arrangement bugs that nine commits
138
+ of ear-checking never caught, and every improvement since has been measured
139
+ before and after. Read it before changing the generator.
140
+
141
+ Tests:
142
+
143
+ ```bash
144
+ python3 -m unittest discover -s tests -v
145
+ ```
146
+
147
+ ## Windows
148
+
149
+ Every release carries `bitComposer-windows.zip` with two executables built by GitHub Actions on every push and attached on the tag: `bitComposer.exe`, a small window (sound, echo, energy, tempo, how many, save folder, Generate), and `bitcomposer-cli.exe`, the command line. Songs are `.it` files; [OpenMPT](https://openmpt.org) plays them and will play a whole folder as a playlist. The executables are not code-signed, so SmartScreen asks once. With Python installed instead, `pipx install bitcomposer` gives the same two commands as `bitcomposer` and `bitcomposer-gui`. A Windows player with the Plasma widget's features is a 1.x item (see the roadmap).
150
+
151
+ ## Companion player (optional, Linux + KDE Plasma 6)
152
+
153
+ <img src="docs/images/widget.png" alt="The Plasma widget: now playing, transport, gain, generate and settings, playlist" width="420">
154
+
155
+ `companion/` holds a desktop player stack: a libopenmpt D-Bus daemon with
156
+ MPRIS2, a Plasma 6 panel widget with transport, gain, generator settings,
157
+ presets and a per-channel mixer, and an auto-generate "radio" mode that
158
+ composes the next track as the current one ends.
159
+
160
+ ```bash
161
+ ./companion/install.sh --check # see what it would do
162
+ ./companion/install.sh # install, enable the daemon
163
+ ```
164
+
165
+ See [docs/INSTALL.md](docs/INSTALL.md). The generator does not depend on
166
+ any of this.
167
+
168
+ ## License
169
+
170
+ MIT. Every instrument is synthesized; no samples were taken from any game.
@@ -0,0 +1,6 @@
1
+ """bitcomposer — Procedural 16-bit chiptune music generator."""
2
+
3
+ __version__ = "1.0.0rc1"
4
+
5
+ # The registry first, then the packs that register their voices into it.
6
+ from . import samples, packs # noqa: E402,F401
@@ -0,0 +1,5 @@
1
+ """Allow `python3 -m bitcomposer` as a synonym for `python3 -m bitcomposer.cli`."""
2
+
3
+ from .cli import main
4
+
5
+ main()
@@ -0,0 +1,192 @@
1
+ """
2
+ Arpeggio figures.
3
+
4
+ The arp used to be a direction — up, down, up-down or random — stepping
5
+ through the chord tones every eighth note for the whole song, one setting
6
+ per song, with random velocity jitter. That reads as a sequencer running in
7
+ the background. Game arps are *figures*: short rhythmic shapes with accents
8
+ and rests that recur with the section, climb, and leave room for the lead.
9
+
10
+ A figure is one bar of (row, degree, accent) steps. `degree` indexes a
11
+ ladder of chord tones that starts on the chord's root in octave 4 and
12
+ climbs through the chord — so 0 is the root, 1 the next chord tone up, and
13
+ for a triad 3 is the root an octave higher. Negative degrees reach below.
14
+ Steps are 16 rows to the bar; a figure can be sparser than that.
15
+ """
16
+
17
+ import random
18
+
19
+ from .pattern import ROWS_PER_BAR
20
+
21
+ # Accent levels, as a fraction of the section's arp volume.
22
+ S, N, W = 1.0, 0.8, 0.62 # strong, normal, weak
23
+
24
+ FIGURES = {
25
+ # name: {"steps": [(row, degree, accent), ...], "energy": (lo, hi), "climb": bool}
26
+ "sparse_pulse": {
27
+ "steps": [(0, 0, S), (8, 3, N)],
28
+ "energy": (0.0, 0.55), "climb": False},
29
+ "quarter_up": {
30
+ "steps": [(0, 0, S), (4, 1, W), (8, 2, N), (12, 3, W)],
31
+ "energy": (0.0, 0.7), "climb": False},
32
+ "classic_up": {
33
+ "steps": [(0, 0, S), (2, 1, W), (4, 2, N), (6, 3, W),
34
+ (8, 0, S), (10, 1, W), (12, 2, N), (14, 3, W)],
35
+ "energy": (0.4, 1.0), "climb": True},
36
+ "up_down": {
37
+ "steps": [(0, 0, S), (2, 1, W), (4, 2, N), (6, 3, W),
38
+ (8, 2, S), (10, 1, W), (12, 0, N), (14, 1, W)],
39
+ "energy": (0.4, 1.0), "climb": False},
40
+ "broken_octave": { # 1-5-8-5, the Mega Man figure
41
+ "steps": [(0, 0, S), (2, 2, W), (4, 3, N), (6, 2, W),
42
+ (8, 0, S), (10, 2, W), (12, 3, N), (14, 2, W)],
43
+ "energy": (0.5, 1.0), "climb": True},
44
+ "pedal_bounce": { # root pedal against the chord above it
45
+ "steps": [(0, 0, S), (2, 3, W), (4, 0, N), (6, 4, W),
46
+ (8, 0, S), (10, 3, W), (12, 0, N), (14, 5, W)],
47
+ "energy": (0.5, 1.0), "climb": False},
48
+ "sync_push": { # dotted-eighth push
49
+ "steps": [(0, 0, S), (3, 2, W), (6, 3, N), (8, 0, S), (11, 2, W), (14, 3, N)],
50
+ "energy": (0.5, 1.0), "climb": True},
51
+ "offbeat_stabs": { # chord tones on the "and"s, bass has the beat
52
+ "steps": [(2, 3, N), (6, 2, N), (10, 3, S), (14, 4, N)],
53
+ "energy": (0.5, 1.0), "climb": False},
54
+ "gallop": {
55
+ "steps": [(0, 0, S), (3, 0, W), (4, 2, N), (8, 0, S), (11, 0, W), (12, 3, N)],
56
+ "energy": (0.7, 1.0), "climb": False},
57
+ "run16": { # sixteenth-note run over two octaves
58
+ "steps": [(r, d, (S if r % 8 == 0 else (N if r % 4 == 0 else W)))
59
+ for r, d in zip(range(16), [0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5])],
60
+ "energy": (0.8, 1.0), "climb": False},
61
+ "cascade": { # falling from the top of the chord
62
+ "steps": [(0, 5, S), (2, 4, W), (4, 3, N), (6, 2, W),
63
+ (8, 5, S), (10, 4, W), (12, 3, N), (14, 1, W)],
64
+ "energy": (0.5, 1.0), "climb": False},
65
+ }
66
+
67
+
68
+ def pick_figures(energy_pref: str, weights: dict[str, float] | None = None) -> dict[str, str]:
69
+ """Choose a figure per section role for the song.
70
+
71
+ Verse and chorus always differ, so the chorus arp is part of the hook
72
+ rather than the same loop as everything else. The bridge gets a third
73
+ figure; intros and outros use the sparse pulse. A style pack may pass
74
+ weights over figure names; figures it weights at zero are out and the
75
+ chorus draw is weighted.
76
+ """
77
+ if weights:
78
+ pool = [f for f in FIGURES if weights.get(f, 0) > 0]
79
+ chorus_pool = [f for f in pool if FIGURES[f]["energy"][0] >= 0.4] or pool
80
+ chorus = random.choices(chorus_pool, [weights[f] for f in chorus_pool], k=1)[0]
81
+ rest = [f for f in pool if f != chorus]
82
+ random.shuffle(rest)
83
+ return {"verse": rest[0], "chorus": chorus, "bridge": rest[1] if len(rest) > 1 else rest[0],
84
+ "intro": "sparse_pulse", "outro": "quarter_up"}
85
+ if energy_pref == "chill":
86
+ pool = ["quarter_up", "classic_up", "up_down", "pedal_bounce", "sparse_pulse"]
87
+ elif energy_pref == "intense":
88
+ pool = ["classic_up", "broken_octave", "sync_push", "gallop", "run16",
89
+ "offbeat_stabs", "cascade"]
90
+ else:
91
+ pool = ["classic_up", "up_down", "broken_octave", "pedal_bounce", "sync_push",
92
+ "offbeat_stabs", "cascade", "quarter_up"]
93
+ # The chorus runs at the top of the energy curve, so its figure must be
94
+ # one built for that; the verse and bridge can take anything in the pool.
95
+ chorus_pool = [f for f in pool if FIGURES[f]["energy"][0] >= 0.4]
96
+ chorus = random.choice(chorus_pool)
97
+ rest = [f for f in pool if f != chorus]
98
+ random.shuffle(rest)
99
+ return {"verse": rest[0], "chorus": chorus, "bridge": rest[1] if len(rest) > 1 else rest[0],
100
+ "intro": "sparse_pulse", "outro": "quarter_up"}
101
+
102
+
103
+ def _ladder(chord_notes: list[int], base_octave_root: int = 48) -> list[int]:
104
+ """Chord tones from the root placed in octave 4, climbing three octaves."""
105
+ pcs = sorted({n % 12 for n in chord_notes})
106
+ root_pc = chord_notes[0] % 12
107
+ pcs = pcs[pcs.index(root_pc):] + pcs[:pcs.index(root_pc)] # start on the root
108
+ root = base_octave_root + root_pc
109
+ while root >= base_octave_root + 12:
110
+ root -= 12
111
+ ladder = []
112
+ for octave in (-1, 0, 1, 2):
113
+ for pc in pcs:
114
+ n = root + ((pc - root_pc) % 12) + 12 * octave
115
+ ladder.append(n)
116
+ return ladder
117
+
118
+
119
+ def colour_tones(chord_notes: list[int], scale_notes: list[int]) -> list[int]:
120
+ """The 7th and the 9th above the chord's root, as the scale spells them.
121
+
122
+ Returned as pitch offsets from the root (10 or 11 for the 7th, 13 or 14
123
+ for the 9th, 15 means "none"), so they work in any octave. Game-music
124
+ arps on strings and harps nearly always carry one of these — it is what
125
+ makes a harp run sound like a score rather than a music box — but they
126
+ must come from the scale: a major 7th over a mixolydian chord, or a
127
+ natural 9th in phrygian, is a wrong note.
128
+ """
129
+ pcs = {n % 12 for n in scale_notes}
130
+ root = chord_notes[0] % 12
131
+ out = []
132
+ for cands in ((10, 11), (14, 13)): # 7th, then 9th
133
+ for off in cands:
134
+ if (root + off) % 12 in pcs:
135
+ out.append(off)
136
+ break
137
+ return out
138
+
139
+
140
+ def generate_arp(segments: list[tuple[int, int, list[int]]], figure_name: str,
141
+ energy: float, melody_rows: set[int] | None = None,
142
+ scale_notes: list[int] | None = None, colour: bool = False,
143
+ rows_per_bar: int = ROWS_PER_BAR) -> list[tuple[int, int, float]]:
144
+ """Realise a figure over a chord timeline.
145
+
146
+ segments is [(start_row, end_row, chord_notes), ...]. Returns
147
+ [(row, midi_note, accent), ...]. A bar may rest at low energy (never at
148
+ high); a climbing figure plays its even bars an octave up; and where the
149
+ melody is busy in a bar (melody_rows carries its onsets) the figure's
150
+ weak steps are dropped so the arp fills gaps rather than fighting the lead.
151
+
152
+ With colour (the SNES palette), the figure's octave-root step becomes
153
+ the chord's 7th or 9th, alternating by bar, drawn from the scale via
154
+ colour_tones. Lower steps keep the triad, so the shimmer sits on top.
155
+ """
156
+ fig = FIGURES[figure_name]
157
+ out = []
158
+ total = segments[-1][1] if segments else 0
159
+ bar_index = 0
160
+ for bar_start in range(0, total, rows_per_bar):
161
+ seg = next((s for s in segments if s[0] <= bar_start < s[1]), segments[0])
162
+ chord = seg[2]
163
+ ladder = _ladder(chord)
164
+ base = len(ladder) // 4 * 1 # index of the root in octave 4
165
+ per_octave = len(ladder) // 4
166
+ if energy < 0.8 and random.random() > energy + 0.25:
167
+ bar_index += 1
168
+ continue # this bar rests
169
+ shift = per_octave if (fig["climb"] and bar_index % 2 == 1) else 0
170
+ busy = False
171
+ if melody_rows:
172
+ onsets = sum(1 for r in melody_rows if bar_start <= r < bar_start + rows_per_bar)
173
+ busy = onsets >= 5
174
+ tints = colour_tones(chord, scale_notes) if (colour and scale_notes) else []
175
+ tint = tints[bar_index % len(tints)] if tints else None
176
+ for row_off, degree, accent in fig["steps"]:
177
+ if busy and accent == W:
178
+ continue
179
+ idx = base + degree + shift
180
+ if not (0 <= idx < len(ladder)):
181
+ continue
182
+ note = ladder[idx]
183
+ # The octave-root step (and any further octave of it) carries
184
+ # the colour tone, an octave below the root it replaces so the
185
+ # 7th/9th sits just above the triad rather than far on top.
186
+ if tint is not None and degree > 0 and degree % per_octave == 0:
187
+ note = ladder[idx - per_octave] + tint
188
+ row = bar_start + row_off
189
+ if row < total:
190
+ out.append((row, note, accent))
191
+ bar_index += 1
192
+ return out