Holiday music: a dark carol for Christmas, a spring tune for Easter, a harpsichord dance for Halloween

During a holiday the title music plays its piece, the build music every fourth time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 00:12:08 +01:00
1 parent 65ed76865d
commit 9800ce7156
8 files changed
+319 -8

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
+66 -1
View File
@@ -24,6 +24,10 @@ pub enum Category {
Defeat, Defeat,
Surface, // above ground: flute, harp, birdsong Surface, // above ground: flute, harp, birdsong
SurfaceBattle, // fighting above ground SurfaceBattle, // fighting above ground
// the holidays' pieces (see holiday_category)
Christmas,
Easter,
Halloween,
} }
// indices of all tracks of a category, in play order // indices of all tracks of a category, in play order
@@ -34,7 +38,27 @@ pub fn tracks_in(c: Category) -> Vec<usize> {
// the calm tracks the music player offers (no battle, victory or defeat music) // the calm tracks the music player offers (no battle, victory or defeat music)
pub fn player_tracks() -> Vec<usize> { pub fn player_tracks() -> Vec<usize> {
use Category as C; use Category as C;
TRACKS.iter().enumerate().filter(|(_, t)| matches!(t.1, C::Title | C::Build | C::Surface | C::Torture | C::Temple)).map(|(i, _)| i).collect() TRACKS.iter().enumerate().filter(|(_, t)| matches!(t.1, C::Title | C::Build | C::Surface | C::Torture | C::Temple | C::Christmas | C::Easter | C::Halloween)).map(|(i, _)| i).collect()
}
// the music of a holiday (holiday::current()), if it has one
pub fn holiday_music(holiday: u8) -> Option<Category> {
match holiday {
crate::holiday::CHRISTMAS => Some(Category::Christmas),
crate::holiday::EASTER => Some(Category::Easter),
crate::holiday::HALLOWEEN => Some(Category::Halloween),
_ => None,
}
}
// the category actually played for `want` during a holiday: the title music always becomes the
// holiday's piece, the build music every fourth time (`build_picks`: build tracks picked so far)
pub fn holiday_category(want: Category, holiday: u8, build_picks: usize) -> Category {
match (want, holiday_music(holiday)) {
(Category::Title, Some(h)) => h,
(Category::Build, Some(h)) if build_picks % 4 == 3 => h,
_ => want,
}
} }
pub fn track_id(t: usize) -> &'static str { pub fn track_id(t: usize) -> &'static str {
@@ -366,6 +390,47 @@ mod tests {
assert_eq!(track_title(track_by_id("build13").unwrap()), "Underground River"); assert_eq!(track_title(track_by_id("build13").unwrap()), "Underground River");
} }
#[test]
fn every_holiday_has_its_piece_for_the_title_and_now_and_then_the_build_music() {
use crate::holiday::{CHRISTMAS, EASTER, HALLOWEEN, NONE};
for (h, c, id, title) in [
(CHRISTMAS, Category::Christmas, "holiday_christmas", "Frostbound Carol"),
(EASTER, Category::Easter, "holiday_easter", "Eggs in the Moss"),
(HALLOWEEN, Category::Halloween, "holiday_halloween", "Crypt of Lanterns"),
] {
let t = track_by_id(id).unwrap();
assert_eq!(tracks_in(c), vec![t]);
assert_eq!(track_title(t), title);
assert!(player_tracks().contains(&t));
assert_eq!(holiday_category(Category::Title, h, 0), c);
assert_eq!(holiday_category(Category::Title, h, 5), c);
let builds: Vec<Category> = (0..8).map(|k| holiday_category(Category::Build, h, k)).collect();
assert_eq!(builds.iter().filter(|&&b| b == c).count(), 2, "now and then, not always");
assert!(builds.iter().all(|&b| b == c || b == Category::Build));
for other in [Category::Battle, Category::Surface, Category::Victory, Category::Torture] {
assert_eq!(holiday_category(other, h, 3), other);
}
// the piece plays: audible, finite and not clipping
let m = std::rc::Rc::new(xm::load(TRACKS[t].2).unwrap());
let mut p = Player::new(m, 22050.0);
let mut buf = vec![0.0f32; 2 * 22050];
let mut peak = 0.0f32;
for _ in 0..20 {
buf.iter_mut().for_each(|v| *v = 0.0);
p.render(&mut buf, MUSIC_GAIN);
for &v in &buf {
assert!(v.is_finite());
peak = peak.max(v.abs());
}
}
assert!(peak > 0.05 && peak < 1.0, "{} peak {}", id, peak);
}
for k in 0..8 {
assert_eq!(holiday_category(Category::Title, NONE, k), Category::Title);
assert_eq!(holiday_category(Category::Build, NONE, k), Category::Build);
}
}
#[test] #[test]
fn engine_crossfade_and_sfx() { fn engine_crossfade_and_sfx() {
dc_audio_init(48000.0); dc_audio_init(48000.0);
+10 -3
View File
@@ -492,11 +492,12 @@ fn apply_settings(s: &Settings, r3d: &mut R3d) {
struct MusicDirector { struct MusicDirector {
next: std::collections::HashMap<u8, usize>, // category -> next index within the category next: std::collections::HashMap<u8, usize>, // category -> next index within the category
current: Option<audio::Category>, current: Option<audio::Category>,
build_picks: usize, // build tracks picked so far (during a holiday every fourth is its piece)
} }
impl MusicDirector { impl MusicDirector {
fn new() -> MusicDirector { fn new() -> MusicDirector {
MusicDirector { next: std::collections::HashMap::new(), current: None } MusicDirector { next: std::collections::HashMap::new(), current: None, build_picks: 0 }
} }
fn pick(&mut self, c: audio::Category) -> usize { fn pick(&mut self, c: audio::Category) -> usize {
@@ -573,11 +574,17 @@ impl MusicDirector {
} }
let rotate = matches!(want, C::Build | C::Title | C::Surface) && self.current == Some(want) && audio::music_loops() >= 2; let rotate = matches!(want, C::Build | C::Title | C::Surface) && self.current == Some(want) && audio::music_loops() >= 2;
// after the chosen track was let go, the director picks again // after the chosen track was let go, the director picks again
let foreign = chosen.is_none() && audio::current_track().map_or(false, |t| crate::tracks::TRACKS.get(t).map_or(false, |x| x.1 != want)) && calm && self.current == Some(want); // (a holiday's piece belongs to the title and build music)
let holiday = holiday::current();
let fits = |c: C| c == want || (matches!(want, C::Title | C::Build) && audio::holiday_music(holiday) == Some(c));
let foreign = chosen.is_none() && audio::current_track().map_or(false, |t| crate::tracks::TRACKS.get(t).map_or(false, |x| !fits(x.1))) && calm && self.current == Some(want);
if self.current != Some(want) || rotate || foreign || audio::current_track().is_none() { if self.current != Some(want) || rotate || foreign || audio::current_track().is_none() {
let before = self.current; let before = self.current;
self.current = Some(want); self.current = Some(want);
let t = self.pick(want); let t = self.pick(audio::holiday_category(want, holiday, self.build_picks));
if want == C::Build {
self.build_picks += 1;
}
// between the dungeon and the surface the music changes slowly // between the dungeon and the surface the music changes slowly
let fade = if matches!(want, C::Victory | C::Defeat) { let fade = if matches!(want, C::Victory | C::Defeat) {
1.0 1.0
+4 -1
View File
@@ -3,7 +3,7 @@
use crate::audio::Category; use crate::audio::Category;
pub const TRACKS: [(&str, Category, &[u8]); 47] = [ pub const TRACKS: [(&str, Category, &[u8]); 50] = [
("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")),
@@ -51,4 +51,7 @@ pub const TRACKS: [(&str, Category, &[u8]); 47] = [
("victory", Category::Victory, include_bytes!("../assets/music/victory.xm")), ("victory", Category::Victory, include_bytes!("../assets/music/victory.xm")),
("victory2", Category::Victory, include_bytes!("../assets/music/victory2.xm")), ("victory2", Category::Victory, include_bytes!("../assets/music/victory2.xm")),
("defeat", Category::Defeat, include_bytes!("../assets/music/defeat.xm")), ("defeat", Category::Defeat, include_bytes!("../assets/music/defeat.xm")),
("holiday_christmas", Category::Christmas, include_bytes!("../assets/music/holiday_christmas.xm")),
("holiday_easter", Category::Easter, include_bytes!("../assets/music/holiday_easter.xm")),
("holiday_halloween", Category::Halloween, include_bytes!("../assets/music/holiday_halloween.xm")),
]; ];
+2 -1
View File
@@ -1130,7 +1130,8 @@ 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!(m.channels == 8 || (m.channels == 10 && name.starts_with("surface")), "{} {}", name, m.channels); // birds and wind above ground and in the spring
assert!(m.channels == 8 || (m.channels == 10 && (name.starts_with("surface") || *name == "holiday_easter")), "{} {}", 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;
+237 -2
View File
@@ -75,6 +75,10 @@ CHORDS = {
"C": ["C", "E", "G"], "C": ["C", "E", "G"],
"Am": ["A", "C", "E"], "Am": ["A", "C", "E"],
"C7": ["C", "E", "Bb"], "C7": ["C", "E", "Bb"],
"E": ["E", "G#", "B"],
"Em": ["E", "G", "B"],
"Bm": ["B", "D", "F#"],
"Eb": ["Eb", "G", "Bb"],
} }
@@ -304,6 +308,15 @@ def wind():
return out[: n - k] return out[: n - k]
def harpsichord_cycle(amp=0.4):
# bright and nasal: a narrow pulse with a saw on top (many strong harmonics, like a plucked quill)
raw = [(1.0 if i < CYCLE * 0.18 else -0.22) + 0.6 * (2.0 * i / CYCLE - 1.0) for i in range(CYCLE)]
mean = sum(raw) / CYCLE
raw = [v - mean for v in raw]
m = max(abs(v) for v in raw)
return [amp * v / m for v in raw]
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),
@@ -336,12 +349,14 @@ def build_instruments():
Instrument("Bird", Sample("bird", bird(), relnote=12)), Instrument("Bird", Sample("bird", bird(), relnote=12)),
Instrument("Wind", Sample("wind", wind(), loop=(0, len(wind())), relnote=12), Instrument("Wind", Sample("wind", wind(), loop=(0, len(wind())), relnote=12),
env=[(0, 0), (60, 48), (120, 40)], sustain=2, fadeout=200), env=[(0, 0), (60, 48), (120, 40)], sustain=2, fadeout=200),
Instrument("Harpsichord", Sample("harpsichord", harpsichord_cycle(), loop=(0, CYCLE), relnote=CYCLE_REL, finetune=CYCLE_FT),
env=[(0, 64), (2, 46), (14, 22), (40, 8), (70, 0)], fadeout=2400),
] ]
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)
FLUTE, BIRD, WIND = range(17, 20) FLUTE, BIRD, WIND, HARPSI = range(17, 21)
# channel layout (the last two only for songs above ground: birds and wind) # channel layout (the last two only for songs above ground: birds and wind)
CH_LEAD, CH_COUNTER, CH_PAD1, CH_PAD2, CH_BASS, CH_ARP, CH_DRUM, CH_HAT, CH_BIRD, CH_WIND = range(10) CH_LEAD, CH_COUNTER, CH_PAD1, CH_PAD2, CH_BASS, CH_ARP, CH_DRUM, CH_HAT, CH_BIRD, CH_WIND = range(10)
CHANNELS = 10 CHANNELS = 10
@@ -1283,6 +1298,222 @@ def song_victory2():
return s return s
# ------------------------------------------------------------------ holiday pieces
# During a holiday the title music (and now and then the build music) plays the holiday's piece
# (see audio::holiday_category). Each one quotes the leitmotif in its own colours.
def sleigh(pat, vol=8):
"""Sleigh bells: the hihat pitched up, every eighth, louder on the beats."""
for r in range(0, pat.rows, 2):
pat.set(r, CH_HAT, note_num("C6"), HAT, vol + (6 if r % 4 == 0 else 0))
def organ_pads(pat, chords, vol=18, octave=4):
r = 0
for name, rows in chords:
notes = chord_notes(name, octave)
pat.set(r, CH_PAD1, notes[1], ORGAN, vol)
pat.set(r, CH_PAD2, notes[2], ORGAN, vol)
r += rows
def choir_pads(pat, chords, vol=18, octave=4):
r = 0
for name, rows in chords:
notes = chord_notes(name, octave)
pat.set(r, CH_PAD1, notes[1], CHOIR, vol)
pat.set(r, CH_PAD2, notes[2], CHOIR, vol)
r += rows
def song_christmas():
"""Frostbound Carol: a dark carol in D minor on bells and choir, sleigh bells, a turn to
D major in the middle that falls back into the minor; the leitmotif rung on the bells."""
s = Song("Frostbound Carol", 100)
mel_a = "A4:4 A4:4 Bb4:4 A4:4 | G4:4 F4:4 E4:8 | F4:4 G4:4 A4:4 D5:4 | C#5:12 -:4"
mel_b = "D5:4 C5:4 Bb4:4 A4:4 | Bb4:4 A4:4 G4:8 | F4:4 E4:4 D4:4 F4:4 | E4:6 C#4:2 D4:8"
mel_c = "F#4:4 A4:4 D5:8 | B4:4 A4:4 G4:8 | F#4:4 G4:4 A4:4 B4:4 | A4:12 -:4"
ch_a = bars(("Dm", 1), ("Gm", .5), ("A", .5), ("Dm", .5), ("F", .5), ("A", 1))
ch_b = bars(("Bb", 1), ("Gm", 1), ("Dm", 1), ("A", .5), ("Dm", .5))
ch_c = bars(("D", 1), ("G", 1), ("D", .5), ("G", .5), ("A", 1))
ch_i = bars(("Dm", 1), ("Bb", 1), ("Gm", 1), ("A", 1))
# intro: the bells toll over the choir, timpani like distant steps in the snow
p0 = Pattern()
p0.line(CH_LEAD, "D5:16 | D5:16 | D5:16 | C#5:16", BELL, 36)
choir_pads(p0, ch_i, vol=20)
p0.bass(ch_i, "R...............", inst=SUB, vol=42)
for k in (0, 32):
p0.set(k, CH_DRUM, note_num("C4"), TIMP, 44)
p1 = Pattern()
p1.line(CH_LEAD, transpose(mel_a, 12), BELL, 44)
choir_pads(p1, ch_a, vol=18)
p1.bass(ch_a, "R.......F.......", vol=40)
sleigh(p1, 6)
p2 = Pattern()
p2.line(CH_LEAD, transpose(mel_b, 12), BELL, 44)
choir_pads(p2, ch_b, vol=18)
p2.bass(ch_b, "R.......F.......", vol=40)
sleigh(p2, 6)
# second verse: the organ sings, the harp ripples, a soft kick
p3 = Pattern()
p3.line(CH_LEAD, mel_a, ORGAN, 38)
p3.line(CH_COUNTER, "D4:16 | Bb3:8 C#4:8 | D4:8 C4:8 | C#4:16", CHOIR, 22)
p3.pads(ch_a, vol=14)
p3.bass(ch_a, "R...R.F.R...F.O.", vol=40)
p3.arp(ch_a, step=2, vol=11, inst=HARP)
p3.drums(kick="x.......x.......", kv=44)
sleigh(p3, 8)
p4 = Pattern()
p4.line(CH_LEAD, mel_b, ORGAN, 38)
p4.line(CH_COUNTER, "F4:16 | D4:16 | A3:16 | G3:8 F3:8", CHOIR, 22)
p4.pads(ch_b, vol=14)
p4.bass(ch_b, "R...R.F.R...F.O.", vol=40)
p4.arp(ch_b, step=2, vol=11, inst=HARP)
p4.drums(kick="x.......x.......", kv=44)
sleigh(p4, 8)
# the turn to major: candle light for a moment
p5 = Pattern()
p5.line(CH_LEAD, transpose(mel_c, 12), BELL, 46)
p5.line(CH_COUNTER, "D4:16 | D4:16 | D4:8 B3:8 | C#4:16", ORGAN, 26)
choir_pads(p5, ch_c, vol=18)
p5.bass(ch_c, "R.......F.......", vol=42)
p5.arp(ch_c, step=4, vol=10, inst=HARP)
sleigh(p5, 8)
p5.set(48, CH_DRUM, note_num("C4"), TIMP, 40)
# the leitmotif on the bells, echoed by the harp
p6 = Pattern()
p6.line(CH_LEAD, transpose(MOTIF_A, 12), BELL, 42)
p6.echo(CH_LEAD, CH_COUNTER, 4, 18, HARP)
choir_pads(p6, CHORDS_A, vol=18)
p6.bass(CHORDS_A, "R.......F.......", inst=SUB, vol=42)
sleigh(p6, 6)
p7 = Pattern()
p7.line(CH_LEAD, transpose(MOTIF_B, 12), BELL, 42)
p7.echo(CH_LEAD, CH_COUNTER, 4, 18, HARP)
choir_pads(p7, CHORDS_B, vol=18)
p7.bass(CHORDS_B, "R.......F.......", inst=SUB, vol=42)
sleigh(p7, 6)
p7.set(48, CH_DRUM, note_num("C4"), TIMP, 42)
p = [s.add(x) for x in (p0, p1, p2, p3, p4, p5, p6, p7)]
s.order = [p[0], p[1], p[2], p[3], p[4], p[5], p[2], p[6], p[7], p[1], p[2]]
s.restart = 1
return s
def song_easter():
"""Eggs in the Moss: light and playful in G major, flute and harp, birdsong, a bouncing
beat; the leitmotif turned into a spring tune."""
s = Song("Eggs in the Moss", 120)
rng = random.Random(61)
mel_a = "G4:2 B4:2 D5:4 B4:2 D5:2 G5:4 | E5:2 D5:2 C5:2 B4:2 A4:8 | F#4:2 A4:2 D5:4 C5:2 B4:2 A4:4 | B4:4 G4:4 G4:8"
mel_b = "E5:4 D5:2 C5:2 B4:4 A4:4 | D5:4 C5:2 B4:2 A4:8 | G4:2 A4:2 B4:2 C5:2 D5:4 E5:4 | D5:8 -:8"
ch_a = bars(("G", 1), ("C", .5), ("D", .5), ("D", 1), ("G", 1))
ch_b = bars(("Em", 1), ("D", 1), ("C", .5), ("G", .5), ("D", 1))
mot_a = realize(MOTIF_DEG_A, NOTE_BASE["G"], "major", 4)
mot_b = realize(MOTIF_DEG_B, NOTE_BASE["G"], "major", 4)
ch_ma = bars(("G", 1), ("C", .5), ("D", .5), ("G", 1), ("Bm", 1))
ch_mb = bars(("Em", 1), ("G", 1), ("D", 1), ("G", 1))
bouncy = dict(kick="x.......x.......", snare="............x...", hat="..x...x...x...x.", hv=12, sv=28)
def birds(pat):
for bar in range(pat.rows // BAR):
if rng.random() < 0.6:
row = bar * BAR + rng.choice([2, 5, 7, 10, 13])
pat.set(row, CH_BIRD, note_num(rng.choice(["C4", "D4", "E4", "G4", "A4", "C5"])), BIRD, 12 + rng.randrange(10))
def verse(mel, ch, lead, lvol, echo=None):
p = Pattern()
p.line(CH_LEAD, mel, lead, lvol)
if echo:
p.echo(CH_LEAD, CH_COUNTER, 2, 16, echo)
p.pads(ch, vol=13)
p.bass(ch, "R.-.R.F.-.R.O.F.", vol=40)
p.arp(ch, step=2, vol=11, inst=HARP, order=(0, 1, 2, 1))
p.drums(**bouncy)
birds(p)
return p
p0 = Pattern()
choir_pads(p0, ch_a, vol=12)
p0.bass(ch_a, "R.......F.......", inst=SUB, vol=38)
p0.arp(ch_a, step=2, vol=11, inst=HARP, order=(0, 1, 2, 1))
p0.drums(hat="..x...x...x...x.", hv=10)
birds(p0)
p0.set(0, CH_WIND, note_num("C4"), WIND, 10)
p1 = verse(mel_a, ch_a, FLUTE, 40)
p2 = verse(mel_b, ch_b, FLUTE, 40)
p3 = verse(transpose(mel_a, 12), ch_a, PLUCK, 34, HARP)
p4 = verse(transpose(mel_b, 12), ch_b, PLUCK, 34, HARP)
p5 = verse(mot_a, ch_ma, FLUTE, 40, HARP)
p6 = verse(mot_b, ch_mb, FLUTE, 40, HARP)
p = [s.add(x) for x in (p0, p1, p2, p3, p4, p5, p6)]
s.order = [p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[3], p[4], p[1], p[2]]
s.restart = 1
return s
def song_halloween():
"""Crypt of Lanterns: a spooky dance in D harmonic minor, harpsichord and organ, midnight
bells, toms like knocking on coffin lids; the leitmotif on the harpsichord."""
s = Song("Crypt of Lanterns", 120)
mel_a = "D4:4 F4:4 G#4:4 A4:4 | Bb4:6 A4:2 G#4:8 | A4:4 C5:4 Bb4:4 G4:4 | A4:16"
mel_b = "D5:2 Eb5:2 D5:4 A4:4 F4:4 | G4:2 F4:2 E4:4 C#4:8 | D4:2 E4:2 F4:2 G4:2 A4:2 Bb4:2 C#5:4 | D5:16"
ch_a = bars(("Dm", 1), ("E", 1), ("Gm", 1), ("A", 1))
ch_b = bars(("Dm", 1), ("A", 1), ("Gm", .5), ("A", .5), ("Dm", 1))
def knock(pat):
for bar in range(pat.rows // BAR):
pat.set(bar * BAR + 14, CH_DRUM, note_num("A3"), TOM, 36)
# midnight: twelve... well, four bells over an organ drone
p0 = Pattern()
p0.line(CH_LEAD, "D4:16 | D4:16 | D4:16 | C#4:16", BELL, 36)
p0.line(CH_PAD1, "D3:64", ORGAN, 20)
p0.line(CH_PAD2, "A3:32 | G#3:32", ORGAN, 18)
p0.bass(ch_a, "R...............", inst=SUB, vol=44)
p0.arp(ch_a, step=4, vol=9, octave=4, inst=HARPSI)
def verse(mel, ch, lead, lvol, heavy=False):
p = Pattern()
p.line(CH_LEAD, mel, lead, lvol)
organ_pads(p, ch, vol=16)
p.bass(ch, "R.......R...F..." if not heavy else "R.R.....R.R.F...", inst=SUB, vol=44)
p.arp(ch, step=1, vol=10, octave=4, order=(0, 1, 2, 1), inst=HARPSI)
if heavy:
p.drums(kick="x.....x.x.......", snare="....x.......x...", hat="..x...x...x...x.", kv=52, sv=34, hv=10)
else:
p.drums(kick="x.......x.......", hat="..x...x...x...x.", kv=48, hv=9)
knock(p)
return p
p1 = verse(transpose(mel_a, 12), ch_a, HARPSI, 40)
p2 = verse(transpose(mel_b, 12), ch_b, HARPSI, 40)
p3 = verse(mel_a, ch_a, ORGAN, 36, heavy=True)
p3.line(CH_COUNTER, "A3:16 | G#3:16 | Bb3:16 | C#4:16", HARPSI, 24)
p4 = verse(mel_b, ch_b, ORGAN, 36, heavy=True)
p4.line(CH_COUNTER, "F3:16 | E3:16 | D3:8 E3:8 | F3:16", HARPSI, 24)
# the leitmotif on the harpsichord with its own ghostly echo
p5 = verse(transpose(MOTIF_A, 12), CHORDS_A, HARPSI, 40)
p5.echo(CH_LEAD, CH_COUNTER, 3, 16, HARPSI)
p6 = verse(transpose(MOTIF_B, 12), CHORDS_B, HARPSI, 40)
p6.echo(CH_LEAD, CH_COUNTER, 3, 16, HARPSI)
# creeping down: a chromatic organ line over a pedal, the bells again
p7 = Pattern()
p7.line(CH_LEAD, "A4:8 G#4:8 | G4:8 F#4:8 | F4:8 E4:8 | Eb4:8 D4:8", ORGAN, 34)
p7.line(CH_PAD1, "D3:64", ORGAN, 16)
p7.line(CH_PAD2, "A3:64", ORGAN, 14)
p7.bass(ch_a, "R...............", inst=SUB, vol=44)
for k in (0, 32):
p7.set(k, CH_COUNTER, note_num("D5"), BELL, 30)
p7.set(k, CH_DRUM, note_num("C4"), TIMP, 46)
p7.arp(ch_a, step=2, vol=9, octave=4, inst=HARPSI, order=(0, 2))
p = [s.add(x) for x in (p0, p1, p2, p3, p4, p5, p6, p7)]
s.order = [p[0], p[1], p[2], p[3], p[4], p[7], p[5], p[6], p[1], p[2], p[3], p[4]]
s.restart = 1
return s
def generated(spec_): def generated(spec_):
return lambda: Gen(spec_).build() return lambda: Gen(spec_).build()
@@ -1317,6 +1548,9 @@ SONGS["mood_temple"] = ("temple", generated(MOOD_SPECS["temple"]))
SONGS["victory"] = ("victory", song_victory) SONGS["victory"] = ("victory", song_victory)
SONGS["victory2"] = ("victory", song_victory2) SONGS["victory2"] = ("victory", song_victory2)
SONGS["defeat"] = ("defeat", song_defeat) SONGS["defeat"] = ("defeat", song_defeat)
SONGS["holiday_christmas"] = ("christmas", song_christmas)
SONGS["holiday_easter"] = ("easter", song_easter)
SONGS["holiday_halloween"] = ("halloween", song_halloween)
def write_rust_table(names): def write_rust_table(names):
@@ -1331,7 +1565,8 @@ def write_rust_table(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"} "surface": "Surface", "surface_battle": "SurfaceBattle", "christmas": "Christmas", "easter": "Easter",
"halloween": "Halloween"}
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("];")