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:
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
@@ -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<usize> {
|
||||
// the calm tracks the music player offers (no battle, victory or defeat music)
|
||||
pub fn player_tracks() -> Vec<usize> {
|
||||
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 {
|
||||
@@ -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<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]
|
||||
fn engine_crossfade_and_sfx() {
|
||||
dc_audio_init(48000.0);
|
||||
|
||||
+10
-3
@@ -492,11 +492,12 @@ fn apply_settings(s: &Settings, r3d: &mut R3d) {
|
||||
struct MusicDirector {
|
||||
next: std::collections::HashMap<u8, usize>, // category -> next index within the category
|
||||
current: Option<audio::Category>,
|
||||
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
|
||||
|
||||
+4
-1
@@ -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")),
|
||||
];
|
||||
@@ -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;
|
||||
|
||||
+237
-2
@@ -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("];")
|
||||
|
||||
Reference in new issue
Block a user