Skip to content

Commit

Permalink
Quick start guide screen, refactor desync workaround
Browse files Browse the repository at this point in the history
  • Loading branch information
polina4096 committed Aug 21, 2024
1 parent 556775e commit c6d344d
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 76 deletions.
75 changes: 10 additions & 65 deletions crates/apex-client/src/client/audio/game_audio.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::{fs::File, path::Path};

use apex_framework::{
audio::{arc_buffer::ArcSamplesBuffer, audio_engine::AudioEngine, audio_mixer::AudioController, lead_in::lead_in},
audio::{
arc_buffer::ArcSamplesBuffer, audio_engine::AudioEngine, audio_mixer::AudioController,
frameless_source::FramelessSource, lead_in::lead_in,
},
time::{clock::AbstractClock, time::Time},
};
use rodio::{source::UniformSourceIterator, Decoder, Device, DeviceTrait as _, Sample, Source, SupportedStreamConfig};
use rodio::{source::UniformSourceIterator, Decoder, Device, DeviceTrait as _, Source, SupportedStreamConfig};

/// Audio wrapper which allows for leading and trailing additional delays, or other gameplay-specific things.
pub struct GameAudio {
audio_engine: AudioEngine,
audio_controller: AudioController,
Expand Down Expand Up @@ -82,7 +86,10 @@ impl GameAudio {
let channels = self.config.channels();
let sample_rate = self.config.sample_rate();
let source = Decoder::new(File::open(path).unwrap()).unwrap();
let source = UniformSourceIterator::new(source, channels, sample_rate.0);

// FramelessSource is needed for a audio desync workaround, see https://github.com/RustAudio/rodio/issues/316
let source = UniformSourceIterator::new(FramelessSource::new(source), channels, sample_rate.0);

return ArcSamplesBuffer::<f32>::new(channels, sample_rate.0, source.collect::<Vec<_>>());
}
}
Expand Down Expand Up @@ -138,65 +145,3 @@ impl GameAudioController {
self.0.set_sound_volume(volume);
}
}

pub struct FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
inner: I,
}

impl<I> FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
pub fn new(source: I) -> Self {
Self { inner: source }
}
}

impl<I> From<I> for FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
fn from(value: I) -> Self {
Self::new(value)
}
}

impl<I> Iterator for FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}

impl<I> Source for FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
fn current_frame_len(&self) -> Option<usize> {
None
}

fn channels(&self) -> u16 {
self.inner.channels()
}

fn sample_rate(&self) -> u32 {
self.inner.sample_rate()
}

