Skip to content

Commit a8c5bba

Browse files
author
Nathan
committed
v0.9.1 - Music Player Overhaul & Auto-Tier Fix
## Music Player - Full music player with TWAV disk playback (DMA 128-sector batch reads) - Clickable track list UI with polished glass-effect buttons - Effects controls: SYNC (av offset), VIZ (13 modes), PAL (23 palettes), RAIN (3 presets) - Fixed DMA buffer: heap-allocated Vec for correct HHDM virt_to_phys translation - Fixed u16 truncation bug in load_track_data (usize-first min computation) - Removed procedural lo-fi beat (disk-only playback) ## Desktop Auto-Tier System - Fixed visualizer disappearing after ~4 seconds (auto-downgrade was too aggressive) - Lowered FPS threshold from 30 to 18 (23fps with music+viz is acceptable) - Increased downgrade counter from 180 to 360 frames (~6 seconds tolerance) - Added auto-upgrade: FPS >= 35 for 300 frames restores tier back up - Reset FPS counters after disk I/O to prevent transient dips triggering downgrade ## Browser & Network - Replaced spin_loop() busy-waits with yield_thread() in TCP/HTTP/HTTPS - Browser TCP timeout fix - Music dock icon (IconType::Music, IconAction::OpenMusicPlayer) ## Disk I/O - TWAV data disk on SATA port 3, find_data_port() scans all ports for TWAV magic - VFS probes sector 0 for TWAV to avoid TrustFS/TWAV conflict
1 parent fc9d34f commit a8c5bba

9 files changed

Lines changed: 861 additions & 613 deletions

File tree

‎kernel/src/browser/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ impl Browser {
288288
}
289289

290290
/// Extract external resources from the document
291-
fn extract_resources(&mut self, base_url: &str) {
291+
pub fn extract_resources(&mut self, base_url: &str) {
292292
self.pending_resources.clear();
293293
self.resources.clear();
294294

‎kernel/src/desktop.rs‎

Lines changed: 755 additions & 558 deletions
Large diffs are not rendered by default.

‎kernel/src/icons.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,21 @@ pub fn draw_about_icon(x: u32, y: u32, color: u32, _bg: u32) {
158158
framebuffer::fill_rect(x + 12, y + 24, 8, 2, light); // base
159159
}
160160

161+
/// Draw a 32x32 music note icon
162+
pub fn draw_music_icon(x: u32, y: u32, color: u32, _bg: u32) {
163+
let light = lighten(color, 1.3);
164+
// Eighth note: oval head + stem + flag
165+
// Note head (filled oval at bottom-left)
166+
draw_filled_circle(x + 10, y + 24, 5, light);
167+
draw_filled_circle(x + 10, y + 24, 4, color);
168+
// Stem (vertical line going up-right)
169+
framebuffer::fill_rect(x + 14, y + 6, 2, 19, light);
170+
// Flag (curved line at top)
171+
framebuffer::fill_rect(x + 16, y + 6, 2, 4, light);
172+
framebuffer::fill_rect(x + 18, y + 8, 2, 4, light);
173+
framebuffer::fill_rect(x + 20, y + 10, 2, 4, light);
174+
}
175+
161176
/// Draw a 32x32 game controller icon (for games like Snake)
162177
pub fn draw_game_icon(x: u32, y: u32, color: u32, _bg: u32) {
163178
let dark = darken(color, 0.7);
@@ -283,6 +298,7 @@ pub enum IconType {
283298
Calculator,
284299
Network,
285300
About,
301+
Music,
286302
Game,
287303
Editor,
288304
OpenGL,
@@ -302,6 +318,7 @@ pub fn draw_icon(icon_type: IconType, x: u32, y: u32, color: u32, bg: u32) {
302318
IconType::Calculator => draw_calculator_icon(x, y, color, bg),
303319
IconType::Network => draw_network_icon(x, y, color, bg),
304320
IconType::About => draw_about_icon(x, y, color, bg),
321+
IconType::Music => draw_music_icon(x, y, color, bg),
305322
IconType::Game => draw_game_icon(x, y, color, bg),
306323
IconType::Editor => draw_editor_icon(x, y, color, bg),
307324
IconType::OpenGL => draw_opengl_icon(x, y, color, bg),

‎kernel/src/netstack/http.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ fn request_inner(method: &str, url: &str, content_type: Option<&str>, body: Opti
142142
break;
143143
}
144144

145-
core::hint::spin_loop();
145+
// Yield to other threads so desktop stays responsive during fetch
146+
crate::thread::yield_thread();
146147
}
147148

148149
// Send FIN to close connection

‎kernel/src/netstack/https.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn ensure_network_ready() {
2626
}
2727

2828
// Small delay
29-
for _ in 0..10000 { core::hint::spin_loop(); }
29+
crate::thread::yield_thread();
3030
}
3131

3232
if crate::netstack::dhcp::is_bound() {
@@ -135,8 +135,8 @@ fn get_inner(url: &str, depth: u32) -> Result<HttpsResponse, HttpsError> {
135135
return Ok(len);
136136
}
137137

138-
// Small delay
139-
for _ in 0..10000 { core::hint::spin_loop(); }
138+
// Yield to other threads
139+
crate::thread::yield_thread();
140140
}
141141

142142
recv_attempts += 1;
@@ -201,7 +201,7 @@ fn get_inner(url: &str, depth: u32) -> Result<HttpsResponse, HttpsError> {
201201
break;
202202
}
203203

204-
for _ in 0..10000 { core::hint::spin_loop(); }
204+
crate::thread::yield_thread();
205205
}
206206

207207
// Close connection

‎kernel/src/netstack/tcp.rs‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,8 @@ pub fn wait_for_established(dest_ip: [u8; 4], dest_port: u16, src_port: u16, tim
862862
if spins > 2_000_000 {
863863
return false;
864864
}
865-
core::hint::spin_loop();
865+
// Yield to other threads so desktop frame loop stays responsive
866+
crate::thread::yield_thread();
866867
}
867868
}
868869

@@ -910,7 +911,7 @@ pub fn send_data(dest_ip: [u8; 4], dest_port: u16, src_port: u16, data: &[u8]) -
910911
Err(e) if retries < 200 => {
911912
// TX queue full or transient error — poll and retry
912913
crate::netstack::poll();
913-
for _ in 0..2000 { core::hint::spin_loop(); }
914+
crate::thread::yield_thread();
914915
retries += 1;
915916
}
916917
Err(e) => return Err(e),
@@ -920,14 +921,8 @@ pub fn send_data(dest_ip: [u8; 4], dest_port: u16, src_port: u16, data: &[u8]) -
920921
// Poll and pace between batches to let receiver process
921922
if (i + 1) % BATCH == 0 {
922923
crate::netstack::poll();
923-
// Wait ~1ms real time to avoid overwhelming receiver
924-
let wait_start = crate::time::uptime_ms();
925-
loop {
926-
core::hint::spin_loop();
927-
if crate::time::uptime_ms().wrapping_sub(wait_start) >= 1 {
928-
break;
929-
}
930-
}
924+
// Yield to let other threads run between batches
925+
crate::thread::yield_thread();
931926
}
932927

933928
}

‎kernel/src/trustdaw/disk_audio.rs‎

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,18 @@
1010
use alloc::string::String;
1111
use alloc::vec::Vec;
1212
use alloc::vec;
13+
use core::sync::atomic::{AtomicU8, Ordering};
1314

1415
const SECTOR_SIZE: usize = 512;
15-
/// Read 8 sectors at a time (4KB = one page, DMA-safe).
16-
/// Using a small page-aligned intermediate buffer avoids issues with large heap
17-
/// allocations whose physical addresses may not be contiguous across pages.
18-
const DMA_READ_SECTORS: u16 = 8;
19-
const DMA_READ_BYTES: usize = DMA_READ_SECTORS as usize * SECTOR_SIZE; // 4096
16+
/// Read 128 sectors at a time (64KB) for fast bulk loading.
17+
/// AHCI supports up to 128 sectors per command; larger batches = fewer commands.
18+
const DMA_READ_SECTORS: u16 = 128;
19+
const DMA_READ_BYTES: usize = DMA_READ_SECTORS as usize * SECTOR_SIZE; // 65536
2020
/// Maximum tracks in v2 format
2121
const MAX_TRACKS: usize = 10;
2222
/// Bytes per track entry in header
2323
const ENTRY_SIZE: usize = 48;
2424

