paavohtl Claude Opus 4.6 (1M context) commited on
Commit
cbc66c4
·
1 Parent(s): 882d6f3

refactor: use Fixed types and meaningful names in DSSound

Browse files

Rename ChannelDescriptor fields:
- flags_a -> pool_idx (buffer pool index, -1 = not pooled)
- flags_b -> priority (sound priority for channel eviction)
- channel_freq, channel_volume -> Fixed type (were already Fixed 16.16)
- flags_and_slot -> slot_and_flags

Use Fixed for all volume/frequency/pan parameters:
- core_play_sound, play_sound, play_sound_pooled
- set_master_volume, set_channel_volume, set_pan
- DSSoundVtable fn signatures updated to match

Fixed is #[repr(transparent)] wrapping i32 — same ABI, no behavioral change.
Updated all stale comments referencing old field names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/audio/dssound.rs CHANGED
@@ -35,15 +35,17 @@ type Ptr32 = u32;
35
  pub struct ChannelDescriptor {
36
  /// +0x00: Flags/state field (not set by constructor init helper)
37
  pub _field_00: u32,
38
- /// +0x04: Flags A (init -1, -1 = unused/free, checked for < 0)
39
- pub flags_a: i32,
40
- /// +0x08: Flags B (init -1, set to -1 on release)
41
- pub flags_b: i32,
42
- /// +0x0C: Per-channel frequency/pitch value. Multiplied by status_2/status_1
 
 
43
  /// to compute actual playback frequency. Cleared to 0 on release.
44
- pub channel_freq: i32,
45
  /// +0x10: Per-channel volume (Fixed 16.16, 0..0x10000). Set by set_channel_volume.
46
- pub channel_volume: i32,
47
  /// +0x14: IDirectSoundBuffer* for the active buffer (0 = empty).
48
  /// At absolute offset this+0x28 for desc[0]. Use `buffer()` to get a typed ref.
49
  pub ds_buffer: Ptr32,
@@ -86,17 +88,17 @@ pub struct DSSoundVtable {
86
  /// set_volume_params — sets status_1/2, adjusts channel volumes
87
  pub set_volume_params: fn(this: *mut DSSound, status: u32, value: i32),
88
  /// play_sound — wrapper around core play. RET 0x14 = 5 stack params.
89
- pub play_sound: fn(this: *mut DSSound, slot: u32, flags: u32, volume: u32, pan: u32, freq: u32) -> bool,
90
  /// play_sound_pooled — allocates from buffer pool, plays. RET 0x14.
91
- pub play_sound_pooled: fn(this: *mut DSSound, slot: u32, flags: u32, volume: u32, pan: u32, freq: u32) -> i32,
92
  /// set_pan — sets pan on channel (dB lookup)
93
- pub set_pan: fn(this: *mut DSSound, channel: u32, pan: u32) -> u32,
94
  /// **stub** — returns 0
95
  pub stub_6: fn(this: *mut DSSound) -> u32,
96
  /// set_master_volume — sets volume, adjusts all channels
97
- pub set_master_volume: fn(this: *mut DSSound, volume: i32) -> u32,
98
  /// set_channel_volume — volume on specific channel
99
- pub set_channel_volume: fn(this: *mut DSSound, channel: i32, volume: i32) -> u32,
100
  /// is_channel_finished — returns 1 if stopped, 0 if playing
101
  pub is_channel_finished: fn(this: *mut DSSound, channel: i32) -> u8,
102
  /// stop_channel — stops + releases buffer, returns to pool
@@ -212,14 +214,14 @@ pub unsafe extern "thiscall" fn update_channels(this: *mut DSSound) {
212
  for desc in &mut snd.channel_descs {
213
  if let Some(buf) = desc.buffer() {
214
  if let Ok(status) = buf.GetStatus() {
215
- // If not playing (bit 0 clear) and flags_b < 0, release.
216
- if (status & 1) == 0 && desc.flags_a < 0 {
217
  // Take and drop to release COM ref.
218
  desc.take_buffer();
219
- desc.channel_freq = 0;
220
- desc.channel_volume = 0;
221
- desc.flags_b = -1;
222
- desc.flags_a = -1;
223
  }
224
  }
225
  }
@@ -233,12 +235,12 @@ pub unsafe extern "thiscall" fn release_finished(this: *mut DSSound) -> i32 {
233
  for desc in &mut snd.channel_descs {
234
  if let Some(buf) = desc.buffer() {
235
  if let Ok(status) = buf.GetStatus() {
236
- if (status & 1) == 0 && desc.flags_a < 0 {
237
  desc.take_buffer();
238
- desc.channel_freq = 0;
239
- desc.channel_volume = 0;
240
- desc.flags_b = -1;
241
- desc.flags_a = -1;
242
  count += 1;
243
  }
244
  }
@@ -258,11 +260,11 @@ fn volume_to_db(volume: i32) -> i32 {
258
 
259
  /// Slot 7: set_master_volume — sets the master volume and adjusts all active channels.
260
  /// `new_volume` is Fixed 16.16 (0 = silent, 0x10000 = max).
261
- pub unsafe extern "thiscall" fn set_master_volume(this: *mut DSSound, new_volume: i32) -> u32 {
262
  let snd = &mut *this;
263
 
264
  // Clamp to [0, 0x10000].
265
- let vol = new_volume.max(0).min(0x10000);
266
 
267
  // No change → early return.
268
  if snd.volume.0 == vol {
@@ -280,7 +282,7 @@ pub unsafe extern "thiscall" fn set_master_volume(this: *mut DSSound, new_volume
280
  continue;
281
  }
282
  // Compute combined volume: master * per-channel, fixed-point multiply.
283
- let combined = ((vol as i64 * desc.channel_volume as i64) >> 16) as i32;
284
  let db = volume_to_db(combined);
285
  if let Some(buf) = desc.buffer() {
286
  let _ = buf.SetVolume(db);
@@ -318,7 +320,7 @@ pub unsafe extern "thiscall" fn set_volume_params(this: *mut DSSound, param1: u3
318
  if param1 == 0 {
319
  continue;
320
  }
321
- let freq = (desc.channel_freq as i64 * param2 as i64 / param1 as i64) as i32;
322
  let freq = freq.min(200_000);
323
 
324
  let _ = buf.SetFrequency(freq as u32);
@@ -328,7 +330,7 @@ pub unsafe extern "thiscall" fn set_volume_params(this: *mut DSSound, param1: u3
328
  /// Slot 5: set_pan — sets stereo panning on a specific channel.
329
  /// `pool_id` is 1-based (1..64). `pan` is Fixed 16.16 (-0x10000..0x10000).
330
  /// Negative = left, positive = right.
331
- pub unsafe extern "thiscall" fn set_pan(this: *mut DSSound, pool_id: i32, pan: i32) -> u32 {
332
  let idx = (pool_id - 1) as usize;
333
  if idx >= 64 {
334
  return 0;
@@ -340,7 +342,7 @@ pub unsafe extern "thiscall" fn set_pan(this: *mut DSSound, pool_id: i32, pan: i
340
  }
341
 
342
  // Clamp pan to [-0x10000, 0x10000].
343
- let pan = pan.max(-0x10000).min(0x10000);
344
 
345
  // Index into table backwards: abs(pan) >> 10 gives 0..63.
346
  // Table is read from the END (index 63 = near silence, index 0 = full).
@@ -364,7 +366,7 @@ pub unsafe extern "thiscall" fn set_pan(this: *mut DSSound, pool_id: i32, pan: i
364
  pub unsafe extern "thiscall" fn set_channel_volume(
365
  this: *mut DSSound,
366
  pool_id: i32,
367
- volume: i32,
368
  ) -> u32 {
369
  let idx = (pool_id - 1) as usize;
370
  if idx >= 64 {
@@ -378,7 +380,7 @@ pub unsafe extern "thiscall" fn set_channel_volume(
378
  let di = desc_idx as usize;
379
 
380
  // Clamp volume to [0, 0x10000].
381
- let vol = volume.max(0).min(0x10000);
382
 
383
  // Compute dB: if flag 0x20000 set, use volume directly (no master scaling).
384
  let db = if snd.channel_descs[di]._field_00 & 0x20000 != 0 {
@@ -393,7 +395,7 @@ pub unsafe extern "thiscall" fn set_channel_volume(
393
  }
394
 
395
  // Store per-channel volume.
396
- snd.channel_descs[di].channel_volume = vol;
397
 
398
  1
399
  }
@@ -454,11 +456,11 @@ pub unsafe extern "thiscall" fn destructor(this: *mut DSSound, flags: u8) -> *mu
454
  /// Returns channel descriptor index on success, -1 on failure.
455
  unsafe fn core_play_sound(
456
  snd: &mut DSSound,
457
- flags_and_slot: u32,
458
  priority: i32,
459
- frequency: i32,
460
- volume: i32,
461
- pan: i32,
462
  ) -> i32 {
463
  use windows::Win32::Media::Audio::DirectSound::IDirectSound;
464
 
@@ -468,8 +470,8 @@ unsafe fn core_play_sound(
468
  }
469
 
470
  // Extract loop flag and slot index.
471
- let loop_flag = flags_and_slot & 0x10000 != 0;
472
- let slot_idx = (flags_and_slot & 0xFFFF) as usize;
473
 
474
  // Validate slot index and check buffer loaded.
475
  if slot_idx == 0 || slot_idx > 499 || snd.channel_slots[slot_idx] == 0 {
@@ -498,13 +500,13 @@ unsafe fn core_play_sound(
498
  };
499
 
500
  // Clamp volume to [0, 0x10000].
501
- let vol = volume.max(0).min(0x10000);
502
 
503
  // Compute per-channel volume: vol * 3/4 (with rounding).
504
  let vol_scaled = (vol * 3 + (((vol * 3) >> 31) & 3)) >> 2;
505
 
506
  // Compute dB for volume.
507
- let vol_db = if flags_and_slot & 0x20000 != 0 {
508
  volume_to_db(vol_scaled)
509
  } else {
510
  let combined = ((snd.volume.0 as i64 * vol_scaled as i64) >> 16) as i32;
@@ -513,14 +515,14 @@ unsafe fn core_play_sound(
513
 
514
  // Compute channel_freq: frequency * base_freq >> 16 (fixed-point multiply).
515
  let base_freq = template_buf.GetFrequency().unwrap_or(22050) as i32;
516
- let channel_freq = ((frequency.max(0) as i64 * base_freq as i64) >> 16) as i32;
517
 
518
  // Store descriptor state.
519
  let desc = &mut snd.channel_descs[di];
520
- desc.flags_a = -1;
521
- desc.flags_b = priority;
522
- desc.channel_freq = channel_freq;
523
- desc.channel_volume = vol_scaled;
524
  desc.ds_buffer = core::mem::transmute_copy(&new_buf);
525
  core::mem::forget(new_buf);
526
 
@@ -539,7 +541,7 @@ unsafe fn core_play_sound(
539
  let _ = buf.SetVolume(vol_db);
540
 
541
  // Pan: convert fixed-point to dB via reversed lookup table.
542
- let pan_clamped = pan.max(-0x10000).min(0x10000);
543
  let pan_idx = ((pan_clamped.unsigned_abs() >> 10) as usize).min(63);
544
  let mut pan_db = VOLUME_DB_TABLE[63 - pan_idx] as i32;
545
  if pan_clamped > 0 {
@@ -564,11 +566,11 @@ unsafe fn allocate_channel(snd: &mut DSSound, priority: i32) -> i32 {
564
  }
565
  }
566
 
567
- // All busy — find lowest priority (flags_b is priority value).
568
  let mut min_priority = i32::MAX;
569
  let mut min_idx: i32 = -1;
570
  for i in 0..8 {
571
- let p = snd.channel_descs[i].flags_b;
572
  if min_idx < 0 || p <= min_priority {
573
  min_priority = p;
574
  min_idx = i as i32;
@@ -581,8 +583,8 @@ unsafe fn allocate_channel(snd: &mut DSSound, priority: i32) -> i32 {
581
  let desc = &mut snd.channel_descs[di];
582
 
583
  // Clear pool shadow if this desc was in the pool.
584
- if desc.flags_a >= 0 && (desc.flags_a as usize) < 64 {
585
- snd.buffer_pool_shadow[desc.flags_a as usize] = -1;
586
  }
587
 
588
  // Stop and release the evicted buffer.
@@ -597,19 +599,19 @@ unsafe fn allocate_channel(snd: &mut DSSound, priority: i32) -> i32 {
597
  }
598
 
599
  /// Slot 3: play_sound — pure thiscall with 5 stack params, RET 0x14.
600
- /// Params: flags_and_slot, priority, frequency, volume, pan.
601
  pub unsafe extern "thiscall" fn play_sound(
602
  this: *mut DSSound,
603
- flags_and_slot: u32,
604
  priority: i32,
605
- frequency: i32,
606
- volume: i32,
607
- pan: i32,
608
  ) -> bool {
609
  let snd = &mut *this;
610
  let result = core_play_sound(
611
  snd,
612
- flags_and_slot & 0xFFFEFFFF,
613
  priority,
614
  frequency,
615
  volume,
@@ -622,11 +624,11 @@ pub unsafe extern "thiscall" fn play_sound(
622
  /// Returns pool_id (1-based) on success, 0 on failure.
623
  pub unsafe extern "thiscall" fn play_sound_pooled(
624
  this: *mut DSSound,
625
- flags_and_slot: u32,
626
  priority: i32,
627
- frequency: i32,
628
- volume: i32,
629
- pan: i32,
630
  ) -> i32 {
631
  let snd = &mut *this;
632
 
@@ -635,7 +637,7 @@ pub unsafe extern "thiscall" fn play_sound_pooled(
635
  return 0;
636
  }
637
 
638
- let desc_idx = core_play_sound(snd, flags_and_slot, priority, frequency, volume, pan);
639
  if desc_idx < 0 {
640
  return 0;
641
  }
@@ -651,7 +653,7 @@ pub unsafe extern "thiscall" fn play_sound_pooled(
651
 
652
  // Link pool ↔ descriptor.
653
  snd.buffer_pool_shadow[pool_idx as usize] = desc_idx;
654
- snd.channel_descs[desc_idx as usize].flags_a = pool_idx as i32;
655
 
656
  (pool_idx + 1) as i32 // 1-based
657
  }
@@ -723,10 +725,10 @@ pub unsafe extern "thiscall" fn stop_channel(this: *mut DSSound, pool_id: i32) -
723
  }
724
 
725
  // Reset descriptor to free state.
726
- desc.channel_freq = 0;
727
- desc.channel_volume = 0;
728
- desc.flags_b = -1;
729
- desc.flags_a = -1;
730
  }
731
 
732
  // Mark pool shadow as free.
@@ -936,11 +938,11 @@ impl DSSound {
936
  // Volume = 1.0 (16.16 fixed-point)
937
  snd.volume = Fixed(0x10000);
938
 
939
- // 8 channel descriptors: flags_a=-1, flags_b=-1
940
  // (rest is zero from mem::zeroed)
941
  for desc in &mut snd.channel_descs {
942
- desc.flags_a = -1;
943
- desc.flags_b = -1;
944
  }
945
 
946
  // 500 channel slots: already zeroed
 
35
  pub struct ChannelDescriptor {
36
  /// +0x00: Flags/state field (not set by constructor init helper)
37
  pub _field_00: u32,
38
+ /// +0x04: Buffer pool index (0..63), or -1 if not pooled.
39
+ /// Set by play_sound_pooled; checked by update_channels/stop_channel/allocate_channel.
40
+ pub pool_idx: i32,
41
+ /// +0x08: Sound priority (init -1, set to -1 on release).
42
+ /// Used by allocate_channel for eviction: lowest priority channel is evicted first.
43
+ pub priority: i32,
44
+ /// +0x0C: Per-channel frequency (Fixed 16.16). Multiplied by status_2/status_1
45
  /// to compute actual playback frequency. Cleared to 0 on release.
46
+ pub channel_freq: Fixed,
47
  /// +0x10: Per-channel volume (Fixed 16.16, 0..0x10000). Set by set_channel_volume.
48
+ pub channel_volume: Fixed,
49
  /// +0x14: IDirectSoundBuffer* for the active buffer (0 = empty).
50
  /// At absolute offset this+0x28 for desc[0]. Use `buffer()` to get a typed ref.
51
  pub ds_buffer: Ptr32,
 
88
  /// set_volume_params — sets status_1/2, adjusts channel volumes
89
  pub set_volume_params: fn(this: *mut DSSound, status: u32, value: i32),
90
  /// play_sound — wrapper around core play. RET 0x14 = 5 stack params.
91
+ pub play_sound: fn(this: *mut DSSound, slot_and_flags: u32, priority: i32, volume: Fixed, pan: Fixed, freq: Fixed) -> bool,
92
  /// play_sound_pooled — allocates from buffer pool, plays. RET 0x14.
93
+ pub play_sound_pooled: fn(this: *mut DSSound, slot_and_flags: u32, priority: i32, volume: Fixed, pan: Fixed, freq: Fixed) -> i32,
94
  /// set_pan — sets pan on channel (dB lookup)
95
+ pub set_pan: fn(this: *mut DSSound, channel: u32, pan: Fixed) -> u32,
96
  /// **stub** — returns 0
97
  pub stub_6: fn(this: *mut DSSound) -> u32,
98
  /// set_master_volume — sets volume, adjusts all channels
99
+ pub set_master_volume: fn(this: *mut DSSound, volume: Fixed) -> u32,
100
  /// set_channel_volume — volume on specific channel
101
+ pub set_channel_volume: fn(this: *mut DSSound, channel: i32, volume: Fixed) -> u32,
102
  /// is_channel_finished — returns 1 if stopped, 0 if playing
103
  pub is_channel_finished: fn(this: *mut DSSound, channel: i32) -> u8,
104
  /// stop_channel — stops + releases buffer, returns to pool
 
214
  for desc in &mut snd.channel_descs {
215
  if let Some(buf) = desc.buffer() {
216
  if let Ok(status) = buf.GetStatus() {
217
+ // If not playing (bit 0 clear) and not pooled (pool_idx < 0), release.
218
+ if (status & 1) == 0 && desc.pool_idx < 0 {
219
  // Take and drop to release COM ref.
220
  desc.take_buffer();
221
+ desc.channel_freq = Fixed(0);
222
+ desc.channel_volume = Fixed(0);
223
+ desc.priority = -1;
224
+ desc.pool_idx = -1;
225
  }
226
  }
227
  }
 
235
  for desc in &mut snd.channel_descs {
236
  if let Some(buf) = desc.buffer() {
237
  if let Ok(status) = buf.GetStatus() {
238
+ if (status & 1) == 0 && desc.pool_idx < 0 {
239
  desc.take_buffer();
240
+ desc.channel_freq = Fixed(0);
241
+ desc.channel_volume = Fixed(0);
242
+ desc.priority = -1;
243
+ desc.pool_idx = -1;
244
  count += 1;
245
  }
246
  }
 
260
 
261
  /// Slot 7: set_master_volume — sets the master volume and adjusts all active channels.
262
  /// `new_volume` is Fixed 16.16 (0 = silent, 0x10000 = max).
263
+ pub unsafe extern "thiscall" fn set_master_volume(this: *mut DSSound, new_volume: Fixed) -> u32 {
264
  let snd = &mut *this;
265
 
266
  // Clamp to [0, 0x10000].
267
+ let vol = new_volume.0.max(0).min(0x10000);
268
 
269
  // No change → early return.
270
  if snd.volume.0 == vol {
 
282
  continue;
283
  }
284
  // Compute combined volume: master * per-channel, fixed-point multiply.
285
+ let combined = ((vol as i64 * desc.channel_volume.0 as i64) >> 16) as i32;
286
  let db = volume_to_db(combined);
287
  if let Some(buf) = desc.buffer() {
288
  let _ = buf.SetVolume(db);
 
320
  if param1 == 0 {
321
  continue;
322
  }
323
+ let freq = (desc.channel_freq.0 as i64 * param2 as i64 / param1 as i64) as i32;
324
  let freq = freq.min(200_000);
325
 
326
  let _ = buf.SetFrequency(freq as u32);
 
330
  /// Slot 5: set_pan — sets stereo panning on a specific channel.
331
  /// `pool_id` is 1-based (1..64). `pan` is Fixed 16.16 (-0x10000..0x10000).
332
  /// Negative = left, positive = right.
333
+ pub unsafe extern "thiscall" fn set_pan(this: *mut DSSound, pool_id: i32, pan: Fixed) -> u32 {
334
  let idx = (pool_id - 1) as usize;
335
  if idx >= 64 {
336
  return 0;
 
342
  }
343
 
344
  // Clamp pan to [-0x10000, 0x10000].
345
+ let pan = pan.0.max(-0x10000).min(0x10000);
346
 
347
  // Index into table backwards: abs(pan) >> 10 gives 0..63.
348
  // Table is read from the END (index 63 = near silence, index 0 = full).
 
366
  pub unsafe extern "thiscall" fn set_channel_volume(
367
  this: *mut DSSound,
368
  pool_id: i32,
369
+ volume: Fixed,
370
  ) -> u32 {
371
  let idx = (pool_id - 1) as usize;
372
  if idx >= 64 {
 
380
  let di = desc_idx as usize;
381
 
382
  // Clamp volume to [0, 0x10000].
383
+ let vol = volume.0.max(0).min(0x10000);
384
 
385
  // Compute dB: if flag 0x20000 set, use volume directly (no master scaling).
386
  let db = if snd.channel_descs[di]._field_00 & 0x20000 != 0 {
 
395
  }
396
 
397
  // Store per-channel volume.
398
+ snd.channel_descs[di].channel_volume = Fixed(vol);
399
 
400
  1
401
  }
 
456
  /// Returns channel descriptor index on success, -1 on failure.
457
  unsafe fn core_play_sound(
458
  snd: &mut DSSound,
459
+ slot_and_flags: u32,
460
  priority: i32,
461
+ frequency: Fixed,
462
+ volume: Fixed,
463
+ pan: Fixed,
464
  ) -> i32 {
465
  use windows::Win32::Media::Audio::DirectSound::IDirectSound;
466
 
 
470
  }
471
 
472
  // Extract loop flag and slot index.
473
+ let loop_flag = slot_and_flags & 0x10000 != 0;
474
+ let slot_idx = (slot_and_flags & 0xFFFF) as usize;
475
 
476
  // Validate slot index and check buffer loaded.
477
  if slot_idx == 0 || slot_idx > 499 || snd.channel_slots[slot_idx] == 0 {
 
500
  };
501
 
502
  // Clamp volume to [0, 0x10000].
503
+ let vol = volume.0.max(0).min(0x10000);
504
 
505
  // Compute per-channel volume: vol * 3/4 (with rounding).
506
  let vol_scaled = (vol * 3 + (((vol * 3) >> 31) & 3)) >> 2;
507
 
508
  // Compute dB for volume.
509
+ let vol_db = if slot_and_flags & 0x20000 != 0 {
510
  volume_to_db(vol_scaled)
511
  } else {
512
  let combined = ((snd.volume.0 as i64 * vol_scaled as i64) >> 16) as i32;
 
515
 
516
  // Compute channel_freq: frequency * base_freq >> 16 (fixed-point multiply).
517
  let base_freq = template_buf.GetFrequency().unwrap_or(22050) as i32;
518
+ let channel_freq = ((frequency.0.max(0) as i64 * base_freq as i64) >> 16) as i32;
519
 
520
  // Store descriptor state.
521
  let desc = &mut snd.channel_descs[di];
522
+ desc.pool_idx = -1;
523
+ desc.priority = priority;
524
+ desc.channel_freq = Fixed(channel_freq);
525
+ desc.channel_volume = Fixed(vol_scaled);
526
  desc.ds_buffer = core::mem::transmute_copy(&new_buf);
527
  core::mem::forget(new_buf);
528
 
 
541
  let _ = buf.SetVolume(vol_db);
542
 
543
  // Pan: convert fixed-point to dB via reversed lookup table.
544
+ let pan_clamped = pan.0.max(-0x10000).min(0x10000);
545
  let pan_idx = ((pan_clamped.unsigned_abs() >> 10) as usize).min(63);
546
  let mut pan_db = VOLUME_DB_TABLE[63 - pan_idx] as i32;
547
  if pan_clamped > 0 {
 
566
  }
567
  }
568
 
569
+ // All busy — find lowest priority channel to evict.
570
  let mut min_priority = i32::MAX;
571
  let mut min_idx: i32 = -1;
572
  for i in 0..8 {
573
+ let p = snd.channel_descs[i].priority;
574
  if min_idx < 0 || p <= min_priority {
575
  min_priority = p;
576
  min_idx = i as i32;
 
583
  let desc = &mut snd.channel_descs[di];
584
 
585
  // Clear pool shadow if this desc was in the pool.
586
+ if desc.pool_idx >= 0 && (desc.pool_idx as usize) < 64 {
587
+ snd.buffer_pool_shadow[desc.pool_idx as usize] = -1;
588
  }
589
 
590
  // Stop and release the evicted buffer.
 
599
  }
600
 
601
  /// Slot 3: play_sound — pure thiscall with 5 stack params, RET 0x14.
602
+ /// Params: slot_and_flags, priority, frequency, volume, pan.
603
  pub unsafe extern "thiscall" fn play_sound(
604
  this: *mut DSSound,
605
+ slot_and_flags: u32,
606
  priority: i32,
607
+ frequency: Fixed,
608
+ volume: Fixed,
609
+ pan: Fixed,
610
  ) -> bool {
611
  let snd = &mut *this;
612
  let result = core_play_sound(
613
  snd,
614
+ slot_and_flags & 0xFFFEFFFF,
615
  priority,
616
  frequency,
617
  volume,
 
624
  /// Returns pool_id (1-based) on success, 0 on failure.
625
  pub unsafe extern "thiscall" fn play_sound_pooled(
626
  this: *mut DSSound,
627
+ slot_and_flags: u32,
628
  priority: i32,
629
+ frequency: Fixed,
630
+ volume: Fixed,
631
+ pan: Fixed,
632
  ) -> i32 {
633
  let snd = &mut *this;
634
 
 
637
  return 0;
638
  }
639
 
640
+ let desc_idx = core_play_sound(snd, slot_and_flags, priority, frequency, volume, pan);
641
  if desc_idx < 0 {
642
  return 0;
643
  }
 
653
 
654
  // Link pool ↔ descriptor.
655
  snd.buffer_pool_shadow[pool_idx as usize] = desc_idx;
656
+ snd.channel_descs[desc_idx as usize].pool_idx = pool_idx as i32;
657
 
658
  (pool_idx + 1) as i32 // 1-based
659
  }
 
725
  }
726
 
727
  // Reset descriptor to free state.
728
+ desc.channel_freq = Fixed(0);
729
+ desc.channel_volume = Fixed(0);
730
+ desc.priority = -1;
731
+ desc.pool_idx = -1;
732
  }
733
 
734
  // Mark pool shadow as free.
 
938
  // Volume = 1.0 (16.16 fixed-point)
939
  snd.volume = Fixed(0x10000);
940
 
941
+ // 8 channel descriptors: pool_idx=-1, priority=-1
942
  // (rest is zero from mem::zeroed)
943
  for desc in &mut snd.channel_descs {
944
+ desc.pool_idx = -1;
945
+ desc.priority = -1;
946
  }
947
 
948
  // 500 channel slots: already zeroed
crates/openwa-core/src/fixed.rs CHANGED
@@ -45,6 +45,21 @@ impl Fixed {
45
  pub fn to_f32(self) -> f32 {
46
  self.0 as f32 / Self::SCALE as f32
47
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
  impl Add for Fixed {
 
45
  pub fn to_f32(self) -> f32 {
46
  self.0 as f32 / Self::SCALE as f32
47
  }
48
+
49
+ #[inline]
50
+ pub fn max(self, other: Self) -> Self {
51
+ if self > other { self } else { other }
52
+ }
53
+
54
+ #[inline]
55
+ pub fn min(self, other: Self) -> Self {
56
+ if self < other { self } else { other }
57
+ }
58
+
59
+ #[inline]
60
+ pub fn abs(self) -> Self {
61
+ Self(self.0.abs())
62
+ }
63
  }
64
 
65
  impl Add for Fixed {
crates/openwa-wormkit/src/replacements/sound.rs CHANGED
@@ -437,9 +437,9 @@ unsafe extern "fastcall" fn hook_dispatch_global_sound(
437
  dssound,
438
  sound_id,
439
  flags as i32,
440
- volume as i32,
441
- pitch as i32,
442
- 0,
443
  ) as u32
444
  }
445
 
@@ -470,7 +470,7 @@ unsafe extern "fastcall" fn hook_play_sound_pooled_direct(
470
  return 0;
471
  }
472
 
473
- play_sound_pooled(dssound, param1, param2 as i32, 0x10000, param3 as i32, 0) as u32
474
  }
475
 
476
  // ============================================================
 
437
  dssound,
438
  sound_id,
439
  flags as i32,
440
+ Fixed(volume as i32),
441
+ Fixed(pitch as i32),
442
+ Fixed(0),
443
  ) as u32
444
  }
445
 
 
470
  return 0;
471
  }
472
 
473
+ play_sound_pooled(dssound, param1, param2 as i32, Fixed(0x10000), Fixed(param3 as i32), Fixed(0)) as u32
474
  }
475
 
476
  // ============================================================