#![feature(array_chunks)] use cpal::{ BufferSize, SampleRate, StreamConfig, traits::{DeviceTrait, HostTrait, StreamTrait}, }; use glam::{Vec2, vec2}; use sdl2::event::Event; use std::{ collections::VecDeque, env::args, f32::consts::PI, fs::read_to_string, sync::mpsc::{Receiver, channel}, thread::sleep, time::{Duration, Instant}, }; fn main() { let start = Instant::now(); let mut points = VecDeque::new(); let (mouse_tx, mouse_rx) = channel(); let mut state = State::new(mouse_rx); let mut frames = 0; let mut last_stats = Instant::now(); let host = cpal::default_host(); let outdev = host.default_output_device().unwrap(); let outstream = outdev .build_output_stream( &StreamConfig { buffer_size: BufferSize::Default, channels: 2, sample_rate: SampleRate(48_000), }, move |data: &mut [f32], _| { let time = start.elapsed().as_secs_f32(); while points.len() < data.len() / 2 { state.draw(&mut points, time); frames += 1; } if last_stats.elapsed().as_secs() >= 1 { last_stats = Instant::now(); eprintln!("FPS = {frames}"); frames = 0; } for [x, y] in data.array_chunks_mut::<2>() { let p = points.pop_front().unwrap(); *x = p.x; *y = p.y; } }, |err| eprintln!("{err}"), None, ) .unwrap(); outstream.play().unwrap(); let sdl = sdl2::init().unwrap(); let video = sdl.video().unwrap(); let mut window = video .window("Input capture", 256, 256) .resizable() .build() .unwrap(); let mut events = sdl.event_pump().unwrap(); let mut last_draw = Instant::now(); 'blub: loop { for event in events.poll_iter() { match event { Event::Window { .. } => {} Event::MouseButtonDown { .. } => { window.set_mouse_grab(true); mouse_tx.send(Vec2::NEG_INFINITY).unwrap(); } Event::MouseMotion { xrel, yrel, .. } => { mouse_tx.send(vec2(xrel as f32, -yrel as f32)).unwrap(); } Event::Quit { .. } => break 'blub, _ => (), } } if last_draw.elapsed().as_secs_f32() > 0.1 { last_draw = Instant::now(); let mut surf = window.surface(&events).unwrap(); surf.fill_rect( sdl2::rect::Rect::new(0, 0, 100, 100), sdl2::pixels::Color::RED, ) .unwrap(); surf.finish().unwrap(); } sleep(Duration::from_millis(1)); } } struct State { mouse: Vec2, mouse_rx: Receiver, objects: Vec, } impl State { pub fn new(mouse_rx: Receiver) -> Self { let objects = parse_file(&read_to_string(&args().nth(1).unwrap()).unwrap()); Self { mouse: Vec2::ZERO, mouse_rx, objects, } } pub fn draw(&mut self, points: &mut VecDeque, time: f32) { for x in self.mouse_rx.try_iter() { if x.is_finite() { self.mouse += x / 100.; } else { self.mouse *= 0.; } } for i in 0..200 { let t = i as f32 / 30. * PI * 2.; points.push_back((Vec2::from_angle(t) * 2.).clamp(Vec2::NEG_ONE, Vec2::ONE)); } for _ in 0..200 { points.push_back(self.mouse); } render_frame(&self.objects, points, time); } } fn render_frame(obs: &[HitObject], points: &mut VecDeque, time: f32) { let mut render_any = false; for h in obs { let t = h.time as f32 / 1000.; let trel = time - t; if trel > 0. || trel < -1. { continue; } render_any = true; let center = vec2(h.x as f32 / 512., h.y as f32 / 384.) * 2. - 1.; let base_r = 0.1; let approach_r = base_r + trel / -5.; draw_circle(points, center, base_r, 200); draw_circle(points, center, approach_r, 200); } if !render_any { points.extend((0..100).map(|_| Vec2::ZERO)); } } fn draw_circle(points: &mut VecDeque, center: Vec2, r: f32, res: usize) { for i in 0..res { let t = i as f32 / res as f32 * PI * 2.; points.push_back(center + Vec2::from_angle(t) * r); } } struct HitObject { x: i32, y: i32, time: i32, } fn parse_file(s: &str) -> Vec { let mut obs = Vec::new(); let b = s.split_once("[HitObjects]").unwrap().1; for line in b.lines() { if line.is_empty() { continue; } let mut toks = line.split(","); let x = toks.next().unwrap().parse().unwrap(); let y = toks.next().unwrap().parse().unwrap(); let time = toks.next().unwrap().parse().unwrap(); obs.push(HitObject { x, y, time }) } obs }