25-
/// Page-aligned intermediate buffer for AHCI DMA reads.
26-
/// AHCI DMA requires a physically contiguous buffer. The kernel heap may
27-
/// not guarantee physical contiguity for large allocations.
28-
/// This small buffer is always within a single 4KB page.
29-
#[repr(C, align(4096))]
30-
struct DmaReadBuf([u8; DMA_READ_BYTES]);
31-
32-
static mut DMA_READ_BUF: DmaReadBuf = DmaReadBuf([0u8; DMA_READ_BYTES]);
33-
3425
/// Track info parsed from the disk header
3526
#[derive(Clone)]
3627
pub struct TrackInfo {
@@ -46,22 +37,33 @@ pub struct DiskTrackTable {
4637
pub tracks: Vec<TrackInfo>,
4738
}
4839

40+
/// Cached AHCI port number for TWAV disk (0xFE = not scanned yet, 0xFF = not found)
41+
static CACHED_PORT: AtomicU8 = AtomicU8::new(0xFE);
42+
4943
/// Try to find which AHCI port has our audio data disk.
44+
/// Result is cached after first scan to avoid repeated port probing.
5045
fn find_data_port() -> Option<u8> {
46+
let cached = CACHED_PORT.load(Ordering::Relaxed);
47+
if cached != 0xFE {
48+
return if cached == 0xFF { None } else { Some(cached) };
49+
}
5150
if !crate::drivers::ahci::is_initialized() {
5251
return None;
5352
}
5453
let devices = crate::drivers::ahci::list_devices();
5554
for dev in &devices {
56-
if dev.port_num == 2 {
57-
return Some(2);
58-
}
59-
}
60-
for dev in &devices {
61-
if dev.port_num > 1 && dev.device_type == crate::drivers::ahci::AhciDeviceType::Sata {
62-
return Some(dev.port_num);
55+
if dev.device_type == crate::drivers::ahci::AhciDeviceType::Sata && dev.sector_count > 64 {
56+
let mut probe = alloc::vec![0u8; 512];
57+
if crate::drivers::ahci::read_sectors(dev.port_num, 0, 1, &mut probe).is_ok() {
58+
if probe.len() >= 4 && &probe[0..4] == b"TWAV" {
59+
crate::serial_println!("[DISK-AUDIO] Found TWAV disk on port {}", dev.port_num);
60+
CACHED_PORT.store(dev.port_num, Ordering::Relaxed);
61+
return Some(dev.port_num);
62+
}
63+
}
6364
}
6465
}
66+
CACHED_PORT.store(0xFF, Ordering::Relaxed);
6567
None
6668
}
6769

@@ -155,8 +157,10 @@ pub fn load_track_from_disk(track_idx: usize) -> Result<(Vec<u8>, String), &'sta
155157
}
156158

157159
/// Load track data given a TrackInfo.
158-
/// Uses a small page-aligned DMA buffer to avoid physical address issues
159-
/// with large heap allocations.
160+
/// Uses a heap-allocated page-aligned DMA buffer so AHCI virt_to_phys
161+
/// (which subtracts HHDM offset) maps correctly. Static .bss buffers
162+
/// live at the kernel's linked VA, outside the HHDM window, so DMA
163+
/// writes to the wrong physical address and we read back zeros.
160164
fn load_track_data(track: &TrackInfo) -> Result<(Vec<u8>, String), &'static str> {
161165
let port = find_data_port().ok_or("No data disk found on AHCI")?;
162166

@@ -167,34 +171,37 @@ fn load_track_data(track: &TrackInfo) -> Result<(Vec<u8>, String), &'static str>
167171
crate::serial_println!("[DISK-AUDIO] Loading '{}': {} bytes, {} sectors from LBA {}",
168172
track.name, track.wav_size, track.sector_count, track.start_lba);
169173

174+
// Allocate DMA buffer FIRST (small, 64KB) before the large WAV buffer
175+
// so the allocator can service it from a clean free-list.
176+
let mut dma_buf = vec![0u8; DMA_READ_BYTES];
177+
170178
let mut wav_buf = Vec::with_capacity(track.wav_size);
171179

172180
let mut sectors_remaining = track.sector_count;
173181
let mut current_lba = track.start_lba;
174182

175183
while sectors_remaining > 0 {
176-
let chunk = (sectors_remaining as u16).min(DMA_READ_SECTORS);
177-
let chunk_bytes = chunk as usize * SECTOR_SIZE;
178-
179-
// Read into page-aligned DMA-safe intermediate buffer
180-
let bytes_read = unsafe {
181-
crate::drivers::ahci::read_sectors(
182-
port, current_lba, chunk,
183-
&mut DMA_READ_BUF.0[..chunk_bytes],
184-
)?
185-
};
184+
// Compute chunk in usize FIRST, then cast to u16.
185+
// Avoids truncation: e.g. 102496_usize as u16 = 37120, but
186+
// 102496_usize.min(128) = 128 then as u16 = 128.
187+
let chunk_sectors = sectors_remaining.min(DMA_READ_SECTORS as usize);
188+
let chunk = chunk_sectors as u16;
189+
let chunk_bytes = chunk_sectors * SECTOR_SIZE;
190+
191+
crate::drivers::ahci::read_sectors(
192+
port, current_lba, chunk,
193+
&mut dma_buf[..chunk_bytes],
194+
)?;
186195

187196
// Copy from DMA buffer to target Vec (only up to what we still need)
188197
let needed = track.wav_size.saturating_sub(wav_buf.len());
189-
let to_copy = bytes_read.min(needed);
198+
let to_copy = chunk_bytes.min(needed);
190199
if to_copy > 0 {
191-
unsafe {
192-
wav_buf.extend_from_slice(&DMA_READ_BUF.0[..to_copy]);
193-
}
200+
wav_buf.extend_from_slice(&dma_buf[..to_copy]);
194201
}
195202

196-
current_lba += chunk as u64;
197-
sectors_remaining -= chunk as usize;
203+
current_lba += chunk_sectors as u64;
204+
sectors_remaining -= chunk_sectors;
198205
}
199206

200207
wav_buf.truncate(track.wav_size);
@@ -213,3 +220,11 @@ pub fn load_wav_from_disk() -> Result<Vec<u8>, &'static str> {
213220
let (data, _name) = load_track_from_disk(0)?;
214221
Ok(data)
215222
}
223+
224+
/// Get track names for the UI (reads header only, fast).
225+
pub fn get_track_names() -> Vec<String> {
226+
match read_track_table() {
227+
Ok(table) => table.tracks.iter().map(|t| t.name.clone()).collect(),
228+
Err(_) => Vec::new(),
229+
}
230+
}

