Skip to content

Commit 8614b3a

Browse files
committed
v0.9.13: Synthwave visualizers overhaul & visualizer ordering update
1 parent 3233b70 commit 8614b3a

9 files changed

Lines changed: 389 additions & 182 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusttracker"
3-
version = "0.9.12"
3+
version = "0.9.13"
44
edition = "2024"
55
default-run = "rusttracker"
66
description = "High-Performance Vulkan Tracker Module Visualizer"

src/audio.rs

Lines changed: 72 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ pub fn get_default_audio_device_name(mic: bool) -> Option<String> {
116116
default_device.and_then(|d| d.description().ok().map(|desc| desc.name().to_string()))
117117
}
118118

119+
pub fn calculate_power_of_two_window_size(sample_rate: u32) -> usize {
120+
let target = (sample_rate as f32 * 0.185).round() as usize;
121+
let window_size = 1 << (target as f32).log2().round() as usize;
122+
window_size.max(2048).min(65536)
123+
}
124+
119125
pub fn spawn_dsp_thread(
120126
rx: Receiver<DspMessage>,
121127
shared_state: Arc<Mutex<AppState>>,
@@ -161,6 +167,7 @@ pub fn spawn_dsp_thread(
161167
log_bin_mappings.push(BinMapping { i0, i1, frac });
162168
}
163169

170+
let n_sqrt = (window_size as f32).sqrt();
164171
let mut last_waveform_push = Instant::now();
165172

166173
while let Ok(msg) = rx.recv() {
@@ -176,7 +183,6 @@ pub fn spawn_dsp_thread(
176183
fft.process(&mut complex_buf);
177184

178185
// Compute magnitudes (only first half — up to Nyquist)
179-
let n_sqrt = (window_size as f32).sqrt();
180186
for i in 0..window_size / 2 {
181187
magnitudes[i] = complex_buf[i].norm() / n_sqrt;
182188
}
@@ -1569,43 +1575,50 @@ pub fn load_audio_source(file_path: &str) -> Result<Box<dyn AudioSource>> {
15691575

15701576
// 3. Try MIDI
15711577
if ext == "mid" || ext == "midi" {
1572-
// Look for soundfont in project dir, then fallback to executable-relative paths and macOS Resources bundle
1578+
// Look for soundfont in project dir, then fallback to executable-relative paths, system-wide paths, and macOS Resources bundle
15731579
let mut sf_path = "assets/soundfont.sf2".to_string();
15741580
if !std::path::Path::new(&sf_path).exists() {
15751581
if std::path::Path::new("soundfont.sf2").exists() {
15761582
sf_path = "soundfont.sf2".to_string();
1577-
} else if let Ok(exe_path) = std::env::current_exe() {
1578-
if let Some(exe_dir) = exe_path.parent() {
1579-
let test_path = exe_dir.join("assets/soundfont.sf2");
1580-
if test_path.exists() {
1581-
sf_path = test_path.to_string_lossy().into_owned();
1582-
} else {
1583-
let test_path = exe_dir.join("soundfont.sf2");
1584-
if test_path.exists() {
1585-
sf_path = test_path.to_string_lossy().into_owned();
1586-
} else {
1587-
let test_path = exe_dir.join("../share/rusttracker/assets/soundfont.sf2");
1588-
if test_path.exists() {
1589-
sf_path = test_path.to_string_lossy().into_owned();
1590-
} else {
1591-
let test_path = exe_dir.join("../share/rusttracker/soundfont.sf2");
1592-
if test_path.exists() {
1593-
sf_path = test_path.to_string_lossy().into_owned();
1594-
} else {
1595-
let test_path = exe_dir.join("../Resources/soundfont.sf2");
1596-
if test_path.exists() {
1597-
sf_path = test_path.to_string_lossy().into_owned();
1598-
} else {
1599-
let test_path = exe_dir.join("../Resources/assets/soundfont.sf2");
1600-
if test_path.exists() {
1601-
sf_path = test_path.to_string_lossy().into_owned();
1602-
}
1603-
}
1604-
}
1583+
} else {
1584+
let mut found = false;
1585+
if let Ok(exe_path) = std::env::current_exe() {
1586+
if let Some(exe_dir) = exe_path.parent() {
1587+
let test_paths = vec![
1588+
exe_dir.join("assets/soundfont.sf2"),
1589+
exe_dir.join("soundfont.sf2"),
1590+
exe_dir.join("../share/rusttracker/assets/soundfont.sf2"),
1591+
exe_dir.join("../share/rusttracker/soundfont.sf2"),
1592+
exe_dir.join("../Resources/soundfont.sf2"),
1593+
exe_dir.join("../Resources/assets/soundfont.sf2"),
1594+
];
1595+
for tp in test_paths {
1596+
if tp.exists() {
1597+
sf_path = tp.to_string_lossy().into_owned();
1598+
found = true;
1599+
break;
16051600
}
16061601
}
16071602
}
16081603
}
1604+
1605+
// Fallback to system-wide paths on Linux/BSD if not found yet
1606+
if !found {
1607+
let system_paths = vec![
1608+
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
1609+
"/usr/share/sounds/sf2/default.sf2",
1610+
"/usr/share/soundfonts/FluidR3_GM.sf2",
1611+
"/usr/share/soundfonts/default.sf2",
1612+
"/usr/share/soundfonts/FluidR3_GM.sf3",
1613+
"/usr/share/soundfonts/freepats-general-midi.sf2",
1614+
];
1615+
for sp in system_paths {
1616+
if std::path::Path::new(sp).exists() {
1617+
sf_path = sp.to_string();
1618+
break;
1619+
}
1620+
}
1621+
}
16091622
}
16101623
}
16111624
if let Ok(source) = MidiSource::new(file_path, &sf_path, 48000) {
@@ -1636,8 +1649,7 @@ pub fn start_audio_thread(file_path: &str, mic: bool, shared_state: Arc<Mutex<Ap
16361649
if let Ok((handle, decoder_rate, codec_name, has_video)) = crate::bitstream::start_bitstream_thread(file_path, shared_state.clone(), tx.clone(), stop_token.clone()) {
16371650
let max_frequency = shared_state.lock().unwrap().max_frequency;
16381651
let sample_rate = decoder_rate;
1639-
let window_size = (((sample_rate as f32 * 0.185).round() as usize) / 2) * 2;
1640-
let window_size = window_size.max(2048).min(65536);
1652+
let window_size = calculate_power_of_two_window_size(sample_rate);
16411653

16421654
let display_name = match codec_name.as_str() {
16431655
"truehd" => "TrueHD / Dolby Atmos",
@@ -1738,8 +1750,7 @@ pub fn start_audio_thread(file_path: &str, mic: bool, shared_state: Arc<Mutex<Ap
17381750
}
17391751

17401752
let max_frequency = { shared_state.lock().unwrap().max_frequency };
1741-
let window_size = (((config.sample_rate as f32 * 0.185).round() as usize) / 2) * 2;
1742-
let window_size = window_size.max(2048).min(65536);
1753+
let window_size = calculate_power_of_two_window_size(config.sample_rate);
17431754
spawn_dsp_thread(rx, shared_state.clone(), config.sample_rate, max_frequency, window_size);
17441755

17451756
let stream = match supported_config.sample_format() {
@@ -1894,8 +1905,7 @@ pub fn start_audio_thread(file_path: &str, mic: bool, shared_state: Arc<Mutex<Ap
18941905
}
18951906

18961907
let max_frequency = { shared_state_closure.lock().unwrap().max_frequency };
1897-
let window_size = (((config.sample_rate as f32 * 0.185).round() as usize) / 2) * 2;
1898-
let window_size = window_size.max(2048).min(65536);
1908+
let window_size = calculate_power_of_two_window_size(config.sample_rate);
18991909

19001910
spawn_dsp_thread(rx, shared_state_closure.clone(), config.sample_rate, max_frequency, window_size);
19011911

@@ -1934,8 +1944,7 @@ pub fn start_audio_thread(file_path: &str, mic: bool, shared_state: Arc<Mutex<Ap
19341944

19351945
if let Some(mut audio_source) = audio_source_opt.take() {
19361946
let sample_rate = target_rate;
1937-
let window_size = (((sample_rate as f32 * 0.185).round() as usize) / 2) * 2;
1938-
let window_size = window_size.max(2048).min(65536);
1947+
let window_size = calculate_power_of_two_window_size(sample_rate);
19391948
let (tx, rx) = bounded::<DspMessage>(32);
19401949

19411950
{
@@ -2166,20 +2175,26 @@ fn run_dummy(
21662175

21672176
frame.y_plane.clear();
21682177
let y_plane = decoded.data(0);
2169-
if y_len <= y_plane.len() {
2170-
frame.y_plane.extend_from_slice(&y_plane[..y_len]);
2178+
let y_copy_len = y_len.min(y_plane.len());
2179+
frame.y_plane.extend_from_slice(&y_plane[..y_copy_len]);
2180+
if y_copy_len < y_len {
2181+
frame.y_plane.resize(y_len, 0);
21712182
}
21722183

21732184
frame.u_plane.clear();
21742185
let u_plane = decoded.data(1);
2175-
if u_len <= u_plane.len() {
2176-
frame.u_plane.extend_from_slice(&u_plane[..u_len]);
2186+
let u_copy_len = u_len.min(u_plane.len());
2187+
frame.u_plane.extend_from_slice(&u_plane[..u_copy_len]);
2188+
if u_copy_len < u_len {
2189+
frame.u_plane.resize(u_len, 0);
21772190
}
21782191

21792192
frame.v_plane.clear();
21802193
let v_plane = decoded.data(2);
2181-
if v_len <= v_plane.len() {
2182-
frame.v_plane.extend_from_slice(&v_plane[..v_len]);
2194+
let v_copy_len = v_len.min(v_plane.len());
2195+
frame.v_plane.extend_from_slice(&v_plane[..v_copy_len]);
2196+
if v_copy_len < v_len {
2197+
frame.v_plane.resize(v_len, 0);
21832198
}
21842199

21852200
if let Err(crossbeam_channel::TrySendError::Full(f)) = video_frame_tx.try_send(frame) {
@@ -2813,20 +2828,26 @@ where
28132828

28142829
frame.y_plane.clear();
28152830
let y_plane = decoded.data(0);
2816-
if y_len <= y_plane.len() {
2817-
frame.y_plane.extend_from_slice(&y_plane[..y_len]);
2831+
let y_copy_len = y_len.min(y_plane.len());
2832+
frame.y_plane.extend_from_slice(&y_plane[..y_copy_len]);
2833+
if y_copy_len < y_len {
2834+
frame.y_plane.resize(y_len, 0);
28182835
}
28192836

28202837
frame.u_plane.clear();
28212838
let u_plane = decoded.data(1);
2822-
if u_len <= u_plane.len() {
2823-
frame.u_plane.extend_from_slice(&u_plane[..u_len]);
2839+
let u_copy_len = u_len.min(u_plane.len());
2840+
frame.u_plane.extend_from_slice(&u_plane[..u_copy_len]);
2841+
if u_copy_len < u_len {
2842+
frame.u_plane.resize(u_len, 0);
28242843
}
28252844

28262845
frame.v_plane.clear();
28272846
let v_plane = decoded.data(2);
2828-
if v_len <= v_plane.len() {
2829-
frame.v_plane.extend_from_slice(&v_plane[..v_len]);
2847+
let v_copy_len = v_len.min(v_plane.len());
2848+
frame.v_plane.extend_from_slice(&v_plane[..v_copy_len]);
2849+
if v_copy_len < v_len {
2850+
frame.v_plane.resize(v_len, 0);
28302851
}
28312852

28322853
if let Err(crossbeam_channel::TrySendError::Full(f)) = video_frame_tx.try_send(frame) {
@@ -3019,8 +3040,7 @@ where
30193040
T: cpal::Sample + cpal::FromSample<f32> + cpal::SizedSample + Into<f32>,
30203041
{
30213042
let channels = config.channels as usize;
3022-
let window_size = (((config.sample_rate as f32 * 0.185).round() as usize) / 2) * 2;
3023-
let window_size = window_size.max(2048).min(65536);
3043+
let window_size = calculate_power_of_two_window_size(config.sample_rate);
30243044

30253045
let mut fft_buffer: Vec<f32> = vec![0.0; window_size];
30263046
let mut channel_fft_buffers: Vec<Vec<f32>> = vec![vec![0.0; window_size]; channels];

src/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4428,8 +4428,8 @@ impl VulkanEngine {
44284428
self.queue.write_buffer(&vs.params_buffer, 0, bytemuck::cast_slice(&[params]));
44294429
render_pass.set_pipeline(&self.video_pipeline);
44304430
render_pass.set_bind_group(0, &vs.bind_group, &[]);
4431+
render_pass.draw(0..3, 0..1);
44314432
}
4432-
render_pass.draw(0..3, 0..1);
44334433
}
44344434

44354435
if state.show_hud && state.video_mode != 3 {

0 commit comments

Comments
 (0)