Created
July 27, 2023 07:33
-
-
Save sgeos/854030afad6599b3ee788d374d0a692e to your computer and use it in GitHub Desktop.
This version of the piano roll opens a window and plays a looped piano roll until the user presses a key. ChatGPT4 was used instead of relying on standard technical documentation for information.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate sdl2; | |
use sdl2::{ | |
audio::{ AudioCallback, AudioSpecDesired, }, | |
event::Event, | |
pixels::Color, | |
}; | |
use std::{ time::{ Duration, Instant, }, }; | |
#[derive(Copy, Clone)] | |
enum Note { | |
A = 0, | |
ASharp = 1, | |
B = 2, | |
C = 3, | |
CSharp = 4, | |
D = 5, | |
DSharp = 6, | |
E = 7, | |
F = 8, | |
FSharp = 9, | |
G = 10, | |
GSharp = 11, | |
} | |
#[derive(Copy, Clone)] | |
struct OctaveNote { | |
note: Note, | |
octave: i32, | |
} | |
impl OctaveNote { | |
fn frequency(&self) -> f32 { | |
let base_frequency = 440.0; // Frequency of A4 | |
let base_octave = 4; // Octave of A4 | |
let note_distance = (self.note as i32) + (self.octave - base_octave) * 12; | |
base_frequency * (2f32).powf(note_distance as f32 / 12.0) | |
} | |
} | |
struct TriangleWave { | |
phase: f32, | |
phase_inc: f32, | |
amplitude: i16, | |
} | |
impl AudioCallback for TriangleWave { | |
type Channel = i16; | |
fn callback(&mut self, out: &mut [i16]) { | |
for x in out.iter_mut() { | |
*x = (self.amplitude as f32 * self.phase) as i16; | |
self.phase += self.phase_inc; | |
if self.phase >= 1.0 { | |
self.phase -= 2.0; | |
} | |
} | |
} | |
} | |
fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) { | |
let c = (1.0 - (2.0 * l - 1.0).abs()) * s; | |
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); | |
let m = l - c/2.0; | |
let (r, g, b) = if h < 60.0 { | |
(c, x, 0.0) | |
} else if h < 120.0 { | |
(x, c, 0.0) | |
} else if h < 180.0 { | |
(0.0, c, x) | |
} else if h < 240.0 { | |
(0.0, x, c) | |
} else if h < 300.0 { | |
(x, 0.0, c) | |
} else { | |
(c, 0.0, x) | |
}; | |
(((r + m) * 255.0) as u8, ((g + m) * 255.0) as u8, ((b + m) * 255.0) as u8) | |
} | |
fn main() { | |
let fps = 60.0; | |
let frame_duration = 1.0 / fps; | |
let window_title = "Piano Roll Demo"; | |
let window_w = 800; | |
let window_h = 600; | |
let sdl_context = sdl2::init().unwrap(); | |
let mut event_pump = sdl_context.event_pump().unwrap(); | |
let video_subsystem = sdl_context.video().unwrap(); | |
let window = video_subsystem.window(window_title, window_w, window_h) | |
.position_centered() // Center the window | |
.opengl() // Use the opengl backend | |
.build() | |
.unwrap(); | |
let mut canvas = window.into_canvas().build().unwrap(); | |
let mut hue = 0.0; | |
let audio_subsystem = sdl_context.audio().unwrap(); | |
let desired_spec = AudioSpecDesired { | |
freq: Some(44100), | |
channels: Some(1), // mono | |
samples: None // default sample size | |
}; | |
let mut device; | |
let piano_roll = vec![ | |
(0.5, OctaveNote { note: Note::C, octave: 4 }), | |
(0.5, OctaveNote { note: Note::B, octave: 4 }), | |
(0.5, OctaveNote { note: Note::D, octave: 4 }), | |
(0.5, OctaveNote { note: Note::ASharp, octave: 3 }), | |
(1.0, OctaveNote { note: Note::CSharp, octave: 4 }), | |
(0.5, OctaveNote { note: Note::F, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(0.5, OctaveNote { note: Note::G, octave: 4 }), | |
(0.5, OctaveNote { note: Note::A, octave: 4 }), | |
(1.0, OctaveNote { note: Note::GSharp, octave: 4 }), | |
(0.5, OctaveNote { note: Note::FSharp, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(0.5, OctaveNote { note: Note::C, octave: 4 }), | |
(1.0, OctaveNote { note: Note::DSharp, octave: 4 }), | |
(0.5, OctaveNote { note: Note::C, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(1.0, OctaveNote { note: Note::F, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(0.5, OctaveNote { note: Note::C, octave: 4 }), | |
(1.0, OctaveNote { note: Note::D, octave: 4 }), | |
(0.5, OctaveNote { note: Note::D, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(0.5, OctaveNote { note: Note::F, octave: 4 }), | |
(0.5, OctaveNote { note: Note::G, octave: 4 }), | |
(1.0, OctaveNote { note: Note::G, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(0.5, OctaveNote { note: Note::C, octave: 4 }), | |
(1.0, OctaveNote { note: Note::C, octave: 4 }), | |
(0.5, OctaveNote { note: Note::G, octave: 4 }), | |
(0.5, OctaveNote { note: Note::E, octave: 4 }), | |
(1.0, OctaveNote { note: Note::C, octave: 4 }), | |
]; | |
let mut piano_roll_iter = piano_roll.iter().cycle(); | |
let (mut duration, mut note) = piano_roll_iter.next().unwrap().clone(); | |
let mut note_end_time = Instant::now(); | |
println!("Press any key to quit, when the window has focus."); | |
'main_loop: loop { | |
// handle input | |
for event in event_pump.poll_iter() { | |
match event { | |
Event::Quit {..} | | |
Event::KeyDown {..} => { | |
break 'main_loop; | |
}, | |
_ => {} | |
} | |
} | |
// video update | |
hue = (hue + 1.0) % 360.0; | |
let (r, g, b) = hsl_to_rgb(hue, 1.0, 0.5); | |
canvas.set_draw_color(Color::RGB(r, g, b)); | |
canvas.clear(); | |
canvas.present(); | |
// handle piano roll | |
if note_end_time <= Instant::now() { | |
// play note | |
device = audio_subsystem.open_playback(None, &desired_spec, |spec| { | |
TriangleWave { | |
phase: 0.1, | |
phase_inc: note.frequency() / spec.freq as f32, // 440 Hz | |
amplitude: i16::MAX / 4, // reduce amplitude to protect our ears | |
} | |
}).unwrap(); | |
device.resume(); | |
// prepare next note | |
note_end_time = Instant::now() + Duration::from_secs_f32(duration); | |
let (next_duration, next_note) = piano_roll_iter.next().unwrap(); | |
note = next_note.clone(); | |
duration = *next_duration; | |
} | |
// Delay until the end of the frame. | |
std::thread::sleep(Duration::from_secs_f32(frame_duration)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment