wip
This commit is contained in:
parent
55a94f0a75
commit
8389e88a80
7 changed files with 250 additions and 58 deletions
69
Cargo.lock
generated
69
Cargo.lock
generated
|
|
@ -275,6 +275,17 @@ dependencies = [
|
||||||
"portable-atomic-util",
|
"portable-atomic-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audioshader"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
"cpal 0.16.0",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"rodio 0.21.1",
|
||||||
|
"rustfft",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -1134,16 +1145,6 @@ dependencies = [
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bevy_test"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bevy",
|
|
||||||
"cpal 0.16.0",
|
|
||||||
"crossbeam-channel",
|
|
||||||
"rodio 0.21.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_text"
|
name = "bevy_text"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
|
|
@ -3031,6 +3032,15 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
@ -3610,6 +3620,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-check"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
|
|
@ -3834,6 +3853,20 @@ version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustfft"
|
||||||
|
version = "6.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6f140db74548f7c9d7cce60912c9ac414e74df5e718dc947d514b051b42f3f4"
|
||||||
|
dependencies = [
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"primal-check",
|
||||||
|
"strength_reduce",
|
||||||
|
"transpose",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.44"
|
version = "0.38.44"
|
||||||
|
|
@ -4054,6 +4087,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strength_reduce"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.26.3"
|
version = "0.26.3"
|
||||||
|
|
@ -4462,6 +4501,16 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "transpose"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"strength_reduce",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
name = "bevy_test"
|
name = "audioshader"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
cpal = "0.16"
|
cpal = "0.16"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
rodio = "0.21"
|
rodio = "0.21"
|
||||||
|
rustfft = "6"
|
||||||
|
|
||||||
# Enable a small amount of optimization in the dev profile.
|
# Enable a small amount of optimization in the dev profile.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
||||||
|
|
@ -3,46 +3,105 @@
|
||||||
@group(2) @binding(0) var<uniform> u_time: f32;
|
@group(2) @binding(0) var<uniform> u_time: f32;
|
||||||
@group(2) @binding(1) var<uniform> u_resolution: vec2f;
|
@group(2) @binding(1) var<uniform> u_resolution: vec2f;
|
||||||
@group(2) @binding(2) var<uniform> u_mouse_position: vec2f;
|
@group(2) @binding(2) var<uniform> u_mouse_position: vec2f;
|
||||||
|
@group(2) @binding(3) var fft_texture: texture_2d<f32>;
|
||||||
|
@group(2) @binding(4) var fft_texture_sampler: sampler;
|
||||||
|
|
||||||
|
|
||||||
fn lerp(v0: f32, v1: f32, t: f32) -> f32 {
|
fn lerp(v0: f32, v1: f32, t: f32) -> f32 {
|
||||||
return v0 + t * (v1 - v0);
|
return v0 + t * (v1 - v0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility: clamp to 0..1 range
|
||||||
|
fn saturate(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }
|
||||||
|
|
||||||
|
// Cosine palette (like IQ's)
|
||||||
|
fn palette(t: f32, a: vec3f, b: vec3f, c: vec3f, d: vec3f) -> vec3f {
|
||||||
|
return a + b * cos(6.2831853 * (c * t + d));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample the FFT texture at normalized x in [0,1]
|
||||||
|
fn sample_fft(x: f32) -> f32 {
|
||||||
|
// 5-tap Gaussian blur in texture space using actual texel width
|
||||||
|
let size = textureDimensions(fft_texture);
|
||||||
|
let tex_w = max(f32(size.x), 1.0);
|
||||||
|
let one_texel = 1.0 / tex_w;
|
||||||
|
let u = saturate(x);
|
||||||
|
let v = 0.5;
|
||||||
|
let w0 = 0.0625; // gaussian-ish weights: 1,4,6,4,1 normalized
|
||||||
|
let w1 = 0.25;
|
||||||
|
let w2 = 0.375;
|
||||||
|
let s0 = textureSample(fft_texture, fft_texture_sampler, vec2f(u - 2.0 * one_texel, v)).r;
|
||||||
|
let s1 = textureSample(fft_texture, fft_texture_sampler, vec2f(u - 1.0 * one_texel, v)).r;
|
||||||
|
let s2 = textureSample(fft_texture, fft_texture_sampler, vec2f(u, v)).r;
|
||||||
|
let s3 = textureSample(fft_texture, fft_texture_sampler, vec2f(u + 1.0 * one_texel, v)).r;
|
||||||
|
let s4 = textureSample(fft_texture, fft_texture_sampler, vec2f(u + 2.0 * one_texel, v)).r;
|
||||||
|
return w0 * s0 + w1 * s1 + w2 * s2 + w1 * s3 + w0 * s4;
|
||||||
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment(mesh: VertexOutput) -> @location(0) vec4f {
|
fn fragment(mesh: VertexOutput) -> @location(0) vec4f {
|
||||||
var uv = vec2f(lerp(-2, 0.47, mesh.uv.x), lerp(-1.12, 1.12, mesh.uv.y));
|
// Normalize UV to -1..1 with aspect correction
|
||||||
|
let aspect = u_resolution.x / max(u_resolution.y, 1.0);
|
||||||
|
let p = (mesh.uv * 2.0 - vec2f(1.0, 1.0)) * vec2f(aspect, 1.0);
|
||||||
|
|
||||||
// Progressive time-based zoom toward the known interesting point
|
// Polar coords
|
||||||
// c = -0.743643887037151 + 0.13182590420533i
|
let r = length(p);
|
||||||
let center = vec2f(-0.743643887037151, 0.13182590420533);
|
let ang = atan2(p.y, p.x); // [-pi, pi]
|
||||||
let zoomSpeed: f32 = 0.2; // tune this to control how fast we zoom
|
var a01 = ang / 6.2831853 + 0.5; // [0,1]
|
||||||
let zoom = exp(u_time * zoomSpeed); // zoom factor grows with time
|
|
||||||
let c = center + (uv - center) / zoom; // move coordinates toward center as zoom increases
|
|
||||||
|
|
||||||
var zx = 0.0;
|
// Scroll the angle slowly for motion
|
||||||
var zy = 0.0;
|
a01 = fract(a01 + 0.05 * u_time);
|
||||||
let maxIter: i32 = 100 + i32(u_time);
|
|
||||||
var it: i32 = 0;
|
|
||||||
|
|
||||||
for (var i: i32 = 0; i < maxIter; i = i + 1) {
|
// Log-like frequency remap for better distribution of low freqs
|
||||||
let x = zx * zx - zy * zy + c.x;
|
let freq = pow(a01, 0.8);
|
||||||
let y = 2.0 * zx * zy + c.y;
|
|
||||||
zx = x;
|
|
||||||
zy = y;
|
|
||||||
if (zx * zx + zy * zy > 4.0) {
|
|
||||||
it = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let t = f32(it) / f32(maxIter);
|
// Smooth FFT sample with a few taps
|
||||||
|
let amp0 = sample_fft(freq);
|
||||||
|
let amp1 = sample_fft(saturate(freq + 1.0 / 512.0));
|
||||||
|
let amp2 = sample_fft(saturate(freq - 1.0 / 512.0));
|
||||||
|
var amp = (amp0 + amp1 + amp2) / 3.0;
|
||||||
|
// Optional shaping
|
||||||
|
amp = pow(amp, 0.6);
|
||||||
|
|
||||||
let palette = vec3f(0.5) + 0.5 * cos(6.2831853 * (vec3f(0.0, 0.33, 0.66) + t * 1.5) + u_time * 0.2);
|
// Target ring radius driven by amplitude
|
||||||
|
let base_r = 0.35;
|
||||||
|
let ring_r = base_r + amp * 0.45;
|
||||||
|
|
||||||
var color = vec3f(0, 0, 0);
|
// Ring intensity with derivative-based anti-aliasing
|
||||||
if (t < 0.999999) {
|
let band_w = mix(0.02, 0.06, 1.0 - amp);
|
||||||
color = palette;
|
let aa = fwidth(r);
|
||||||
}
|
let dist = abs(r - ring_r);
|
||||||
|
let ring = 1.0 - smoothstep(band_w - aa, band_w + aa, dist);
|
||||||
return vec4f(color, 1);
|
|
||||||
|
// Radial spokes modulated by FFT at harmonics
|
||||||
|
let spokes = 0.5 + 0.5 * cos(ang * (8.0 + 24.0 * amp) + u_time * (1.0 + 2.0 * amp));
|
||||||
|
let spokes_mask = pow(spokes, 3.0);
|
||||||
|
|
||||||
|
// Glow falloff
|
||||||
|
let glow = 0.015 / (r * r + 0.03);
|
||||||
|
|
||||||
|
// Background subtle gradient with slow drift
|
||||||
|
let bg = 0.08 + 0.06 * cos(6.28318 * (p.x * 0.15 + p.y * 0.12 + 0.03 * u_time));
|
||||||
|
|
||||||
|
// Color palette over angle
|
||||||
|
let col = palette(
|
||||||
|
a01,
|
||||||
|
vec3f(0.35, 0.3, 0.35), // a
|
||||||
|
vec3f(0.35, 0.35, 0.35), // b
|
||||||
|
vec3f(1.0, 1.0, 1.0), // c
|
||||||
|
vec3f(0.0, 0.33, 0.67) // d
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine layers
|
||||||
|
var color = vec3f(bg) * 0.6;
|
||||||
|
color += col * ring * (0.6 + 0.6 * spokes_mask);
|
||||||
|
color += vec3f(0.9, 0.95, 1.0) * glow * (0.3 + 1.5 * amp);
|
||||||
|
|
||||||
|
// Subtle vignette
|
||||||
|
let vig = smoothstep(1.2, 0.3, r);
|
||||||
|
color *= vig;
|
||||||
|
|
||||||
|
// Final boost
|
||||||
|
color = clamp(color, vec3f(0.0), vec3f(1.0));
|
||||||
|
return vec4f(color, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,28 @@ use {
|
||||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||||
},
|
},
|
||||||
crossbeam_channel::{Receiver, Sender},
|
crossbeam_channel::{Receiver, Sender},
|
||||||
|
rustfft::{Fft, FftPlanner, num_complex::Complex},
|
||||||
|
std::sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Resource holding the latest FFT result (magnitude spectrum)
|
||||||
|
#[derive(Resource, Clone, Debug)]
|
||||||
|
pub struct AudioFft {
|
||||||
|
pub spectrum: Vec<f32>,
|
||||||
|
pub sample_rate: u32,
|
||||||
|
pub channels: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AudioFft {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
spectrum: vec![0.0; 1024],
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Event carrying interleaved mic samples as f32 in the device's native
|
/// Event carrying interleaved mic samples as f32 in the device's native
|
||||||
/// sample rate and channel layout.
|
/// sample rate and channel layout.
|
||||||
#[derive(Event, Debug, Clone)]
|
#[derive(Event, Debug, Clone)]
|
||||||
|
|
@ -16,19 +36,6 @@ pub struct AudioCaptureData {
|
||||||
pub channels: u16,
|
pub channels: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_audio_capture_events(mut reader: EventReader<AudioCaptureData>) {
|
|
||||||
for event in reader.read() {
|
|
||||||
// Process mic data here.
|
|
||||||
// For example, print the number of samples received.
|
|
||||||
println!(
|
|
||||||
"Received mic data: {} samples at {} Hz, {} channels",
|
|
||||||
event.samples.len(),
|
|
||||||
event.sample_rate,
|
|
||||||
event.channels
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holds the CPAL stream alive and a channel for transferring audio to Bevy world.
|
/// Holds the CPAL stream alive and a channel for transferring audio to Bevy world.
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct AudioCaptureStream {
|
struct AudioCaptureStream {
|
||||||
|
|
@ -44,6 +51,7 @@ pub struct AudioCapturePlugin;
|
||||||
impl Plugin for AudioCapturePlugin {
|
impl Plugin for AudioCapturePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_event::<AudioCaptureData>()
|
app.add_event::<AudioCaptureData>()
|
||||||
|
.init_resource::<AudioFft>()
|
||||||
.add_systems(Startup, init_audio_input_stream)
|
.add_systems(Startup, init_audio_input_stream)
|
||||||
.add_systems(Update, dispatch_audio_events);
|
.add_systems(Update, dispatch_audio_events);
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +85,7 @@ fn init_audio_input_stream(mut commands: Commands) {
|
||||||
stream.play().expect("Failed to play input stream");
|
stream.play().expect("Failed to play input stream");
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Mic stream started: {} ch @ {} Hz (default OS input)",
|
"Audio capture stream started: {} ch @ {} Hz (default OS input)",
|
||||||
channels, sample_rate
|
channels, sample_rate
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -124,13 +132,54 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_audio_events(mic: Res<AudioCaptureStream>, mut writer: EventWriter<AudioCaptureData>) {
|
fn dispatch_audio_events(
|
||||||
|
mic: Res<AudioCaptureStream>,
|
||||||
|
mut writer: EventWriter<AudioCaptureData>,
|
||||||
|
mut fft_res: ResMut<AudioFft>,
|
||||||
|
) {
|
||||||
|
// FFT parameters
|
||||||
|
const FFT_SIZE: usize = 1024;
|
||||||
// Drain any available audio buffers without blocking the frame.
|
// Drain any available audio buffers without blocking the frame.
|
||||||
while let Ok(samples) = mic.rx.try_recv() {
|
while let Ok(samples) = mic.rx.try_recv() {
|
||||||
writer.write(AudioCaptureData {
|
writer.write(AudioCaptureData {
|
||||||
samples,
|
samples: samples.clone(),
|
||||||
sample_rate: mic.sample_rate,
|
sample_rate: mic.sample_rate,
|
||||||
channels: mic.channels,
|
channels: mic.channels,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Compute FFT on the first channel (mono mix)
|
||||||
|
let mut mono: Vec<f32> = if mic.channels > 1 {
|
||||||
|
samples
|
||||||
|
.chunks(mic.channels as usize)
|
||||||
|
.map(|frame| frame[0])
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
samples.clone()
|
||||||
|
};
|
||||||
|
// Pad or truncate to FFT_SIZE
|
||||||
|
if mono.len() < FFT_SIZE {
|
||||||
|
mono.resize(FFT_SIZE, 0.0);
|
||||||
|
} else if mono.len() > FFT_SIZE {
|
||||||
|
mono.truncate(FFT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare input for FFT
|
||||||
|
let mut buffer: Vec<Complex<f32>> = mono
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| Complex { re: x, im: 0.0 })
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Compute FFT
|
||||||
|
let mut planner = FftPlanner::<f32>::new();
|
||||||
|
let fft: Arc<dyn Fft<f32>> = planner.plan_fft_forward(FFT_SIZE);
|
||||||
|
fft.process(&mut buffer);
|
||||||
|
|
||||||
|
// Compute magnitude spectrum
|
||||||
|
let spectrum: Vec<f32> = buffer.iter().map(|c| c.norm()).collect();
|
||||||
|
|
||||||
|
// Update AudioFft resource
|
||||||
|
fft_res.spectrum = spectrum;
|
||||||
|
fft_res.sample_rate = mic.sample_rate;
|
||||||
|
fft_res.channels = mic.channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ fn main() {
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
material::update_material_time,
|
material::update_material_time,
|
||||||
audio_input::handle_audio_capture_events,
|
material::update_material_audio_fft,
|
||||||
systems::screen_resized,
|
systems::screen_resized,
|
||||||
systems::mouse_moved,
|
systems::mouse_moved,
|
||||||
systems::exit_app.run_if(input_just_pressed(KeyCode::Escape)),
|
systems::exit_app.run_if(input_just_pressed(KeyCode::Escape)),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
asset::Assets,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
reflect::TypePath,
|
reflect::TypePath,
|
||||||
render::render_resource::{AsBindGroup, ShaderRef},
|
render::render_resource::{AsBindGroup, Extent3d, ShaderRef},
|
||||||
sprite::{AlphaMode2d, Material2d},
|
sprite::{AlphaMode2d, Material2d},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -15,6 +16,9 @@ pub struct CustomMaterial {
|
||||||
pub resolution: Vec2,
|
pub resolution: Vec2,
|
||||||
#[uniform(2)]
|
#[uniform(2)]
|
||||||
pub mouse_position: Vec2,
|
pub mouse_position: Vec2,
|
||||||
|
#[texture(3)]
|
||||||
|
#[sampler(4)]
|
||||||
|
pub audio_fft_tex: Handle<Image>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_material_time(mut materials: ResMut<Assets<CustomMaterial>>, time: Res<Time>) {
|
pub fn update_material_time(mut materials: ResMut<Assets<CustomMaterial>>, time: Res<Time>) {
|
||||||
|
|
@ -24,6 +28,35 @@ pub fn update_material_time(mut materials: ResMut<Assets<CustomMaterial>>, time:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_material_audio_fft(
|
||||||
|
mut materials: ResMut<Assets<CustomMaterial>>,
|
||||||
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
audio_fft: Res<crate::audio_input::AudioFft>,
|
||||||
|
) {
|
||||||
|
let spectrum = &audio_fft.spectrum;
|
||||||
|
if spectrum.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let image = Image::new(
|
||||||
|
Extent3d {
|
||||||
|
width: spectrum.len() as u32,
|
||||||
|
height: 1,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
bevy::render::render_resource::TextureDimension::D2,
|
||||||
|
spectrum
|
||||||
|
.iter()
|
||||||
|
.map(|&v| (v * 255.0).clamp(0.0, 255.0) as u8)
|
||||||
|
.collect(),
|
||||||
|
bevy::render::render_resource::TextureFormat::R8Unorm,
|
||||||
|
bevy::asset::RenderAssetUsages::RENDER_WORLD,
|
||||||
|
);
|
||||||
|
let handle = images.add(image);
|
||||||
|
for (_handle, material) in materials.iter_mut() {
|
||||||
|
material.audio_fft_tex = handle.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Material2d for CustomMaterial {
|
impl Material2d for CustomMaterial {
|
||||||
fn fragment_shader() -> ShaderRef {
|
fn fragment_shader() -> ShaderRef {
|
||||||
DEFAULT_SHADER_PATH.into()
|
DEFAULT_SHADER_PATH.into()
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub fn setup(
|
||||||
time: 0.0,
|
time: 0.0,
|
||||||
resolution: screen_size,
|
resolution: screen_size,
|
||||||
mouse_position: Vec2::ZERO,
|
mouse_position: Vec2::ZERO,
|
||||||
|
audio_fft_tex: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue