mirror of
https://github.com/zbanks/radiance.git
synced 2026-06-16 04:26:25 +02:00
Add bouncing ball widget
This commit is contained in:
+15
-2
@@ -28,7 +28,7 @@ use radiance::{
|
||||
|
||||
mod ui;
|
||||
use ui::{modal, modal_shown, mosaic};
|
||||
use ui::{SpectrumWidget, WaveformWidget};
|
||||
use ui::{BeatWidget, SpectrumWidget, WaveformWidget};
|
||||
|
||||
mod winit_output;
|
||||
use winit_output::WinitOutput;
|
||||
@@ -171,6 +171,7 @@ pub async fn run() {
|
||||
// Make widgets
|
||||
let mut waveform_widget = WaveformWidget::new(device.clone(), queue.clone(), pixels_per_point);
|
||||
let mut spectrum_widget = SpectrumWidget::new(device.clone(), queue.clone(), pixels_per_point);
|
||||
let mut beat_widget = BeatWidget::new(device.clone(), queue.clone(), pixels_per_point);
|
||||
|
||||
// Make an AutoDJ
|
||||
let mut auto_dj_1: Option<AutoDJ> = None;
|
||||
@@ -336,6 +337,7 @@ pub async fn run() {
|
||||
|
||||
let mut waveform_texture: Option<egui::TextureId> = None;
|
||||
let mut spectrum_texture: Option<egui::TextureId> = None;
|
||||
let mut beat_texture: Option<egui::TextureId> = None;
|
||||
|
||||
let mut autosave_timer: usize = 0;
|
||||
|
||||
@@ -438,7 +440,7 @@ pub async fn run() {
|
||||
let waveform_native_texture = waveform_widget.paint(
|
||||
waveform_size,
|
||||
&music_info.audio,
|
||||
music_info.uncompensated_time,
|
||||
music_info.uncompensated_unscaled_time,
|
||||
);
|
||||
|
||||
update_or_register_native_texture(
|
||||
@@ -459,6 +461,16 @@ pub async fn run() {
|
||||
&mut spectrum_texture,
|
||||
);
|
||||
|
||||
let beat_size = egui::vec2(65., 65.);
|
||||
let beat_native_texture = beat_widget.paint(beat_size, music_info.unscaled_time);
|
||||
|
||||
update_or_register_native_texture(
|
||||
&mut egui_renderer,
|
||||
&device,
|
||||
&beat_native_texture.view,
|
||||
&mut beat_texture,
|
||||
);
|
||||
|
||||
// EGUI update
|
||||
let raw_input = platform.take_egui_input(&window);
|
||||
let full_output = egui_ctx.run(raw_input, |egui_ctx| {
|
||||
@@ -478,6 +490,7 @@ pub async fn run() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.image(waveform_texture.unwrap(), waveform_size);
|
||||
ui.image(spectrum_texture.unwrap(), spectrum_size);
|
||||
ui.image(beat_texture.unwrap(), beat_size);
|
||||
ui.checkbox(&mut auto_dj_1_enabled, "Auto DJ 1");
|
||||
ui.checkbox(&mut auto_dj_2_enabled, "Auto DJ 2");
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
use egui::Vec2;
|
||||
use radiance::ArcTextureViewSampler;
|
||||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct BeatWidget {
|
||||
// Constructor arguments:
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
pixels_per_point: f32,
|
||||
|
||||
// Internal state:
|
||||
width: u32,
|
||||
height: u32,
|
||||
texture: ArcTextureViewSampler,
|
||||
_shader_module: wgpu::ShaderModule,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
// The uniform buffer associated with the ball
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
resolution: [f32; 2], // in pixels
|
||||
size: [f32; 2], // in points
|
||||
beat: f32,
|
||||
_padding: [u8; 4],
|
||||
}
|
||||
|
||||
impl BeatWidget {
|
||||
fn make_texture(device: &wgpu::Device, width: u32, height: u32) -> ArcTextureViewSampler {
|
||||
let texture_size = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture_desc = wgpu::TextureDescriptor {
|
||||
size: texture_size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::COPY_SRC
|
||||
| wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
label: None,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&texture_desc);
|
||||
|
||||
let view = texture.create_view(&Default::default());
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
ArcTextureViewSampler::new(texture, view, sampler)
|
||||
}
|
||||
|
||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, pixels_per_point: f32) -> Self {
|
||||
let width = 1;
|
||||
let height = 1;
|
||||
let texture = Self::make_texture(&device, width, height);
|
||||
|
||||
let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(&"beat widget shader module"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("beat_widget.wgsl").into()),
|
||||
});
|
||||
|
||||
// The uniform buffer for this widget
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(&"beat widget uniform buffer"),
|
||||
size: std::mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some(&"beat widget bind group layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0, // Uniforms
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("beat bind group"),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Beat widget render pipeline layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Beat widget render pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader_module,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader_module,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleStrip,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
pixels_per_point,
|
||||
|
||||
width,
|
||||
height,
|
||||
texture,
|
||||
_shader_module: shader_module,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
render_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, size: Vec2, beat: f32) -> ArcTextureViewSampler {
|
||||
// Possibly remake the texture if the size has changed
|
||||
let width = (size.x * self.pixels_per_point) as u32;
|
||||
let height = (size.y * self.pixels_per_point) as u32;
|
||||
if width != self.width || height != self.height {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.texture = Self::make_texture(&self.device, width, height);
|
||||
}
|
||||
|
||||
// Populate the uniforms
|
||||
let uniforms = Uniforms {
|
||||
resolution: [width as f32, height as f32],
|
||||
size: [size.x as f32, size.y as f32],
|
||||
beat,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.queue
|
||||
.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Beat widget encoder"),
|
||||
});
|
||||
// Record output render pass.
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Output window render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &self.texture.view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.,
|
||||
g: 0.,
|
||||
b: 0.,
|
||||
a: 0.,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..4, 0..1);
|
||||
}
|
||||
|
||||
// Submit the commands.
|
||||
self.queue.submit(iter::once(encoder.finish()));
|
||||
|
||||
self.texture.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
struct Uniforms {
|
||||
resolution: vec2<f32>,
|
||||
size: vec2<f32>,
|
||||
beat: f32,
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> global: Uniforms;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) gl_Position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
var pos_array = array<vec2<f32>, 4>(
|
||||
vec2<f32>(1., 1.),
|
||||
vec2<f32>(-1., 1.),
|
||||
vec2<f32>(1., -1.),
|
||||
vec2<f32>(-1., -1.),
|
||||
);
|
||||
var uv_array = array<vec2<f32>, 4>(
|
||||
vec2<f32>(1., 0.),
|
||||
vec2<f32>(0., 0.),
|
||||
vec2<f32>(1., 1.),
|
||||
vec2<f32>(0., 1.),
|
||||
);
|
||||
|
||||
return VertexOutput(
|
||||
vec4<f32>(pos_array[vertex_index], 0., 1.),
|
||||
uv_array[vertex_index],
|
||||
);
|
||||
}
|
||||
|
||||
// Alpha-compsite two colors, putting one on top of the other
|
||||
fn composite(under: vec4<f32>, over: vec4<f32>) -> vec4<f32> {
|
||||
let a_out = 1. - (1. - over.a) * (1. - under.a);
|
||||
return clamp(vec4<f32>((over.rgb + under.rgb * (1. - over.a)), a_out), vec4<f32>(0.), vec4<f32>(1.));
|
||||
}
|
||||
|
||||
// Box from [0, 0] to (1, 1)
|
||||
fn box(p: vec2<f32>) -> f32 {
|
||||
let b = step(vec2<f32>(0.), p) - step(vec2<f32>(1.), p);
|
||||
return b.x * b.y;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(vertex: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let ballOutlineColor = vec4<f32>(0.267, 0., 0.444, 1.);
|
||||
let ballColorBottom = vec4<f32>(0.4, 0., 0.667, 1.);
|
||||
let ballColorTop = vec4<f32>(0.667, 0., 1., 1.);
|
||||
let floorColor = vec4<f32>(0.667, 0.667, 0.667, 1.);
|
||||
|
||||
let height = 1. - pow(abs(2. * (fract(global.beat) - 0.5)), 2.);
|
||||
let height = height * (1. - 0.4 * (1. - step(3., global.beat % 4.)));
|
||||
let ballLoc = vec2<f32>(0.5, 0.8 - 0.6 * height);
|
||||
let ballColor = mix(ballColorBottom, ballColorTop, clamp(10. * (ballLoc.y - vertex.uv.y), 0., 1.));
|
||||
let ball = 1. - smoothstep(0.08, 0.09, length(vertex.uv - ballLoc));
|
||||
let ballOutline = 1. - smoothstep(0.09, 0.1, length(vertex.uv - ballLoc));
|
||||
let floorBox = box((vertex.uv - vec2(0.2, 0.9)) / vec2(0.6, 0.05));
|
||||
|
||||
let fragColor = floorBox * floorColor;
|
||||
let fragColor = composite(fragColor, ballOutline * ballOutlineColor);
|
||||
let fragColor = composite(fragColor, ball * ballColor);
|
||||
return fragColor;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod beat_widget;
|
||||
mod drop_target;
|
||||
mod effect_node_tile;
|
||||
mod image_node_tile;
|
||||
@@ -11,6 +12,7 @@ mod spectrum_widget;
|
||||
mod tile;
|
||||
mod waveform_widget;
|
||||
|
||||
pub use beat_widget::*;
|
||||
pub use drop_target::*;
|
||||
pub use effect_node_tile::*;
|
||||
pub use image_node_tile::*;
|
||||
|
||||
@@ -26,7 +26,6 @@ pub struct SpectrumWidget {
|
||||
struct Uniforms {
|
||||
resolution: [f32; 2], // in pixels
|
||||
size: [f32; 2], // in points
|
||||
_padding: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
||||
@@ -32,7 +32,6 @@ pub struct WaveformWidget {
|
||||
struct Uniforms {
|
||||
resolution: [f32; 2], // in pixels
|
||||
size: [f32; 2], // in points
|
||||
_padding: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
||||
+18
-7
@@ -6,7 +6,7 @@ use std::time;
|
||||
|
||||
const MAX_TIME: f32 = 64.;
|
||||
// Anticipate beats by this many seconds
|
||||
const LATENCY_COMPENSATION: f32 = 0.07;
|
||||
const LATENCY_COMPENSATION: f32 = 0.10;
|
||||
|
||||
const DEFAULT_BPM: f32 = 120.;
|
||||
|
||||
@@ -68,9 +68,12 @@ impl Update {
|
||||
/// containing real-time information about the audio.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MusicInfo {
|
||||
pub time: f32, // time in beats
|
||||
pub uncompensated_time: f32, // time in beats, without latency compensation (so it aligns with reported audio levels)
|
||||
pub tempo: f32, // beats per second
|
||||
pub time: f32, // time in beats
|
||||
pub unscaled_time: f32, // time in beats, without the global timescale applied
|
||||
// (for widgets that shouldn't be affected by global
|
||||
// timescale)
|
||||
pub uncompensated_unscaled_time: f32, // time in beats, without the global timescale or latency compensation (so it aligns with reported audio levels)
|
||||
pub tempo: f32, // beats per second
|
||||
pub audio: AudioLevels,
|
||||
pub spectrum: [f32; SPECTRUM_LENGTH],
|
||||
}
|
||||
@@ -272,8 +275,15 @@ impl Mir {
|
||||
}
|
||||
|
||||
// Compute t
|
||||
let uncompensated_time =
|
||||
(self.last_update.t(time::Instant::now()) * self.global_timescale).rem_euclid(MAX_TIME);
|
||||
let uncompensated_unscaled_time = self
|
||||
.last_update
|
||||
.t(time::Instant::now())
|
||||
.rem_euclid(MAX_TIME);
|
||||
|
||||
let unscaled_time = self
|
||||
.last_update
|
||||
.t(time::Instant::now() + time::Duration::from_secs_f32(LATENCY_COMPENSATION))
|
||||
.rem_euclid(MAX_TIME);
|
||||
|
||||
let time = (self
|
||||
.last_update
|
||||
@@ -283,7 +293,8 @@ impl Mir {
|
||||
|
||||
MusicInfo {
|
||||
time,
|
||||
uncompensated_time,
|
||||
unscaled_time,
|
||||
uncompensated_unscaled_time,
|
||||
tempo: self.last_update.tempo * self.global_timescale,
|
||||
audio: self.last_update.audio.clone(),
|
||||
spectrum: self.last_update.spectrum.clone(),
|
||||
|
||||
Reference in New Issue
Block a user