Skip to content

Commit dd3fce3

Browse files
committed
Release v0.9.6
1 parent 2998c29 commit dd3fce3

23 files changed

Lines changed: 1501 additions & 236 deletions

.github/workflows/macos.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,24 @@ jobs:
6969
<string>APPL</string>
7070
<key>LSMinimumSystemVersion</key>
7171
<string>10.15</string>
72+
<key>NSHighResolutionCapable</key>
73+
<true/>
74+
<key>LSApplicationCategoryType</key>
75+
<string>public.app-category.music</string>
76+
<key>NSMicrophoneUsageDescription</key>
77+
<string>RustTracker needs microphone access to visualize live audio input.</string>
7278
</dict>
7379
</plist>
7480
EOF
7581
76-
# Copy binary and bundle libraries
82+
# Copy binary, soundfont asset, and bundle libraries
7783
cp target/release/rusttracker RustTracker.app/Contents/MacOS/
84+
cp assets/soundfont.sf2 RustTracker.app/Contents/Resources/
7885
dylibbundler -od -b -x RustTracker.app/Contents/MacOS/rusttracker -d RustTracker.app/Contents/Frameworks/ -p @executable_path/../Frameworks/
7986
87+
# Ad-hoc codesign the entire bundle recursively to satisfy security requirements (especially Apple Silicon)
88+
codesign --force --deep --sign - RustTracker.app
89+
8090
- name: Create DMG
8191
run: |
8292
create-dmg \

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ scratch/
4848
scratch_bitstream/target/
4949
scratch_bitstream_windows/target/
5050
check_shader
51+
/test.rs
52+
/test_*

Cargo.lock

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

Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[package]
22
name = "rusttracker"
3-
version = "0.9.5"
3+
version = "0.9.6"
44
edition = "2024"
55
default-run = "rusttracker"
66
description = "High-Performance Vulkan Tracker Module Visualizer"
7-
license = "MIT"
7+
license = "GPL-3.0-or-later"
88

