///|
/// Semitone musical pitch class in standard 12-tone equal temperament.
#derive(Eq, Debug)
pub(all) enum Note {
C
Cs // C# / Db
D
Ds // D# / Eb
E
F
Fs // F# / Gb
G
Gs // G# / Ab
A
As // A# / Bb
B
Rest // Silence
}
///|
/// Musical note duration relative to measure meter.
#derive(Eq, Debug)
pub(all) enum Duration {
Whole // 4 beats
Half // 2 beats
Quarter // 1 beat
Eighth // 0.5 beat
Sixteenth // 0.25 beat
}
///|
/// A single scored note event with pitch, octave, duration, and velocity volume.
#derive(Eq, Debug)
pub struct ScoreItem {
note : Note
octave : Int32
duration : Duration
volume : Double
}
///|
/// Creates a new ScoreItem.
pub fn ScoreItem::new(
note : Note,
octave : Int32,
duration : Duration,
volume : Double,
) -> ScoreItem {
let v = if volume < 0.0 { 0.0 } else if volume > 1.0 { 1.0 } else { volume }
{ note, octave, duration, volume: v, }
}
///|
/// Calculates exact fundamental frequency in Hertz based on A4 = 440 Hz standard.
pub fn note_to_frequency(note : Note, octave : Int32) -> Double {
match note {
Rest => 0.0
_ => {
let semitone_offset = match note {
C => 0
Cs => 1
D => 2
Ds => 3
E => 4
F => 5
Fs => 6
G => 7
Gs => 8
A => 9
As => 10
B => 11
Rest => 0
}
// MIDI note number: C0 is 12, A4 is 69
let midi_note = (octave + 1) * 12 + semitone_offset
let exponent = (midi_note - 69).to_double() / 12.0
440.0 * @math.pow(2.0, exponent)
}
}
}
///|
/// Converts Duration enum to duration in seconds based on Tempo BPM.
pub fn duration_to_seconds(duration : Duration, bpm : Int32) -> Double {
let tempo = if bpm <= 20 { 120.0 } else { bpm.to_double() }
let beat_sec = 60.0 / tempo // Length of 1 quarter note in seconds
match duration {
Whole => beat_sec * 4.0
Half => beat_sec * 2.0
Quarter => beat_sec * 1.0
Eighth => beat_sec * 0.5
Sixteenth => beat_sec * 0.25
}
}
///|
/// A musical sequence of notes played sequentially.
#derive(Eq, Debug)
pub struct Score {
items : Array[ScoreItem]
}
///|
/// Creates an empty musical score.
pub fn Score::new() -> Score {
{ items: [], }
}
///|
/// Appends a note to the musical score.
pub fn Score::add(
self : Score,
note : Note,
octave : Int32,
duration : Duration,
volume : Double,
) -> Unit {
self.items.push(ScoreItem::new(note, octave, duration, volume))
}
///|
/// Returns the number of notes in this score.
pub fn Score::length(self : Score) -> Int32 {
self.items.length()
}
///|
/// Creates a classic retro C Major ascending/descending arpeggio.
pub fn Score::arpeggio_c_major() -> Score {
let s = Score::new()
s.add(C, 4, Eighth, 0.8)
s.add(E, 4, Eighth, 0.8)
s.add(G, 4, Eighth, 0.8)
s.add(C, 5, Quarter, 0.9)
s.add(G, 4, Eighth, 0.8)
s.add(E, 4, Eighth, 0.8)
s.add(C, 4, Half, 0.85)
s
}
///|
/// Creates a triumphant retro game victory fanfare jingle.
pub fn Score::victory_fanfare() -> Score {
let s = Score::new()
s.add(C, 4, Sixteenth, 0.8)
s.add(C, 4, Sixteenth, 0.8)
s.add(C, 4, Sixteenth, 0.8)
s.add(C, 4, Eighth, 0.8)
s.add(Gs, 3, Eighth, 0.8)
s.add(As, 3, Eighth, 0.8)
s.add(C, 4, Quarter, 0.9)
s.add(Rest, 4, Sixteenth, 0.0)
s.add(As, 3, Sixteenth, 0.8)
s.add(C, 4, Half, 0.95)
s
}
///|
/// Renders an entire Score into an AudioBuffer using the specified waveform and ADSR articulation.
pub fn render_score(
score : Score,
bpm : Int32,
waveform : Waveform,
adsr : ADSR,
sample_rate : Int32,
) -> AudioBuffer {
let sr = if sample_rate <= 0 { 44100 } else { sample_rate }
let master = AudioBuffer::new(sr, 0)
for i = 0; i < score.items.length(); i = i + 1 {
let item = score.items[i]
let dur = duration_to_seconds(item.duration, bpm)
let freq = note_to_frequency(item.note, item.octave)
if item.note == Rest || freq <= 0.0 {
let silent_count = (dur * sr.to_double()).to_int()
let silence = AudioBuffer::with_silence(sr, silent_count)
master.mix(silence, master.length(), 0.0)
} else {
let tone = render_tone(waveform, freq, dur, sr, item.volume)
tone.apply_adsr(adsr, Exponential)
master.mix(tone, master.length(), 1.0)
}
}
master
}