cleaning up, working on audio playback, screw chrono duration, it sucks

main
aprzn 2 years ago
parent cb756f39d0
commit b71669f553

10
Cargo.lock generated

@ -1624,6 +1624,15 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "mp3-duration"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348bdc7300502f0801e5b57c448815713cd843b744ef9bda252a2698fdf90a0f"
dependencies = [
"thiserror",
]
[[package]]
name = "native-tls"
version = "0.2.11"
@ -2069,6 +2078,7 @@ dependencies = [
"itertools",
"log",
"md5",
"mp3-duration",
"ordered-float",
"reqwest",
"rodio",

@ -22,3 +22,4 @@ itertools = "0.10.5"
directories = "4.0.1"
simplelog = "0.12.0"
log = "0.4.17"
mp3-duration = "0.1.10"

@ -1,6 +1,6 @@
use crate::music::Lines;
use base64::engine::{general_purpose::URL_SAFE, Engine};
use chrono::Duration;
use std::time::Duration;
use eframe::egui::TextFormat;
use eframe::epaint::text::LayoutJob;
use flate2::read::GzDecoder;
@ -79,7 +79,7 @@ impl InnerLevel {
log::info!("{} could not be parsed", color_code);
return;
};
let Ok(duration) = timestamp.parse::<f64>().map(|t| Duration::nanoseconds((t * 1_000_000_000f64) as i64)) else {
let Ok(duration) = timestamp.parse::<f64>().map(|t| Duration::from_secs_f64(t)) else {
log::info!("{} could not be parsed", timestamp);
return;
};
@ -103,16 +103,13 @@ impl Song {
match self {
Self::Newgrounds { id } => {
let mut out = SongResponse(Default::default());
dbg!(req::ClientBuilder::new()
req::ClientBuilder::new()
.user_agent("")
.build()?
.post("http://www.boomlings.com/database/getGJSongInfo.php")
// .body(dbg!(format!(
// r#" {{ "secret": "Wmfd2893gb7", "songID": {} }} "#, id
// )))
.form(&[("songID", id.to_string().as_ref()), ("secret", "Wmfd2893gb7")])
.send()?
.text()?)
.text()?
.split("~|~")
.array_chunks()
.try_for_each(|[id, value]| -> Result<(), SongRequestError> {

@ -14,6 +14,8 @@ use std::fs::File;
use std::io::Write;
use std::mem;
use thiserror::Error;
use std::time;
use rodio::source::Source;
fn project_dirs() -> directories::ProjectDirs {
directories::ProjectDirs::from("xyz", "interestingzinc", "pipedash").expect("Home dir missing?")
@ -60,9 +62,9 @@ struct WizardEditor {
}
struct EditorState {
scroll_pos: f32,
pts_per_second: f32, // zoom level
subdivisions: f32,
scroll_pos: f64,
pts_per_second: f64, // zoom level
subdivisions: u32,
}
struct GdlData {
@ -74,9 +76,9 @@ struct GdlData {
}
struct WizardData {
green_lines: music::Lines<chrono::Duration>,
orange_lines: music::Lines<chrono::Duration>,
yellow_lines: music::Lines<chrono::Duration>,
green_lines: music::Lines<time::Duration>,
orange_lines: music::Lines<time::Duration>,
yellow_lines: music::Lines<time::Duration>,
beat_rate: Option<music::BeatRate>,
time_signatures: Option<music::TimeSignature>,
}
@ -84,7 +86,10 @@ struct WizardData {
struct Song {
name: String,
id: i64,
decoder: rodio::Decoder<File>,
buffer: rodio::buffer::SamplesBuffer<i16>,
length: time::Duration,
stream: rodio::OutputStream,
sink: rodio::Sink,
}
#[derive(Error, Debug)]
@ -101,26 +106,31 @@ enum SongError {
NgServerError(#[from] reqwest::Error),
#[error("Missing download link")]
MissingLink,
#[error("Unable to create audio stream")]
StreamError(#[from] rodio::StreamError),
#[error("Unable to create audio sink")]
SinkError(#[from] rodio::PlayError),
}
struct BeatRateWidget<'a> {
state: &'a mut EditorState,
beat_rate: Option<&'a mut music::BeatRate>,
song: &'a Song, // for waveform
}
struct TimeSignatureWidget<'a> {
state: &'a mut EditorState,
time_signatures: Option<&'a mut music::TimeSignature>,
song: &'a Song, // for waveform
}
struct LinesWidget<'a, T = music::BeatPosition>
where T: Ord
{
state: &'a mut EditorState,
lines: &'a mut music::Lines<T>,
color: Color,
}
struct WaveformWidget<'a> {
state: &'a mut EditorState,
song: &'a Song,
song: &'a Song, // for waveform
}
fn allocate_editor_space(ui: &mut egui::Ui) -> (egui::Rect, egui::Response) {
@ -141,25 +151,30 @@ impl From<Color> for eframe::epaint::Color32 {
impl EditorMode {
pub fn display(&mut self, ui: &mut egui::Ui) {
let ctx = ui.ctx();
match self {
EditorMode::RhythmWizard { editor, song } => {
ui.label("Rhythm Wizard");
},
EditorMode::Full { editor, song } => {
ui.add(editor.time_signature_widget());
ui.add(editor.beat_rate_widget());
ui.add(editor.lines_widget(Color::Green));
ui.add(editor.lines_widget(Color::Orange));
ui.add(editor.lines_widget(Color::Yellow));
ui.add(editor.waveform_widget(song));
editor.handle_keyboard_input(ctx, song);
ui.label("Editor");
ui.add(editor.time_signature_widget(song));
ui.add(editor.beat_rate_widget(song));
ui.add(editor.lines_widget(Color::Green, song));
ui.add(editor.lines_widget(Color::Orange, song));
ui.add(editor.lines_widget(Color::Yellow, song));
},
EditorMode::NoSong => {
ui.label("No song to edit");
},
EditorMode::NoSong => {},
}
}
}
impl Default for EditorState {
fn default() -> Self {
EditorState { scroll_pos: 0f32, pts_per_second: 10f32, subdivisions: 4f32 }
EditorState { scroll_pos: 0.0, pts_per_second: 10.0, subdivisions: 4 }
}
}
@ -169,7 +184,7 @@ impl Default for GdlData {
green_lines: Default::default(),
orange_lines: Default::default(),
yellow_lines: Default::default(),
beat_rate: music::StaticBeatRate::from_bpm(120f32).into(),
beat_rate: music::StaticBeatRate::from_bpm(120.0).into(),
time_signatures: music::StaticTimeSignature::new(4, 4).into(),
}
}
@ -204,9 +219,15 @@ impl Song {
(Err(err), Err(_)) => return Err(err.into()),
};
let length = mp3_duration::from_file(&file).unwrap();
let decoder = rodio::Decoder::new_mp3(file)?;
let buffer = rodio::buffer::SamplesBuffer::new(decoder.channels(), decoder.sample_rate(), decoder.collect::<Vec<i16>>());
let (stream, stream_handle) = rodio::OutputStream::try_default()?;
let sink = rodio::Sink::try_new(&stream_handle)?;
Ok(Self { name, id, decoder })
Ok(Self { name, id, buffer, length, stream, sink })
} else {
Err(SongError::NotNewgrounds)
}
@ -214,21 +235,23 @@ impl Song {
}
impl Editor {
pub fn beat_rate_widget(&mut self) -> BeatRateWidget {
pub fn beat_rate_widget<'a>(&'a mut self, song: & 'a mut Song) -> BeatRateWidget {
BeatRateWidget {
state: &mut self.state,
beat_rate: Some(&mut self.data.beat_rate),
song,
}
}
pub fn time_signature_widget(&mut self) -> TimeSignatureWidget {
pub fn time_signature_widget<'a>(&'a mut self, song: &'a mut Song) -> TimeSignatureWidget {
TimeSignatureWidget {
state: &mut self.state,
time_signatures: Some(&mut self.data.time_signatures),
song,
}
}
pub fn lines_widget(&mut self, col: Color) -> LinesWidget {
pub fn lines_widget<'a>(&'a mut self, col: Color, song: &'a mut Song) -> LinesWidget {
LinesWidget {
state: &mut self.state,
lines: match col {
@ -237,33 +260,57 @@ impl Editor {
Color::Orange => &mut self.data.orange_lines,
},
color: col,
song,
}
}
pub fn waveform_widget<'a>(&'a mut self, song: &'a Song) -> WaveformWidget {
WaveformWidget {
state: &mut self.state,
song,
/// points in width of entire song
fn song_width(&self, song: &Song) -> f64 {
song.length.as_secs_f64() * self.state.pts_per_second
}
fn play_pause(&self, song: &Song) {
todo!("toggle song playback")
}
fn handle_keyboard_input(&mut self, ctx: &egui::Context, song: &Song) {
use egui::Key;
use egui::Event;
ctx.input().events
.iter()
.for_each(|ev| match ev {
Event::Key { key: Key::ArrowLeft, pressed: true, modifiers } => self.scroll(-5.0, song),
Event::Key { key: Key::ArrowRight, pressed: true, modifiers } => self.scroll(5.0, song),
Event::Key { key: Key::Space, pressed: true, modifiers } => self.play_pause(song),
_ => (),
});
}
fn scroll(&mut self, pts: f64, song: &Song) {
self.state.scroll_pos += pts;
self.state.scroll_pos.clamp(0f64, self.song_width(song));
}
}
impl WizardEditor {
pub fn beat_rate_widget(&mut self) -> BeatRateWidget {
pub fn beat_rate_widget<'a>(&'a mut self, song: &'a mut Song) -> BeatRateWidget {
BeatRateWidget {
state: &mut self.state,
beat_rate: self.data.beat_rate.as_mut(),
song,
}
}
pub fn time_signature_widget(&mut self) -> TimeSignatureWidget {
pub fn time_signature_widget<'a>(&'a mut self, song: &'a mut Song) -> TimeSignatureWidget {
TimeSignatureWidget {
state: &mut self.state,
time_signatures: self.data.time_signatures.as_mut(),
song,
}
}
pub fn lines_widget(&mut self, col: Color) -> LinesWidget<chrono::Duration> {
pub fn lines_widget<'a>(&'a mut self, col: Color, song: &'a mut Song) -> LinesWidget<time::Duration> {
LinesWidget {
state: &mut self.state,
lines: match col {
@ -272,12 +319,6 @@ impl WizardEditor {
Color::Orange => &mut self.data.orange_lines,
},
color: col,
}
}
pub fn waveform_widget<'a>(&'a mut self, song: &'a Song) -> WaveformWidget {
WaveformWidget {
state: &mut self.state,
song,
}
}
@ -308,7 +349,7 @@ impl<'a> egui::Widget for BeatRateWidget<'a> {
// draw widget
if ui.is_rect_visible(rect) {
ui.painter()
.rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
.rect_filled(rect, 0.0, eframe::epaint::Color32::from_gray(0));
}
res
}
@ -325,7 +366,7 @@ impl<'a> egui::Widget for TimeSignatureWidget<'a> {
// 4. draw widget
if ui.is_rect_visible(rect) {
ui.painter()
.rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
.rect_filled(rect, 0.0, eframe::epaint::Color32::from_gray(0));
}
res
}
@ -342,24 +383,7 @@ impl<'a> egui::Widget for LinesWidget<'a> {
// 4. draw widget
if ui.is_rect_visible(rect) {
ui.painter()
.rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
}
res
}
}
impl<'a> egui::Widget for WaveformWidget<'a> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
// 1. choose size
let max_rect = ui.max_rect();
let preferred_size = egui::Vec2::new(max_rect.size().x, 60.0);
// 2. allocate space
let (rect, res) = ui.allocate_exact_size(preferred_size, egui::Sense::click_and_drag());
// 3. handle interactions
// 4. draw widget
if ui.is_rect_visible(rect) {
ui.painter()
.rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
.rect_filled(rect, 0.0, eframe::epaint::Color32::from_gray(0));
}
res
}
@ -379,7 +403,7 @@ impl PipeDash {
fn side_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::SidePanel::left("level_picker")
.default_width(100f32)
.default_width(100.0)
.show(ctx, |ui| {
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
if ui
@ -484,7 +508,7 @@ impl PipeDash {
impl eframe::App for PipeDash {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
ctx.set_pixels_per_point(2f32);
ctx.set_pixels_per_point(2.0);
if let Some(boxed_err) = &self.errors.front() {
egui::CentralPanel::default().show(ctx, |ui| {

@ -1,4 +1,4 @@
use chrono::Duration;
use std::time::Duration;
use ordered_float::OrderedFloat as Float;
use std::collections::{BTreeMap, BTreeSet};
@ -35,7 +35,7 @@ where
impl StaticBeatRate {
pub fn from_bpm(bpm: f32) -> Self {
Self(Duration::microseconds(60_000_000 / bpm as i64))
Self(Duration::from_secs_f32(60.0 / bpm))
}
}

Loading…
Cancel
Save