99
[package.metadata.generate-rpm]
1010
assets = [
@@ -59,3 +59,7 @@ winres = "0.1.12"
5959

6060
[target."cfg(windows)".dependencies]
6161
windows = { version = "0.62.2", features = ["Win32_System_Com", "Win32_Media_Audio", "Win32_Foundation", "Win32_System_Threading", "Win32_System_Pipes"] }
62+
63+
[target."cfg(target_os = \"macos\")".dependencies]
64+
objc2 = "0.6.4"
65+
objc2-core-audio = "0.3.2"

LICENSE

Lines changed: 237 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,7 @@ cargo deb
9090
* `spectrum-analyzer` - Fast Fourier Transforms that make the pretty things react to the loud things
9191
* `crossbeam-channel` - Lock-free concurrency because threads should get along
9292
* `openmpt` - Tracker module decoding for that sweet, sweet nostalgia
93+
94+
## License
95+
96+
This project is licensed under the [GNU General Public License v3.0](LICENSE) (GPLv3).

src/audio.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,16 +1384,39 @@ pub fn load_audio_source(file_path: &str) -> Result<Box<dyn AudioSource>> {
13841384

13851385
// 3. Try MIDI
13861386
if ext == "mid" || ext == "midi" {
1387-
// Look for soundfont in project dir, then fallback
1388-
let sf_path = if std::path::Path::new("assets/soundfont.sf2").exists() {
1389-
"assets/soundfont.sf2"
1390-
} else {
1391-
"soundfont.sf2" // User must provide it in the working dir if not in assets
1392-
};
1393-
if let Ok(source) = MidiSource::new(file_path, sf_path, 48000) {
1387+
// Look for soundfont in project dir, then fallback to executable-relative paths and macOS Resources bundle
1388+
let mut sf_path = "assets/soundfont.sf2".to_string();
1389+
if !std::path::Path::new(&sf_path).exists() {
1390+
if std::path::Path::new("soundfont.sf2").exists() {
1391+
sf_path = "soundfont.sf2".to_string();
1392+
} else if let Ok(exe_path) = std::env::current_exe() {
1393+
if let Some(exe_dir) = exe_path.parent() {
1394+
let test_path = exe_dir.join("assets/soundfont.sf2");
1395+
if test_path.exists() {
1396+
sf_path = test_path.to_string_lossy().into_owned();
1397+
} else {
1398+
let test_path = exe_dir.join("soundfont.sf2");
1399+
if test_path.exists() {
1400+
sf_path = test_path.to_string_lossy().into_owned();
1401+
} else {
1402+
let test_path = exe_dir.join("../Resources/soundfont.sf2");
1403+
if test_path.exists() {
1404+
sf_path = test_path.to_string_lossy().into_owned();
1405+
} else {
1406+
let test_path = exe_dir.join("../Resources/assets/soundfont.sf2");
1407+
if test_path.exists() {
1408+
sf_path = test_path.to_string_lossy().into_owned();
1409+
}
1410+
}
1411+
}
1412+
}
1413+
}
1414+
}
1415+
}
1416+
if let Ok(source) = MidiSource::new(file_path, &sf_path, 48000) {
13941417
return Ok(Box::new(source));
13951418
} else {
1396-
return Err(anyhow::anyhow!("Failed to parse MIDI or missing SoundFont ({}). Please place a SoundFont in assets/soundfont.sf2", sf_path));
1419+
return Err(anyhow::anyhow!("Failed to parse MIDI or missing SoundFont ({}). Please place a SoundFont in assets/soundfont.sf2 or bundled resources", sf_path));
13971420
}
13981421
}
13991422

src/bin/validate_wgsl.rs

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,69 @@
1+
use std::path::Path;
2+
13
fn main() {
2-
let source = std::fs::read_to_string("src/shaders/vis_3drain.wgsl").unwrap();
3-
// we need to include common
4-
let common = "struct VertexOutput { @builtin(position) clip_position: vec4<f32>, @location(0) uv: vec2<f32>, }; @vertex fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput { var out: VertexOutput; return out; } struct AudioUniforms { spectrum: array<vec4<f32>, 256>, fire_heat: array<vec4<f32>, 256>, channels: array<vec4<f32>, 8>, channel_peaks: array<vec4<f32>, 8>, spatial_channels: array<vec4<f32>, 4>, display_order: array<vec4<u32>, 4>, num_channels: u32, mode: u32, time: f32, duration: f32, smooth_time: f32, heatmap_row: u32, fft_channels: u32, num_spatial_channels: u32, ui_meters_rect: vec4<f32>, ui_heatmap_rect: vec4<f32>, ui_fire_rect: vec4<f32>, waveform_resolution: u32, waveform_history_size: u32, _pad0: u32, _pad1: u32, };";
5-
let full_source = source.replace("// INCLUDE: common", common);
6-
let mut frontend = naga::front::wgsl::Frontend::new();
7-
match frontend.parse(&full_source) {
8-
Ok(module) => {
9-
let mut validator = naga::valid::Validator::new(
10-
naga::valid::ValidationFlags::all(),
11-
naga::valid::Capabilities::all(),
12-
);
13-
match validator.validate(&module) {
14-
Ok(_) => println!("WGSL Validated Successfully"),
15-
Err(e) => {
16-
eprintln!("Validation error: {:?}", e);
17-
std::process::exit(1);
4+
let common = std::fs::read_to_string("src/shaders/_common.wgsl")
5+
.expect("Failed to read _common.wgsl");
6+
let glyph_font = std::fs::read_to_string("src/shaders/_glyph_font.wgsl")
7+
.expect("Failed to read _glyph_font.wgsl");
8+
9+
let shader_dir = Path::new("src/shaders");
10+
let entries = std::fs::read_dir(shader_dir).expect("Failed to read src/shaders directory");
11+
12+
let mut failed = false;
13+
let mut validated_count = 0;
14+
15+
for entry in entries {
16+
let entry = entry.expect("Failed to read entry");
17+
let path = entry.path();
18+
if path.is_file() {
19+
if let Some(ext) = path.extension() {
20+
if ext == "wgsl" {
21+
let filename = path.file_name().unwrap().to_string_lossy().into_owned();
22+
// Skip the helper header files themselves as they are incomplete standalone WGSL
23+
if filename.starts_with('_') {
24+
continue;
25+
}
26+
27+
let source = std::fs::read_to_string(&path)
28+
.unwrap_or_else(|_| panic!("Failed to read shader {:?}", path));
29+
30+
let full_source = source
31+
.replace("// INCLUDE: common", &common)
32+
.replace("// INCLUDE: glyph_font", &glyph_font);
33+
34+
let mut frontend = naga::front::wgsl::Frontend::new();
35+
match frontend.parse(&full_source) {
36+
Ok(module) => {
37+
let mut validator = naga::valid::Validator::new(
38+
naga::valid::ValidationFlags::all(),
39+
naga::valid::Capabilities::all(),
40+
);
41+
match validator.validate(&module) {
42+
Ok(_) => {
43+
println!("Successfully validated: {}", filename);
44+
validated_count += 1;
45+
}
46+
Err(e) => {
47+
eprintln!("Validation error in {}: {:?}", filename, e);
48+
failed = true;
49+
}
50+
}
51+
}
52+
Err(e) => {
53+
let err_str = e.emit_to_string_with_path(&full_source, &filename);
54+
eprintln!("Parse error in {}:\n{}", filename, err_str);
55+
failed = true;
56+
}
57+
}
1858
}
1959
}
20-
},
21-
Err(e) => {
22-
let err_str = e.emit_to_string_with_path(&full_source, "vis_3drain.wgsl");
23-
eprintln!("{}", err_str);
24-
std::process::exit(1);
2560
}
2661
}
62+
63+
if failed {
64+
eprintln!("\nSome shaders failed validation.");
65+
std::process::exit(1);
66+
} else {
67+
println!("\nAll {} shaders validated successfully!", validated_count);
68+
}
2769
}

0 commit comments

Comments
 (0)