fn total_duration(&self) -> Option<std::time::Duration> {
self.inner.total_duration()
}
}
15 changes: 10 additions & 5 deletions crates/apex-client/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use rusqlite::Connection;
use tap::Tap;
use triomphe::Arc;
use winit::{
dpi::PhysicalSize,
dpi::{LogicalSize, PhysicalSize},
event::{KeyEvent, Modifiers},
event_loop::{ActiveEventLoop, EventLoopProxy},
keyboard::{KeyCode, PhysicalKey},
Expand All @@ -25,7 +25,7 @@ use winit::{

use apex_framework::{
app::App,
audio::{self, audio_engine::AudioEngine},
audio::{self, audio_engine::AudioEngine, frameless_source::FramelessSource},
core::Core,
data::persistent::Persistent as _,
event::{CoreEvent, EventBus},
Expand All @@ -45,7 +45,7 @@ use apex_framework::{

use super::{
action::ClientAction,
audio::game_audio::{FramelessSource, GameAudio},
audio::game_audio::GameAudio,
event::ClientEvent,
gameplay::beatmap_cache::{BeatmapCache, BeatmapInfo},
graphics::{FrameLimiterOptions, RenderingBackend},
Expand Down Expand Up @@ -153,6 +153,10 @@ impl App for Client {
.block_on();
}

fn window_attrs() -> winit::window::WindowAttributes {
return Window::default_attributes().with_title("Apex").with_min_inner_size(LogicalSize::new(800.0, 600.0));
}

fn prepare(&mut self, core: &mut Core<Self>, encoder: &mut wgpu::CommandEncoder) {
core.egui.begin_frame(&core.window);
core.egui.ctx().style_mut(|style| {
Expand Down Expand Up @@ -543,8 +547,9 @@ impl Client {
let source = Decoder::new(file).unwrap();

let config = audio.device().default_output_config().unwrap();
let source = FramelessSource::new(source);
let source = UniformSourceIterator::new(source, config.channels(), config.sample_rate().0);

// FramelessSource is needed for a audio desync workaround, see https://github.com/RustAudio/rodio/issues/316
let source = UniformSourceIterator::new(FramelessSource::new(source), config.channels(), config.sample_rate().0);

// TODO: calculate length of the audio
let length = source.total_duration().unwrap_or(Duration::from_secs(0));
Expand Down
8 changes: 8 additions & 0 deletions crates/apex-client/src/client/gameplay/beatmap_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,12 @@ impl BeatmapCache {
pub fn last_update(&self) -> Instant {
return self.last_update;
}

pub fn len(&self) -> usize {
return self.cache.len();
}

pub fn is_empty(&self) -> bool {
return self.cache.is_empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use jiff::Timestamp;
use rodio::{source::UniformSourceIterator, Decoder, DeviceTrait};

use crate::client::{
audio::game_audio::{FramelessSource, GameAudio, GameAudioController},
audio::game_audio::{GameAudio, GameAudioController},
client::Client,
event::ClientEvent,
gameplay::{
Expand All @@ -18,7 +18,7 @@ use crate::client::{
ui::{break_overlay::BreakOverlayView, ingame_overlay::IngameOverlayView},
};
use apex_framework::{
audio::arc_buffer::ArcSamplesBuffer,
audio::{arc_buffer::ArcSamplesBuffer, frameless_source::FramelessSource},
core::Core,
event::EventBus,
graphics::{
Expand Down Expand Up @@ -180,8 +180,9 @@ impl GameplayScreen {
let audio_path = beatmap_path.parent().unwrap().join(&beatmap.audio);
let file = BufReader::new(File::open(audio_path).unwrap());
let source = Decoder::new(file).unwrap();
let source = FramelessSource::new(source);
let source = UniformSourceIterator::new(source, config.channels(), config.sample_rate().0);

// FramelessSource is needed for a audio desync workaround, see https://github.com/RustAudio/rodio/issues/316
let source = UniformSourceIterator::new(FramelessSource::new(source), config.channels(), config.sample_rate().0);

let end_time = beatmap.hit_objects.last().unwrap().time;

Expand Down
72 changes: 71 additions & 1 deletion crates/apex-client/src/client/ui/beatmap_selection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,76 @@ impl BeatmapSelectionView {

use egui_extras::{Size, StripBuilder};

if beatmap_cache.is_empty() {
egui::Window::new("no_beatmaps")
.title_bar(false)
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
.fixed_size(egui::vec2(320.0, 0.0))
.show(core.egui.ctx(), |ui| {
ui.add_space(10.0);

ui.vertical_centered(|ui| {
ui.label(egui::RichText::new("🗋 No beatmaps found!").heading().strong());
});

ui.add_space(8.0);

ui.separator();

egui::Frame::none() //
.inner_margin(egui::Margin::symmetric(12.0, 8.0))
.show(ui, |ui| {
let mut job = egui::text::LayoutJob::default();

job.append("Import beatmaps by dragging ", 0.0, egui::TextFormat::default());

job.append(
".ozs",
0.0,
egui::TextFormat {
font_id: egui::FontId::monospace(12.0),
..Default::default()
},
);

job.append(" files onto the window, or move them into the ", 0.0, egui::TextFormat::default());

job.append(
"beatmaps",
0.0,
egui::TextFormat {
font_id: egui::FontId::monospace(12.0),
..Default::default()
},
);

job.append(
" directory. If a beatmap is missing, enqueue a cache rebuild with ",
0.0,
egui::TextFormat::default(),
);

// Actually unimplemented
job.append(
"Super + R",
0.0,
egui::TextFormat {
font_id: egui::FontId::monospace(12.0),
..Default::default()
},
);

job.append(" anywhere.", 0.0, egui::TextFormat::default());

ui.vertical_centered(|ui| {
ui.label(job);
});
});
});

return;
}

let selected = selector.selected();
let Some((path, info)) = beatmap_cache.get_index(selected) else {
// TODO: Show error message no beatmaps found
Expand Down Expand Up @@ -113,7 +183,7 @@ impl BeatmapSelectionView {

StripBuilder::new(ui) //
.size(Size::remainder())
.size(Size::relative(0.4))
.size(Size::relative(0.4).at_most(512.0))
.horizontal(|mut builder| {
builder.cell(|ui| {
ui.with_layout(egui::Layout::top_down(egui::Align::Min), |ui| {
Expand Down
5 changes: 4 additions & 1 deletion crates/apex-framework/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use triomphe::Arc;
use winit::dpi::PhysicalSize;
use winit::event::{KeyEvent, Modifiers};
use winit::window::WindowAttributes;
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
Expand All @@ -30,6 +31,8 @@ pub trait App: Drawable + Sized {

fn recreate_graphics(&mut self, core: &mut Core<Self>) -> Graphics;

fn window_attrs() -> WindowAttributes;

fn prepare(&mut self, core: &mut Core<Self>, encoder: &mut wgpu::CommandEncoder) {}
fn render(&self, core: &mut Core<Self>, encoder: &mut wgpu::CommandEncoder, view: wgpu::TextureView) {}
fn input(&mut self, core: &mut Core<Self>, event: KeyEvent) {}
Expand Down Expand Up @@ -64,7 +67,7 @@ impl<A: App> ApexFrameworkApplication<A> {

impl<A: App> ApplicationHandler<CoreEvent<A::Event>> for ApexFrameworkApplication<A> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attrs = Window::default_attributes() //
let window_attrs = A::window_attrs() //
.with_inner_size(LogicalSize::new(1200, 800));

let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
Expand Down
65 changes: 65 additions & 0 deletions crates/apex-framework/src/audio/frameless_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use std::time::Duration;

use rodio::{Sample, Source};

pub struct FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
inner: I,
}

impl<I> FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
pub fn new(source: I) -> Self {
return Self { inner: source };
}
}

impl<I> From<I> for FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
fn from(value: I) -> Self {
return Self::new(value);
}
}

impl<I> Iterator for FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
return self.inner.next();
}
}

impl<I> Source for FramelessSource<I>
where
I: Source,
I::Item: Sample,
{
fn current_frame_len(&self) -> Option<usize> {
return None;
}

fn channels(&self) -> u16 {
return self.inner.channels();
}

fn sample_rate(&self) -> u32 {
return self.inner.sample_rate();
}

fn total_duration(&self) -> Option<Duration> {
return self.inner.total_duration();
}
}
1 change: 1 addition & 0 deletions crates/apex-framework/src/audio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use rodio::{cpal::traits::HostTrait as _, OutputStream, OutputStreamHandle, Stre
pub mod arc_buffer;
pub mod audio_engine;
pub mod audio_mixer;
pub mod frameless_source;
pub mod lead_in;

pub use audio_mixer::mixer;
Expand Down

0 comments on commit c6d344d

Please sign in to comment.