diff --git a/assets/music/holiday_christmas.xm b/assets/music/holiday_christmas.xm new file mode 100644 index 0000000..93d28c8 Binary files /dev/null and b/assets/music/holiday_christmas.xm differ diff --git a/assets/music/holiday_easter.xm b/assets/music/holiday_easter.xm new file mode 100644 index 0000000..343dd53 Binary files /dev/null and b/assets/music/holiday_easter.xm differ diff --git a/assets/music/holiday_halloween.xm b/assets/music/holiday_halloween.xm new file mode 100644 index 0000000..ac7a96b Binary files /dev/null and b/assets/music/holiday_halloween.xm differ diff --git a/src/audio.rs b/src/audio.rs index fdfc5ec..fae6a3a 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -24,6 +24,10 @@ pub enum Category { Defeat, Surface, // above ground: flute, harp, birdsong SurfaceBattle, // fighting above ground + // the holidays' pieces (see holiday_category) + Christmas, + Easter, + Halloween, } // indices of all tracks of a category, in play order @@ -34,7 +38,27 @@ pub fn tracks_in(c: Category) -> Vec { // the calm tracks the music player offers (no battle, victory or defeat music) pub fn player_tracks() -> Vec { 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 { + 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 { @@ -366,6 +390,47 @@ mod tests { 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 = (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] fn engine_crossfade_and_sfx() { dc_audio_init(48000.0); diff --git a/src/main.rs b/src/main.rs index f9d6206..a42b283 100644 --- a/src/main.rs +++ b/src/main.rs @@ -492,11 +492,12 @@ fn apply_settings(s: &Settings, r3d: &mut R3d) { struct MusicDirector { next: std::collections::HashMap, // category -> next index within the category current: Option, + build_picks: usize, // build tracks picked so far (during a holiday every fourth is its piece) } impl 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 { @@ -573,11 +574,17 @@ impl MusicDirector { } 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 - 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() { let before = self.current; 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 let fade = if matches!(want, C::Victory | C::Defeat) { 1.0 diff --git a/src/tracks.rs b/src/tracks.rs index 7ec3b45..2b18893 100644 --- a/src/tracks.rs +++ b/src/tracks.rs @@ -3,7 +3,7 @@ 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")), ("title2", Category::Title, include_bytes!("../assets/music/title2.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")), ("victory2", Category::Victory, include_bytes!("../assets/music/victory2.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")), ]; diff --git a/src/xm.rs b/src/xm.rs index 18d45a8..78b37de 100644 --- a/src/xm.rs +++ b/src/xm.rs @@ -1130,7 +1130,8 @@ mod tests { fn render_all_songs() { for (k, (name, _, data)) in crate::tracks::TRACKS.iter().enumerate() { 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 buf = vec![0.0f32; 48000 * 2]; let mut peak = 0.0f32; diff --git a/tools/compose_music.py b/tools/compose_music.py index 2f72786..c00952b 100755 --- a/tools/compose_music.py +++ b/tools/compose_music.py @@ -75,6 +75,10 @@ CHORDS = { "C": ["C", "E", "G"], "Am": ["A", "C", "E"], "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] +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(): return [ 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("Wind", Sample("wind", wind(), loop=(0, len(wind())), relnote=12), 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) 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) CH_LEAD, CH_COUNTER, CH_PAD1, CH_PAD2, CH_BASS, CH_ARP, CH_DRUM, CH_HAT, CH_BIRD, CH_WIND = range(10) CHANNELS = 10 @@ -1283,6 +1298,222 @@ def song_victory2(): 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_): return lambda: Gen(spec_).build() @@ -1317,6 +1548,9 @@ SONGS["mood_temple"] = ("temple", generated(MOOD_SPECS["temple"])) SONGS["victory"] = ("victory", song_victory) SONGS["victory2"] = ("victory", song_victory2) 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): @@ -1331,7 +1565,8 @@ def write_rust_table(names): ] cat = {"title": "Title", "build": "Build", "battle": "Battle", "assault": "Assault", "boss_paladin": "BossPaladin", "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: lines.append(' ("%s", Category::%s, include_bytes!("../assets/music/%s.xm")),' % (n, cat[SONGS[n][0]], n)) lines.append("];")