‎kernel/src/vfs/mod.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,17 @@ pub fn init() {
280280
if !root_mounted && crate::drivers::ahci::is_initialized() {
281281
let devices = crate::drivers::ahci::list_devices();
282282
// Use the first non-optical AHCI port with sectors > 0
283+
// Skip disks that have TWAV magic (audio data disks)
283284
for dev in &devices {
284285
if dev.sector_count > 64 {
286+
// Check if this disk has TWAV magic — skip audio data disks
287+
let mut probe = alloc::vec![0u8; 512];
288+
if crate::drivers::ahci::read_sectors(dev.port_num, 0, 1, &mut probe).is_ok() {
289+
if probe.len() >= 4 && &probe[0..4] == b"TWAV" {
290+
crate::log!("[VFS] Skipping AHCI port {} (TWAV audio data disk)", dev.port_num);
291+
continue;
292+
}
293+
}
285294
let backend = Arc::new(fat32::AhciBlockReader::new(dev.port_num as usize, 0));
286295
match trustfs::TrustFs::new(backend, dev.sector_count) {
287296
Ok(trustfs) => {

‎scripts/launch/launch-vbox-clean.ps1‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ Write-Output "Step 4: Configure VM..."
6161
& $VBM storagectl $VMName --name "SATA" --add sata --controller IntelAhci --portcount 4 2>&1
6262
& $VBM storageattach $VMName --storagectl "SATA" --port 0 --device 0 --type dvddrive --medium $ISOPath 2>&1
6363

64+
# Audio data disk on AHCI port 3 (raw sectors with WAV tracks)
65+
# Port 2 is used by TrustFS for the root filesystem
66+
$DataDisk = "C:\Users\nathan\Documents\Scripts\OSrust\builds\trustos_data.img"
67+
if (Test-Path $DataDisk) {
68+
# Convert raw .img to VDI for VirtualBox
69+
$DataVDI = "C:\Users\nathan\Documents\Scripts\OSrust\builds\trustos_data.vdi"
70+
Remove-Item $DataVDI -ErrorAction SilentlyContinue
71+
& $VBM convertfromraw $DataDisk $DataVDI --format VDI 2>&1
72+
& $VBM storageattach $VMName --storagectl "SATA" --port 3 --device 0 --type hdd --medium $DataVDI 2>&1
73+
Write-Output "Audio data disk attached on SATA port 3"
74+
} else {
75+
Write-Output "WARNING: No audio data disk found at $DataDisk"
76+
}
77+
6478
# Serial
6579
Remove-Item $SerialLog -ErrorAction SilentlyContinue
6680
& $VBM modifyvm $VMName --uart1 0x3F8 4 --uartmode1 file $SerialLog 2>&1

0 commit comments

Comments
 (0)