Skip to content

Instantly share code, notes, and snippets.

@sgeos
Created July 27, 2023 07:33
Show Gist options
  • Save sgeos/854030afad6599b3ee788d374d0a692e to your computer and use it in GitHub Desktop.
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.
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