Music: six more dungeon pieces and six surface pieces with flute, harp, birdsong and wind

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 22:58:41 +01:00
1 parent 86c707e151
commit d54de78f37
19 files changed
+166 -16

No files matched your search

+3 -1
View File
@@ -202,7 +202,9 @@ On phones: tap to use a tool, drag to mark or move, two fingers to zoom and turn
- **Rust**, compiled to **WebAssembly**, with [macroquad](https://github.com/not-fl3/macroquad) - one small module, no engine, no plugins. - **Rust**, compiled to **WebAssembly**, with [macroquad](https://github.com/not-fl3/macroquad) - one small module, no engine, no plugins.
- A real 3D isometric world in pixel art: chunked meshes, flickering point lights, a glow pass, particles, - A real 3D isometric world in pixel art: chunked meshes, flickering point lights, a glow pass, particles,
billboard sprites with eight viewing directions, every frame of it drawn by code - **no image files** except the font. billboard sprites with eight viewing directions, every frame of it drawn by code - **no image files** except the font.
- Procedural chiptune soundtrack as FastTracker 2 modules with its own XM replayer and synthesised sound effects. - Procedural chiptune soundtrack as FastTracker 2 modules with its own XM replayer and synthesised sound effects:
47 pieces around one leitmotif - in the dungeon dark and minor, above ground bright, with flute, harp, birdsong
and wind; the music follows the camera between the two worlds.
- A tiny Rust savegame server (axum, Argon2 passwords) - optional; without it the game uses savegame files. - A tiny Rust savegame server (axum, Argon2 passwords) - optional; without it the game uses savegame files.
- 18 languages: German, English, Spanish, Portuguese, Dutch, Danish, Norwegian, Swedish, Finnish, - 18 languages: German, English, Spanish, Portuguese, Dutch, Danish, Norwegian, Swedish, Finnish,
Russian, Croatian, Hungarian, Serbian, Romanian, Slovak, Czech, Italian and Greek – chosen from a Russian, Croatian, Hungarian, Serbian, Romanian, Slovak, Czech, Italian and Greek – chosen from a
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6 -1
View File
@@ -54,12 +54,17 @@ in hero waves from wave 4/6, buildable with a forge). The fortresses above groun
fortresses (fortress.rs) in a clearing. Final missions: campaign M30 and co-op M20; free play, versus matches fortresses (fortress.rs) in a clearing. Final missions: campaign M30 and co-op M20; free play, versus matches
and editor random maps have the option "half above ground". and editor random maps have the option "half above ground".
## 3b. Music (next milestone) ## 3b. Music – done (v202611-00039)
Six more underground tracks and a block of six surface tracks: the same leitmotif brighter and in major, synthetic Six more underground tracks and a block of six surface tracks: the same leitmotif brighter and in major, synthetic
birdsong and wind, flute leads and harp arpeggios. The music follows the camera (surface or underground) with a birdsong and wind, flute leads and harp arpeggios. The music follows the camera (surface or underground) with a
crossfade. crossfade.
Built in `tools/compose_music.py` (instruments flute, bird and wind; birds and wind on two extra channels only in
surface songs, so the older pieces stay byte for byte the same): build16-19, battle9-10, surface1-4,
surface_battle1-2; `audio::Category::Surface` / `SurfaceBattle`, chosen in `MusicDirector` when most of the
camera's surroundings lie above ground (4 s crossfade).
## 4. Additions ## 4. Additions
1. **Random events** – earthquake (tunnels cave in), lava eruption, flood, gold vein found. Rare 1. **Random events** – earthquake (tunnels cave in), lava eruption, flood, gold vein found. Rare
+5 -1
View File
@@ -21,6 +21,8 @@ pub enum Category {
Temple, Temple,
Victory, Victory,
Defeat, Defeat,
Surface, // above ground: flute, harp, birdsong
SurfaceBattle, // fighting above ground
} }
// indices of all tracks of a category, in play order // indices of all tracks of a category, in play order
@@ -221,7 +223,9 @@ mod tests {
dc_audio_init(48000.0); dc_audio_init(48000.0);
let build = tracks_in(Category::Build); let build = tracks_in(Category::Build);
let battle = tracks_in(Category::Battle); let battle = tracks_in(Category::Battle);
assert_eq!(build.len(), 15); assert_eq!(build.len(), 19);
assert_eq!(tracks_in(Category::Surface).len(), 4);
assert_eq!(tracks_in(Category::SurfaceBattle).len(), 2);
play_music(build[0], 0.0); play_music(build[0], 0.0);
let mut peak = 0.0f32; let mut peak = 0.0f32;
for k in 0..200 { for k in 0..200 {
+28 -2
View File
@@ -487,6 +487,19 @@ impl MusicDirector {
} }
n >= 4 n >= 4
}; };
// the camera looks at the land above ground: the music of the surface
let above = {
let (cx, cz) = (cam.0 as i32, cam.1 as i32);
let mut n = 0;
for y in (cz - 4..=cz + 4).step_by(2) {
for x in (cx - 4..=cx + 4).step_by(2) {
if map::inb(x, y) && g.map.sky(map::idx(x, y)) {
n += 1;
}
}
}
n >= 13
};
let want = if title { let want = if title {
C::Title C::Title
} else if g.state == 1 { } else if g.state == 1 {
@@ -508,11 +521,24 @@ impl MusicDirector {
} else { } else {
C::Build C::Build
}; };
let rotate = matches!(want, C::Build | C::Title) && self.current == Some(want) && audio::music_loops() >= 2; let want = match want {
C::Battle if above => C::SurfaceBattle,
C::Build | C::Torture | C::Temple if above => C::Surface,
w => w,
};
let rotate = matches!(want, C::Build | C::Title | C::Surface) && self.current == Some(want) && audio::music_loops() >= 2;
if self.current != Some(want) || rotate || audio::current_track().is_none() { if self.current != Some(want) || rotate || audio::current_track().is_none() {
let before = self.current;
self.current = Some(want); self.current = Some(want);
let t = self.pick(want); let t = self.pick(want);
let fade = if matches!(want, C::Victory | C::Defeat) { 1.0 } else { 2.5 }; // between the dungeon and the surface the music changes slowly
let fade = if matches!(want, C::Victory | C::Defeat) {
1.0
} else if matches!((before, want), (Some(C::Surface | C::SurfaceBattle), _) | (_, C::Surface | C::SurfaceBattle)) {
4.0
} else {
2.5
};
audio::play_music(t, fade); audio::play_music(t, fade);
} }
} }
+13 -1
View File
@@ -3,7 +3,7 @@
use crate::audio::Category; use crate::audio::Category;
pub const TRACKS: [(&str, Category, &[u8]); 35] = [ pub const TRACKS: [(&str, Category, &[u8]); 47] = [
("title", Category::Title, include_bytes!("../assets/music/title.xm")), ("title", Category::Title, include_bytes!("../assets/music/title.xm")),
("title2", Category::Title, include_bytes!("../assets/music/title2.xm")), ("title2", Category::Title, include_bytes!("../assets/music/title2.xm")),
("build1", Category::Build, include_bytes!("../assets/music/build1.xm")), ("build1", Category::Build, include_bytes!("../assets/music/build1.xm")),
@@ -28,6 +28,18 @@ pub const TRACKS: [(&str, Category, &[u8]); 35] = [
("battle6", Category::Battle, include_bytes!("../assets/music/battle6.xm")), ("battle6", Category::Battle, include_bytes!("../assets/music/battle6.xm")),
("battle7", Category::Battle, include_bytes!("../assets/music/battle7.xm")), ("battle7", Category::Battle, include_bytes!("../assets/music/battle7.xm")),
("battle8", Category::Battle, include_bytes!("../assets/music/battle8.xm")), ("battle8", Category::Battle, include_bytes!("../assets/music/battle8.xm")),
("build16", Category::Build, include_bytes!("../assets/music/build16.xm")),
("build17", Category::Build, include_bytes!("../assets/music/build17.xm")),
("build18", Category::Build, include_bytes!("../assets/music/build18.xm")),
("build19", Category::Build, include_bytes!("../assets/music/build19.xm")),
("battle9", Category::Battle, include_bytes!("../assets/music/battle9.xm")),
("battle10", Category::Battle, include_bytes!("../assets/music/battle10.xm")),
("surface1", Category::Surface, include_bytes!("../assets/music/surface1.xm")),
("surface2", Category::Surface, include_bytes!("../assets/music/surface2.xm")),
("surface3", Category::Surface, include_bytes!("../assets/music/surface3.xm")),
("surface4", Category::Surface, include_bytes!("../assets/music/surface4.xm")),
("surface_battle1", Category::SurfaceBattle, include_bytes!("../assets/music/surface_battle1.xm")),
("surface_battle2", Category::SurfaceBattle, include_bytes!("../assets/music/surface_battle2.xm")),
("battle2", Category::Assault, include_bytes!("../assets/music/battle2.xm")), ("battle2", Category::Assault, include_bytes!("../assets/music/battle2.xm")),
("assault2", Category::Assault, include_bytes!("../assets/music/assault2.xm")), ("assault2", Category::Assault, include_bytes!("../assets/music/assault2.xm")),
("assault3", Category::Assault, include_bytes!("../assets/music/assault3.xm")), ("assault3", Category::Assault, include_bytes!("../assets/music/assault3.xm")),
+1 -1
View File
@@ -1130,7 +1130,7 @@ mod tests {
fn render_all_songs() { fn render_all_songs() {
for (k, (name, _, data)) in crate::tracks::TRACKS.iter().enumerate() { for (k, (name, _, data)) in crate::tracks::TRACKS.iter().enumerate() {
let m = Rc::new(load(data).expect("load")); let m = Rc::new(load(data).expect("load"));
assert_eq!(m.channels, 8, "{}", name); assert!(m.channels == 8 || (m.channels == 10 && name.starts_with("surface")), "{} {}", name, m.channels);
let mut p = Player::new(m, 48000.0); let mut p = Player::new(m, 48000.0);
let mut buf = vec![0.0f32; 48000 * 2]; let mut buf = vec![0.0f32; 48000 * 2];
let mut peak = 0.0f32; let mut peak = 0.0f32;
+110 -9
View File
@@ -258,6 +258,52 @@ def timpani():
return out return out
def flute_cycle(amp=0.5):
# soft and hollow: a sine with a little triangle and a quiet second harmonic
raw = [math.sin(2 * math.pi * i / CYCLE) + 0.18 * (4.0 * abs(i / CYCLE - 0.5) - 1.0) + 0.12 * math.sin(4 * math.pi * i / CYCLE) for i in range(CYCLE)]
m = max(abs(v) for v in raw)
return [amp * v / m for v in raw]
def bird():
"""A little songbird: three quick rising chirps and a falling trill (rendered for C-4)."""
out = []
ph = 0.0
phrases = [(2600, 3900, 0.06), (2700, 4100, 0.06), (2800, 4300, 0.05), (4200, 3000, 0.11)]
for k, (f0, f1, dur) in enumerate(phrases):
n = int(DRUM_RATE * dur)
for i in range(n):
t = i / n
f = f0 + (f1 - f0) * t
# a fast warble on the last one
if k == 3:
f += 250 * math.sin(2 * math.pi * 28 * i / DRUM_RATE)
ph += 2 * math.pi * f / DRUM_RATE
env = math.sin(math.pi * t) ** 0.6
out.append(0.55 * env * math.sin(ph))
out += [0.0] * int(DRUM_RATE * 0.035)
return out
def wind():
"""Soft wind: filtered noise that swells and fades (a seamless loop of two seconds)."""
rnd = random.Random(23)
n = int(DRUM_RATE * 2.0)
out, lp, lp2 = [], 0.0, 0.0
for i in range(n):
x = rnd.random() * 2 - 1
lp += 0.06 * (x - lp)
lp2 += 0.06 * (lp - lp2)
swell = 0.6 + 0.4 * math.sin(2 * math.pi * i / n)
out.append(2.2 * lp2 * swell)
# cross-fade the ends so the loop has no click
k = int(DRUM_RATE * 0.1)
for i in range(k):
a = i / k
out[i] = out[i] * a + out[n - k + i] * (1 - a)
return out[: n - k]
def build_instruments(): def build_instruments():
return [ return [
Instrument("Lead pulse 25%", Sample("pulse25", pulse(0.25), loop=(0, CYCLE), relnote=CYCLE_REL, finetune=CYCLE_FT), Instrument("Lead pulse 25%", Sample("pulse25", pulse(0.25), loop=(0, CYCLE), relnote=CYCLE_REL, finetune=CYCLE_FT),
@@ -285,14 +331,21 @@ def build_instruments():
Instrument("Harp", Sample("harp", harp_cycle(), loop=(0, CYCLE), relnote=CYCLE_REL, finetune=CYCLE_FT), Instrument("Harp", Sample("harp", harp_cycle(), loop=(0, CYCLE), relnote=CYCLE_REL, finetune=CYCLE_FT),
env=[(0, 64), (4, 44), (30, 12), (70, 0)], fadeout=2000), env=[(0, 64), (4, 44), (30, 12), (70, 0)], fadeout=2000),
Instrument("Timpani", Sample("timpani", timpani(), relnote=12)), Instrument("Timpani", Sample("timpani", timpani(), relnote=12)),
Instrument("Flute", Sample("flute", flute_cycle(), loop=(0, CYCLE), relnote=CYCLE_REL, finetune=CYCLE_FT),
env=[(0, 0), (5, 58), (20, 48), (60, 44)], sustain=3, fadeout=1100, vib=(0, 40, 4, 11)),
Instrument("Bird", Sample("bird", bird(), relnote=12)),
Instrument("Wind", Sample("wind", wind(), loop=(0, len(wind())), relnote=12),
env=[(0, 0), (60, 48), (120, 40)], sustain=2, fadeout=200),
] ]
LEAD, SQUARE, PAD, BASS, PLUCK, KICK, SNARE, HAT = range(1, 9) LEAD, SQUARE, PAD, BASS, PLUCK, KICK, SNARE, HAT = range(1, 9)
ORGAN, BELL, CHOIR, SUB, TOM, CRASH, HARP, TIMP = range(9, 17) ORGAN, BELL, CHOIR, SUB, TOM, CRASH, HARP, TIMP = range(9, 17)
# channel layout FLUTE, BIRD, WIND = range(17, 20)
CH_LEAD, CH_COUNTER, CH_PAD1, CH_PAD2, CH_BASS, CH_ARP, CH_DRUM, CH_HAT = range(8) # channel layout (the last two only for songs above ground: birds and wind)
CHANNELS = 8 CH_LEAD, CH_COUNTER, CH_PAD1, CH_PAD2, CH_BASS, CH_ARP, CH_DRUM, CH_HAT, CH_BIRD, CH_WIND = range(10)
CHANNELS = 10
BASE_CHANNELS = 8 # songs that leave birds and wind out are written with 8 channels
# ------------------------------------------------------------------ patterns # ------------------------------------------------------------------ patterns
@@ -661,7 +714,7 @@ def song_victory():
p0.bass(ch, "R...R...R...R...", vol=48) p0.bass(ch, "R...R...R...R...", vol=48)
p0.drums(kick="x.......x.......", snare="xxxx............", sv=30) p0.drums(kick="x.......x.......", snare="xxxx............", sv=30)
p0.set(63, CH_LEAD, KEY_OFF) p0.set(63, CH_LEAD, KEY_OFF)
for c in range(CHANNELS): for c in range(BASE_CHANNELS):
p0.set(63, c, KEY_OFF) p0.set(63, c, KEY_OFF)
silence = Pattern(1) silence = Pattern(1)
p = [s.add(x) for x in (p0, silence)] p = [s.add(x) for x in (p0, silence)]
@@ -679,7 +732,7 @@ def song_defeat():
p0.pads(ch, vol=22, octave=3) p0.pads(ch, vol=22, octave=3)
p0.bass(ch, "R...............", vol=44, octave=1) p0.bass(ch, "R...............", vol=44, octave=1)
p0.drums(kick="x...............", kv=44) p0.drums(kick="x...............", kv=44)
for c in range(CHANNELS): for c in range(BASE_CHANNELS):
p0.set(63, c, KEY_OFF) p0.set(63, c, KEY_OFF)
silence = Pattern(1) silence = Pattern(1)
p = [s.add(x) for x in (p0, silence)] p = [s.add(x) for x in (p0, silence)]
@@ -703,13 +756,14 @@ def write_xm(path, song, instruments):
out += b"DungeonCraft compose".ljust(20, b" ") out += b"DungeonCraft compose".ljust(20, b" ")
out += struct.pack("<H", 0x0104) out += struct.pack("<H", 0x0104)
order = song.order + [0] * (256 - len(song.order)) order = song.order + [0] * (256 - len(song.order))
out += struct.pack("<IHHHHHHHH", 276, len(song.order), song.restart, CHANNELS, len(song.patterns), nch = CHANNELS if any(row[c] for pat in song.patterns for row in pat.cells for c in range(BASE_CHANNELS, CHANNELS)) else BASE_CHANNELS
out += struct.pack("<IHHHHHHHH", 276, len(song.order), song.restart, nch, len(song.patterns),
len(used), 1, song.speed, song.bpm) len(used), 1, song.speed, song.bpm)
out += bytes(order) out += bytes(order)
for pat in song.patterns: for pat in song.patterns:
data = bytearray() data = bytearray()
for row in pat.cells: for row in pat.cells:
for c in row: for c in row[:nch]:
if c is None: if c is None:
data += b"\x80" data += b"\x80"
else: else:
@@ -910,6 +964,18 @@ class Gen:
if style in ("boss", "driving", "march"): if style in ("boss", "driving", "march"):
pat.set(0, CH_HAT, note_num("C4"), CRASH, 34) pat.set(0, CH_HAT, note_num("C4"), CRASH, 34)
# ---- above ground: birds sing now and then, the wind blows softly
def nature(self, pat):
sp = self.sp
if sp.get("wind"):
pat.set(0, CH_WIND, note_num("C4"), WIND, sp.get("wind_vol", 12))
if sp.get("birds"):
calls = ["C4", "D4", "E4", "G4", "A4", "C5"]
for bar in range(pat.rows // BAR):
if self.rng.random() < sp.get("bird_chance", 0.55):
row = bar * BAR + self.rng.choice([2, 5, 7, 10, 13])
pat.set(row, CH_BIRD, note_num(self.rng.choice(calls)), BIRD, 14 + self.rng.randrange(12))
# ---- generated melody over chords (strong beats on chord tones) # ---- generated melody over chords (strong beats on chord tones)
def melody(self, chords, inst, vol, octave, style): def melody(self, chords, inst, vol, octave, style):
rhythms = { rhythms = {
@@ -996,6 +1062,7 @@ class Gen:
intro = Pattern() intro = Pattern()
self.accompany(intro, free) self.accompany(intro, free)
self.drums(intro, sp.get("intro_drums", "soft" if sp.get("drums", "none") != "none" else "none")) self.drums(intro, sp.get("intro_drums", "soft" if sp.get("drums", "none") != "none" else "none"))
self.nature(intro)
pats.append(intro) pats.append(intro)
for k, (mel, chords) in enumerate(sections): for k, (mel, chords) in enumerate(sections):
p = Pattern() p = Pattern()
@@ -1008,6 +1075,7 @@ class Gen:
p.line(CH_COUNTER, cm, sp["counter"], sp.get("counter_vol", 24)) p.line(CH_COUNTER, cm, sp["counter"], sp.get("counter_vol", 24))
self.accompany(p, chords) self.accompany(p, chords)
self.drums(p, sp.get("drums", "none")) self.drums(p, sp.get("drums", "none"))
self.nature(p)
pats.append(p) pats.append(p)
idx = [song.add(x) for x in pats] idx = [song.add(x) for x in pats]
song.order = idx + idx[1:] song.order = idx + idx[1:]
@@ -1054,6 +1122,30 @@ ASSAULT_SPECS = [
spec("Last Charge", 32, "C", "minor", 168, [0, 3, 4, 0], motif="normal", lead=LEAD, lead_vol=46, pad=PAD, pad_vol=16, bass_pat="R.R.R.R.R.R.R.R.", arp=PLUCK, arp_step=1, arp_vol=10, drums="boss", lead_oct=5, mel_style="driving", counter=ORGAN), spec("Last Charge", 32, "C", "minor", 168, [0, 3, 4, 0], motif="normal", lead=LEAD, lead_vol=46, pad=PAD, pad_vol=16, bass_pat="R.R.R.R.R.R.R.R.", arp=PLUCK, arp_step=1, arp_vol=10, drums="boss", lead_oct=5, mel_style="driving", counter=ORGAN),
] ]
# six more underground pieces (block 3: wood, walls, drawbridges, siege): four calm, two battles
BUILD_SPECS_2 = [
spec("Root Cellar", 16, "C", "dorian", 86, [0, 3, 4, 0], motif="normal", lead=HARP, lead_vol=34, pad=PAD, arp=PLUCK, arp_step=4, drums="calm", echo=True, echo_inst=HARP),
spec("Glowcap Grove", 17, "E", "minor", 82, [0, 5, 3, 4], motif="aug", lead=BELL, lead_vol=38, pad=CHOIR, bass=SUB, arp=HARP, arp_step=2, drums="soft", echo=True, echo_inst=HARP),
spec("Chains of Iron", 18, "A", "harmonic", 92, [0, 5, 1, 4], motif="frag", lead=ORGAN, lead_vol=30, pad=PAD, bass=SUB, bass_pat="R.R.....R.R.....", drums="tribal", counter=SQUARE),
spec("Mushroom Timber", 19, "G", "minor", 100, [0, 6, 3, 4], motif="normal", lead=PLUCK, lead_vol=36, pad=PAD, bass_pat="R...R.F.R...F.O.", arp=HARP, drums="bouncy", mel_style="bouncy", long=True, prog2=[3, 0, 6, 4]),
]
BATTLE_SPECS_2 = [
spec("Drawbridge Up!", 26, "D", "minor", 150, [0, 6, 3, 4], motif="frag", lead=LEAD, lead_vol=44, pad=PAD, pad_vol=15, bass_pat="R.R.R.R.R.R.R.R.", arp=PLUCK, arp_step=1, arp_vol=10, drums="rock", lead_oct=5, mel_style="driving", counter=SQUARE),
spec("Rams at the Door", 27, "F", "phrygian", 146, [0, 1, 6, 0], motif="dim", lead=SQUARE, lead_vol=42, pad=ORGAN, pad_vol=14, bass_pat="RRRRRRRRRRRRRRRR", drums="march", mel_style="driving", echo=True),
]
# above ground: the same leitmotif, brighter (major modes), with flute, harp, birdsong and wind
SURFACE_SPECS = [
spec("First Light", 50, "D", "major", 84, [0, 3, 4, 0], motif="normal", lead=FLUTE, lead_vol=38, pad=CHOIR, pad_vol=14, bass=SUB, arp=HARP, arp_step=2, arp_vol=12, drums="soft", birds=True, wind=True, echo=True, echo_inst=HARP),
spec("Open Sky", 51, "G", "lydian", 90, [0, 1, 4, 0], motif="aug", lead=FLUTE, lead_vol=36, pad=PAD, pad_vol=16, bass=SUB, arp=HARP, arp_step=4, drums="calm", birds=True, bird_chance=0.7),
spec("Forest Path", 52, "A", "mixolydian", 98, [0, 6, 3, 0], motif="frag", lead=FLUTE, lead_vol=38, pad=PAD, pad_vol=14, bass_pat="R...R.F.R...F.O.", arp=PLUCK, arp_step=2, drums="bouncy", mel_style="bouncy", birds=True, wind=True, counter=HARP),
spec("Sunlit Ruins", 53, "F", "major", 78, [0, 5, 3, 4], motif="inv", lead=HARP, lead_vol=36, pad=CHOIR, pad_vol=16, bass=SUB, bass_pat="R.......F.......", arp=FLUTE, arp_step=4, arp_vol=14, drums="soft", birds=True, wind=True, long=True, prog2=[3, 4, 5, 4]),
]
SURFACE_BATTLE_SPECS = [
spec("Banners in the Wind", 54, "D", "mixolydian", 138, [0, 6, 3, 4], motif="normal", lead=FLUTE, lead_vol=44, pad=PAD, pad_vol=15, bass_pat="R.R.R.F.R.R.F.O.", drums="march", lead_oct=5, mel_style="driving", counter=LEAD, wind=True),
spec("Storm the Keep", 55, "E", "dorian", 150, [0, 3, 6, 4], motif="dim", lead=LEAD, lead_vol=44, pad=CHOIR, pad_vol=14, bass_pat="RRRRRRRRRRRRRRRR", arp=HARP, arp_step=1, arp_vol=10, drums="driving", mel_style="driving", echo=True, echo_inst=FLUTE),
]
MOOD_SPECS = { MOOD_SPECS = {
"torture": spec("The Rack", 40, "C#", "phrygian", 66, [0, 1, 0, 1], motif="aug", lead=BELL, lead_vol=34, pad=CHOIR, pad_vol=16, bass=SUB, bass_pat="R...............", drums="none", intro_drums="none", arp=PLUCK, arp_step=8, arp_order=(0, 1), arp_vol=12), "torture": spec("The Rack", 40, "C#", "phrygian", 66, [0, 1, 0, 1], motif="aug", lead=BELL, lead_vol=34, pad=CHOIR, pad_vol=16, bass=SUB, bass_pat="R...............", drums="none", intro_drums="none", arp=PLUCK, arp_step=8, arp_order=(0, 1), arp_vol=12),
"temple": spec("Ember Shrine", 41, "D", "lydian", 74, [0, 1, 4, 0], motif="aug", lead=ORGAN, lead_vol=32, pad=CHOIR, pad_vol=20, bass=SUB, bass_pat="R...............", drums="none", intro_drums="none", arp=BELL, arp_step=8, arp_order=(0, 2), arp_vol=18, echo=True, echo_inst=HARP), "temple": spec("Ember Shrine", 41, "D", "lydian", 74, [0, 1, 4, 0], motif="aug", lead=ORGAN, lead_vol=32, pad=CHOIR, pad_vol=20, bass=SUB, bass_pat="R...............", drums="none", intro_drums="none", arp=BELL, arp_step=8, arp_order=(0, 2), arp_vol=18, echo=True, echo_inst=HARP),
@@ -1182,7 +1274,7 @@ def song_victory2():
for k in (0, 32, 40, 44): for k in (0, 32, 40, 44):
p0.set(k, CH_DRUM, note_num("C4"), TIMP, 52) p0.set(k, CH_DRUM, note_num("C4"), TIMP, 52)
p0.set(32, CH_HAT, note_num("C4"), CRASH, 36) p0.set(32, CH_HAT, note_num("C4"), CRASH, 36)
for c in range(CHANNELS): for c in range(BASE_CHANNELS):
p0.set(63, c, KEY_OFF) p0.set(63, c, KEY_OFF)
silence = Pattern(1) silence = Pattern(1)
p = [s.add(x) for x in (p0, silence)] p = [s.add(x) for x in (p0, silence)]
@@ -1207,6 +1299,14 @@ for k, sp_ in enumerate(BUILD_SPECS):
SONGS["battle1"] = ("battle", song_battle1) SONGS["battle1"] = ("battle", song_battle1)
for k, sp_ in enumerate(BATTLE_SPECS): for k, sp_ in enumerate(BATTLE_SPECS):
SONGS["battle%d" % (k + 3)] = ("battle", generated(sp_)) SONGS["battle%d" % (k + 3)] = ("battle", generated(sp_))
for k, sp_ in enumerate(BUILD_SPECS_2):
SONGS["build%d" % (k + 16)] = ("build", generated(sp_))
for k, sp_ in enumerate(BATTLE_SPECS_2):
SONGS["battle%d" % (k + 9)] = ("battle", generated(sp_))
for k, sp_ in enumerate(SURFACE_SPECS):
SONGS["surface%d" % (k + 1)] = ("surface", generated(sp_))
for k, sp_ in enumerate(SURFACE_BATTLE_SPECS):
SONGS["surface_battle%d" % (k + 1)] = ("surface_battle", generated(sp_))
SONGS["battle2"] = ("assault", song_battle2) SONGS["battle2"] = ("assault", song_battle2)
for k, sp_ in enumerate(ASSAULT_SPECS): for k, sp_ in enumerate(ASSAULT_SPECS):
SONGS["assault%d" % (k + 2)] = ("assault", generated(sp_)) SONGS["assault%d" % (k + 2)] = ("assault", generated(sp_))
@@ -1230,7 +1330,8 @@ def write_rust_table(names):
"pub const TRACKS: [(&str, Category, &[u8]); %d] = [" % len(names), "pub const TRACKS: [(&str, Category, &[u8]); %d] = [" % len(names),
] ]
cat = {"title": "Title", "build": "Build", "battle": "Battle", "assault": "Assault", "boss_paladin": "BossPaladin", cat = {"title": "Title", "build": "Build", "battle": "Battle", "assault": "Assault", "boss_paladin": "BossPaladin",
"boss_warden": "BossWarden", "torture": "Torture", "temple": "Temple", "victory": "Victory", "defeat": "Defeat"} "boss_warden": "BossWarden", "torture": "Torture", "temple": "Temple", "victory": "Victory", "defeat": "Defeat",
"surface": "Surface", "surface_battle": "SurfaceBattle"}
for n in names: for n in names:
lines.append(' ("%s", Category::%s, include_bytes!("../assets/music/%s.xm")),' % (n, cat[SONGS[n][0]], n)) lines.append(' ("%s", Category::%s, include_bytes!("../assets/music/%s.xm")),' % (n, cat[SONGS[n][0]], n))
lines.append("];") lines.append("];")