From ebe651761b6c02b0e0e3fb01c37a1378be860a70 Mon Sep 17 00:00:00 2001 From: Domppari Date: Sat, 3 Jan 2026 21:24:33 +0200 Subject: [PATCH 1/7] Initial HDD sound implementation using IBM example drive --- src/disk/CMakeLists.txt | 1 + src/disk/hdd.c | 6 +- src/disk/hdd_audio.c | 232 +++++++++++++++++++++++++++++++++ src/floppy/fdd_audio.c | 108 ++------------- src/include/86box/fdd_audio.h | 17 --- src/include/86box/hdd_audio.h | 6 + src/include/86box/sound.h | 4 + src/include/86box/sound_util.h | 28 ++++ src/sound/CMakeLists.txt | 1 + src/sound/audio4.c | 20 ++- src/sound/openal.c | 58 ++++++--- src/sound/sndio.c | 14 +- src/sound/sound.c | 67 ++++++++++ src/sound/sound_util.c | 85 ++++++++++++ src/sound/xaudio2.c | 19 +++ 15 files changed, 523 insertions(+), 143 deletions(-) create mode 100644 src/disk/hdd_audio.c create mode 100644 src/include/86box/hdd_audio.h create mode 100644 src/include/86box/sound_util.h create mode 100644 src/sound/sound_util.c diff --git a/src/disk/CMakeLists.txt b/src/disk/CMakeLists.txt index 9b8d72f54..fff17a5e4 100644 --- a/src/disk/CMakeLists.txt +++ b/src/disk/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(hdd OBJECT hdc_ide_sff8038i.c hdc_ide_um8673f.c hdc_ide_w83769f.c + hdd_audio.c ) add_library(rdisk OBJECT rdisk.c) diff --git a/src/disk/hdd.c b/src/disk/hdd.c index 357c787b8..f6d769b9e 100644 --- a/src/disk/hdd.c +++ b/src/disk/hdd.c @@ -27,6 +27,7 @@ #include <86box/hdd.h> #include <86box/cdrom.h> #include <86box/video.h> +#include <86box/hdd_audio.h> #include "cpu.h" #define HDD_OVERHEAD_TIME 50.0 @@ -38,7 +39,7 @@ hdd_init(void) { /* Clear all global data. */ memset(hdd, 0x00, sizeof(hdd)); - + hdd_audio_init(); return 0; } @@ -196,6 +197,9 @@ hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_t operation, uint8_ } if (!max_seek_time || seek_time <= max_seek_time) { + if (new_cylinder != hdd->cur_cylinder) + hdd_audio_seek(hdd, new_cylinder); + hdd->cur_addr = dst_addr; hdd->cur_track = new_track; hdd->cur_cylinder = new_cylinder; diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c new file mode 100644 index 000000000..54419c244 --- /dev/null +++ b/src/disk/hdd_audio.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include <86box/86box.h> +#include <86box/hdd.h> +#include <86box/sound.h> +#include <86box/sound_util.h> +#include <86box/thread.h> + +/* Maximum number of simultaneous seek sounds */ +#define HDD_MAX_SEEK_VOICES 8 + +typedef struct { + int active; + int position; + float volume; +} hdd_seek_voice_t; + +static int16_t *hdd_spindle_sound_buffer = NULL; +static int16_t *hdd_seek_sound_buffer = NULL; +static int hdd_samples; +static int hdd_seek_samples; + +static hdd_seek_voice_t hdd_seek_voices[HDD_MAX_SEEK_VOICES]; +static mutex_t *hdd_audio_mutex = NULL; + +void +hdd_audio_init(void) +{ + hdd_spindle_sound_buffer = sound_load_wav( + "assets/sounds/hdd/1993 IBM H3171/1993_IBM_H3171_3.5_SPINDLE_RUNNING.wav", + &hdd_samples); + + if (hdd_spindle_sound_buffer) { + pclog("HDD Audio: Loaded spindle sound, %d frames\n", hdd_samples); + } else { + pclog("HDD Audio: Failed to load spindle sound!\n"); + } + + hdd_seek_sound_buffer = sound_load_wav( + "assets/sounds/hdd/1993 IBM H3171/1993_IBM_H3171_3.5_SEEK_1TRACK.wav", + &hdd_seek_samples); + + if (hdd_seek_sound_buffer) { + pclog("HDD Audio: Loaded seek sound, %d frames (%.1f ms)\n", + hdd_seek_samples, (float)hdd_seek_samples / 48.0f); + } else { + pclog("HDD Audio: Failed to load seek sound!\n"); + } + + for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { + hdd_seek_voices[i].active = 0; + hdd_seek_voices[i].position = 0; + hdd_seek_voices[i].volume = 1.0f; + } + + hdd_audio_mutex = thread_create_mutex(); + + sound_hdd_thread_init(); +} + +void +hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder) +{ + uint32_t cylinder_diff = abs((int) hdd->cur_cylinder - (int) new_cylinder); + + if (cylinder_diff == 0) + return; + + pclog("HDD Audio Seek: cur_cyl=%u -> new_cyl=%u, diff=%u, speed_preset=%d, cyl_switch_usec=%.1f\n", + hdd->cur_cylinder, new_cylinder, cylinder_diff, hdd->speed_preset, + hdd->cyl_switch_usec); + + if (hdd->speed_preset == 0) + return; + + if (!hdd_seek_sound_buffer || hdd_seek_samples == 0) { + pclog("HDD Audio Seek: No seek sound buffer loaded!\n"); + return; + } + + int min_seek_spacing = 0; + if (hdd->cyl_switch_usec > 0) + min_seek_spacing = (int)(hdd->cyl_switch_usec * 48000.0 / 1000000.0); + + thread_wait_mutex(hdd_audio_mutex); + + for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { + if (hdd_seek_voices[i].active) { + int pos = hdd_seek_voices[i].position; + if (pos >= 0 && pos < min_seek_spacing) { + thread_release_mutex(hdd_audio_mutex); + return; + } + } + } + + int active_count = 0; + + for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { + if (!hdd_seek_voices[i].active) { + hdd_seek_voices[i].active = 1; + hdd_seek_voices[i].position = 0; + hdd_seek_voices[i].volume = 0.5f; + pclog("HDD Audio Seek: Using free voice %d\n", i); + thread_release_mutex(hdd_audio_mutex); + return; + } + active_count++; + } + + pclog("HDD Audio Seek: All %d voices active, skipping seek\n", active_count); + + thread_release_mutex(hdd_audio_mutex); +} + +void +hdd_audio_callback(int16_t *buffer, int length) +{ + static int spindle_pos = 0; + static int debug_counter = 0; + const float spindle_volume = 0.2f; + const float seek_volume = 0.5f; + int frames_in_buffer = length / 2; + + if (sound_is_float) { + float *float_buffer = (float *) buffer; + + if (hdd_spindle_sound_buffer && hdd_samples > 0) { + for (int i = 0; i < frames_in_buffer; i++) { + float left_sample = (float) hdd_spindle_sound_buffer[spindle_pos * 2] / 131072.0f * spindle_volume; + float right_sample = (float) hdd_spindle_sound_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume; + float_buffer[i * 2] = left_sample; + float_buffer[i * 2 + 1] = right_sample; + + spindle_pos++; + if (spindle_pos >= hdd_samples) { + spindle_pos = 0; + } + } + } else { + for (int i = 0; i < length; i++) { + float_buffer[i] = 0.0f; + } + } + + if (hdd_seek_sound_buffer && hdd_seek_samples > 0 && hdd_audio_mutex) { + thread_wait_mutex(hdd_audio_mutex); + + for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { + if (!hdd_seek_voices[v].active) + continue; + + float voice_vol = hdd_seek_voices[v].volume; + int pos = hdd_seek_voices[v].position; + if (pos < 0) pos = 0; + + for (int i = 0; i < frames_in_buffer && pos < hdd_seek_samples; i++, pos++) { + float seek_left = (float) hdd_seek_sound_buffer[pos * 2] / 131072.0f * seek_volume * voice_vol; + float seek_right = (float) hdd_seek_sound_buffer[pos * 2 + 1] / 131072.0f * seek_volume * voice_vol; + + float_buffer[i * 2] += seek_left; + float_buffer[i * 2 + 1] += seek_right; + } + + if (pos >= hdd_seek_samples) { + hdd_seek_voices[v].active = 0; + hdd_seek_voices[v].position = 0; + } else { + hdd_seek_voices[v].position = pos; + } + } + + thread_release_mutex(hdd_audio_mutex); + } + + if (debug_counter++ % 100 == 0) { + pclog("HDD Audio: float_buffer[0]=%.6f, float_buffer[1]=%.6f, spindle_pos=%d, frames=%d\n", + float_buffer[0], float_buffer[1], spindle_pos, frames_in_buffer); + } + } else { + if (hdd_spindle_sound_buffer && hdd_samples > 0) { + for (int i = 0; i < frames_in_buffer; i++) { + buffer[i * 2] = hdd_spindle_sound_buffer[spindle_pos * 2]; + buffer[i * 2 + 1] = hdd_spindle_sound_buffer[spindle_pos * 2 + 1]; + + spindle_pos++; + if (spindle_pos >= hdd_samples) { + spindle_pos = 0; + } + } + } else { + for (int i = 0; i < length; i++) { + buffer[i] = 0; + } + } + + if (hdd_seek_sound_buffer && hdd_seek_samples > 0 && hdd_audio_mutex) { + thread_wait_mutex(hdd_audio_mutex); + + for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { + if (!hdd_seek_voices[v].active) + continue; + + int pos = hdd_seek_voices[v].position; + if (pos < 0) pos = 0; + + for (int i = 0; i < frames_in_buffer && pos < hdd_seek_samples; i++, pos++) { + int32_t left = buffer[i * 2] + hdd_seek_sound_buffer[pos * 2] / 2; + int32_t right = buffer[i * 2 + 1] + hdd_seek_sound_buffer[pos * 2 + 1] / 2; + + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + } + + if (pos >= hdd_seek_samples) { + hdd_seek_voices[v].active = 0; + hdd_seek_voices[v].position = 0; + } else { + hdd_seek_voices[v].position = pos; + } + } + + thread_release_mutex(hdd_audio_mutex); + } + } +} \ No newline at end of file diff --git a/src/floppy/fdd_audio.c b/src/floppy/fdd_audio.c index 8cbebf209..62e1881ef 100644 --- a/src/floppy/fdd_audio.c +++ b/src/floppy/fdd_audio.c @@ -31,6 +31,7 @@ #include <86box/plat.h> #include <86box/path.h> #include <86box/ini.h> +#include <86box/sound_util.h> #ifndef DISABLE_FDD_AUDIO @@ -53,9 +54,6 @@ static multi_seek_state_t seek_state[FDD_NUM][MAX_CONCURRENT_SEEKS] = {}; extern uint64_t motoron[FDD_NUM]; extern char exe_path[2048]; -/* Forward declaration */ -static int16_t *load_wav(const char *filename, int *sample_count); - extern uint8_t *rom; extern uint32_t biosmask; extern uint32_t biosaddr; @@ -412,7 +410,7 @@ load_profile_samples(int profile_id) if (samples->spindlemotor_start.buffer == NULL && config->spindlemotor_start.filename[0]) { strcpy(samples->spindlemotor_start.filename, config->spindlemotor_start.filename); samples->spindlemotor_start.volume = config->spindlemotor_start.volume; - samples->spindlemotor_start.buffer = load_wav(config->spindlemotor_start.filename, + samples->spindlemotor_start.buffer = sound_load_wav(config->spindlemotor_start.filename, &samples->spindlemotor_start.samples); if (samples->spindlemotor_start.buffer) { fdd_log(" Loaded spindlemotor_start: %s (%d samples, volume %.2f)\n", @@ -428,7 +426,7 @@ load_profile_samples(int profile_id) if (samples->spindlemotor_loop.buffer == NULL && config->spindlemotor_loop.filename[0]) { strcpy(samples->spindlemotor_loop.filename, config->spindlemotor_loop.filename); samples->spindlemotor_loop.volume = config->spindlemotor_loop.volume; - samples->spindlemotor_loop.buffer = load_wav(config->spindlemotor_loop.filename, + samples->spindlemotor_loop.buffer = sound_load_wav(config->spindlemotor_loop.filename, &samples->spindlemotor_loop.samples); if (samples->spindlemotor_loop.buffer) { fdd_log(" Loaded spindlemotor_loop: %s (%d samples, volume %.2f)\n", @@ -444,7 +442,7 @@ load_profile_samples(int profile_id) if (samples->spindlemotor_stop.buffer == NULL && config->spindlemotor_stop.filename[0]) { strcpy(samples->spindlemotor_stop.filename, config->spindlemotor_stop.filename); samples->spindlemotor_stop.volume = config->spindlemotor_stop.volume; - samples->spindlemotor_stop.buffer = load_wav(config->spindlemotor_stop.filename, + samples->spindlemotor_stop.buffer = sound_load_wav(config->spindlemotor_stop.filename, &samples->spindlemotor_stop.samples); if (samples->spindlemotor_stop.buffer) { fdd_log(" Loaded spindlemotor_stop: %s (%d samples, volume %.2f)\n", @@ -466,7 +464,7 @@ load_profile_samples(int profile_id) if (samples->seek_up[idx].buffer == NULL && config->seek_up[idx].filename[0]) { strcpy(samples->seek_up[idx].filename, config->seek_up[idx].filename); samples->seek_up[idx].volume = config->seek_up[idx].volume; - samples->seek_up[idx].buffer = load_wav(config->seek_up[idx].filename, + samples->seek_up[idx].buffer = sound_load_wav(config->seek_up[idx].filename, &samples->seek_up[idx].samples); if (samples->seek_up[idx].buffer) { fdd_log(" Loaded seek_up[%d]: %s (%d samples, volume %.2f)\n", @@ -479,7 +477,7 @@ load_profile_samples(int profile_id) if (samples->seek_down[idx].buffer == NULL && config->seek_down[idx].filename[0]) { strcpy(samples->seek_down[idx].filename, config->seek_down[idx].filename); samples->seek_down[idx].volume = config->seek_down[idx].volume; - samples->seek_down[idx].buffer = load_wav(config->seek_down[idx].filename, + samples->seek_down[idx].buffer = sound_load_wav(config->seek_down[idx].filename, &samples->seek_down[idx].samples); if (samples->seek_down[idx].buffer) { fdd_log(" Loaded seek_down[%d]: %s (%d samples, volume %.2f)\n", @@ -493,7 +491,7 @@ load_profile_samples(int profile_id) if (samples->post_seek_up[idx].buffer == NULL) { strcpy(samples->post_seek_up[idx].filename, config->post_seek_up[idx].filename); samples->post_seek_up[idx].volume = config->post_seek_up[idx].volume; - samples->post_seek_up[idx].buffer = load_wav(config->post_seek_up[idx].filename, + samples->post_seek_up[idx].buffer = sound_load_wav(config->post_seek_up[idx].filename, &samples->post_seek_up[idx].samples); if (samples->post_seek_up[idx].buffer) { fdd_log(" Loaded POST seek_up[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -507,7 +505,7 @@ load_profile_samples(int profile_id) if (samples->post_seek_down[idx].buffer == NULL) { strcpy(samples->post_seek_down[idx].filename, config->post_seek_down[idx].filename); samples->post_seek_down[idx].volume = config->post_seek_down[idx].volume; - samples->post_seek_down[idx].buffer = load_wav(config->post_seek_down[idx].filename, + samples->post_seek_down[idx].buffer = sound_load_wav(config->post_seek_down[idx].filename, &samples->post_seek_down[idx].samples); if (samples->post_seek_down[idx].buffer) { fdd_log(" Loaded POST seek_down[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -529,7 +527,7 @@ load_profile_samples(int profile_id) if (samples->bios_post_seek_up[vendor][idx].buffer == NULL) { strcpy(samples->bios_post_seek_up[vendor][idx].filename, config->bios_post_seek_up[vendor][idx].filename); samples->bios_post_seek_up[vendor][idx].volume = config->bios_post_seek_up[vendor][idx].volume; - samples->bios_post_seek_up[vendor][idx].buffer = load_wav(config->bios_post_seek_up[vendor][idx].filename, + samples->bios_post_seek_up[vendor][idx].buffer = sound_load_wav(config->bios_post_seek_up[vendor][idx].filename, &samples->bios_post_seek_up[vendor][idx].samples); if (samples->bios_post_seek_up[vendor][idx].buffer) { fdd_log(" Loaded %s POST seek_up[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -543,7 +541,7 @@ load_profile_samples(int profile_id) if (samples->bios_post_seek_down[vendor][idx].buffer == NULL) { strcpy(samples->bios_post_seek_down[vendor][idx].filename, config->bios_post_seek_down[vendor][idx].filename); samples->bios_post_seek_down[vendor][idx].volume = config->bios_post_seek_down[vendor][idx].volume; - samples->bios_post_seek_down[vendor][idx].buffer = load_wav(config->bios_post_seek_down[vendor][idx].filename, + samples->bios_post_seek_down[vendor][idx].buffer = sound_load_wav(config->bios_post_seek_down[vendor][idx].filename, &samples->bios_post_seek_down[vendor][idx].samples); if (samples->bios_post_seek_down[vendor][idx].buffer) { fdd_log(" Loaded %s POST seek_down[%d] (%d-track): %s (%d samples, volume %.2f)\n", @@ -947,92 +945,6 @@ fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) drive, slot, sample_to_use->samples); } -static int16_t * -load_wav(const char *filename, int *sample_count) -{ - if ((filename == NULL) || (strlen(filename) == 0)) - return NULL; - - if (strstr(filename, "..") != NULL) - return NULL; - - FILE *f = asset_fopen(filename, "rb"); - if (f == NULL) { - fdd_log("FDD Audio: Failed to open WAV file: %s\n", filename); - return NULL; - } - - wav_header_t hdr; - if (fread(&hdr, sizeof(hdr), 1, f) != 1) { - fdd_log("FDD Audio: Failed to read WAV header from: %s\n", filename); - fclose(f); - return NULL; - } - - if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) { - fdd_log("FDD Audio: Invalid WAV format in file: %s\n", filename); - fclose(f); - return NULL; - } - - /* Accept both mono and stereo, 16-bit PCM */ - if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || (hdr.num_channels != 1 && hdr.num_channels != 2)) { - fdd_log("FDD Audio: Unsupported WAV format in %s (format: %d, bits: %d, channels: %d)\n", - filename, hdr.audio_format, hdr.bits_per_sample, hdr.num_channels); - fclose(f); - return NULL; - } - - int input_samples = hdr.data_size / 2; /* 2 bytes per sample */ - int16_t *input_data = malloc(hdr.data_size); - if (!input_data) { - fdd_log("FDD Audio: Failed to allocate memory for WAV data: %s\n", filename); - fclose(f); - return NULL; - } - - if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) { - fdd_log("FDD Audio: Failed to read WAV data from: %s\n", filename); - free(input_data); - fclose(f); - return NULL; - } - fclose(f); - - int16_t *output_data; - int output_samples; - - if (hdr.num_channels == 1) { - /* Convert mono to stereo */ - output_samples = input_samples; /* Number of stereo sample pairs */ - output_data = malloc(input_samples * 2 * sizeof(int16_t)); /* Allocate for stereo */ - if (!output_data) { - fdd_log("FDD Audio: Failed to allocate stereo conversion buffer for: %s\n", filename); - free(input_data); - return NULL; - } - - /* Convert mono to stereo by duplicating each sample */ - for (int i = 0; i < input_samples; i++) { - output_data[i * 2] = input_data[i]; /* Left channel */ - output_data[i * 2 + 1] = input_data[i]; /* Right channel */ - } - - free(input_data); - fdd_log("FDD Audio: Loaded %s (mono->stereo, %d samples)\n", filename, output_samples); - } else { - /* Already stereo */ - output_data = input_data; - output_samples = input_samples / 2; /* Number of stereo sample pairs */ - fdd_log("FDD Audio: Loaded %s (stereo, %d samples)\n", filename, output_samples); - } - - if (sample_count) - *sample_count = output_samples; - - return output_data; -} - void fdd_audio_callback(int16_t *buffer, int length) { diff --git a/src/include/86box/fdd_audio.h b/src/include/86box/fdd_audio.h index 433aeaff8..ac6f225be 100644 --- a/src/include/86box/fdd_audio.h +++ b/src/include/86box/fdd_audio.h @@ -71,23 +71,6 @@ typedef enum { MOTOR_STATE_STOPPING } motor_state_t; -/* WAV header structure */ -typedef struct { - char riff[4]; - uint32_t size; - char wave[4]; - char fmt[4]; - uint32_t fmt_size; - uint16_t audio_format; - uint16_t num_channels; - uint32_t sample_rate; - uint32_t byte_rate; - uint16_t block_align; - uint16_t bits_per_sample; - char data[4]; - uint32_t data_size; -} wav_header_t; - /* Audio sample structure */ typedef struct { char filename[512]; diff --git a/src/include/86box/hdd_audio.h b/src/include/86box/hdd_audio.h new file mode 100644 index 000000000..1e6d7b103 --- /dev/null +++ b/src/include/86box/hdd_audio.h @@ -0,0 +1,6 @@ +#include +#include <86box/hdd.h> + +extern void hdd_audio_init(void); +extern void hdd_audio_callback(int16_t *buffer, int length); +extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder); \ No newline at end of file diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index e35969ead..f3cbaeea1 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -103,6 +103,9 @@ extern void sound_cd_thread_reset(void); extern void sound_fdd_thread_init(void); extern void sound_fdd_thread_end(void); +extern void sound_hdd_thread_init(void); +extern void sound_hdd_thread_end(void); + extern void closeal(void); extern void inital(void); extern void givealbuffer(const void *buf); @@ -110,6 +113,7 @@ extern void givealbuffer_music(const void *buf); extern void givealbuffer_wt(const void *buf); extern void givealbuffer_cd(const void *buf); extern void givealbuffer_fdd(const void *buf, const uint32_t size); +extern void givealbuffer_hdd(const void *buf, const uint32_t size); #define sb_vibra16c_onboard_relocate_base sb_vibra16s_onboard_relocate_base #define sb_vibra16cl_onboard_relocate_base sb_vibra16s_onboard_relocate_base diff --git a/src/include/86box/sound_util.h b/src/include/86box/sound_util.h new file mode 100644 index 000000000..95e95a360 --- /dev/null +++ b/src/include/86box/sound_util.h @@ -0,0 +1,28 @@ +#ifndef SOUND_UTIL_H +#define SOUND_UTIL_H + +#include + +/* WAV file header structure */ +typedef struct wav_header_t { + char riff[4]; + uint32_t file_size; + char wave[4]; + char fmt[4]; + uint32_t fmt_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char data[4]; + uint32_t data_size; +} wav_header_t; + +/* Load a WAV file and return stereo 16-bit samples + * Returns allocated buffer (caller must free) or NULL on error + * sample_count receives the number of stereo sample pairs */ +int16_t *sound_load_wav(const char *filename, int *sample_count); + +#endif /* SOUND_UTIL_H */ \ No newline at end of file diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt index 2c039fa92..3f6b1ef62 100644 --- a/src/sound/CMakeLists.txt +++ b/src/sound/CMakeLists.txt @@ -54,6 +54,7 @@ add_library(snd OBJECT snd_opl_esfm.c snd_ymf701.c snd_ymf71x.c + sound_util.c ) # TODO: Should platform-specific audio driver be here? diff --git a/src/sound/audio4.c b/src/sound/audio4.c index 060e574e6..25ff5b1c2 100644 --- a/src/sound/audio4.c +++ b/src/sound/audio4.c @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include <86box/86box.h> @@ -37,17 +37,17 @@ #define I_WT 2 #define I_CD 3 #define I_FDD 4 -#define I_MIDI 5 +#define I_HDD 5 +#define I_MIDI 6 -static int audio[6] = {-1, -1, -1, -1, -1, -1}; +static int audio[7] = {-1, -1, -1, -1, -1, -1, -1}; #ifdef USE_NEW_API -static struct audio_swpar info[5]; +static struct audio_swpar info[7]; #else -static audio_info_t info[6]; +static audio_info_t info[7]; #endif -static int freqs[6] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0}; - +static int freqs[7] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, SOUND_FREQ, 0}; void closeal(void) { @@ -173,6 +173,12 @@ givealbuffer_fdd(const void *buf, const uint32_t size) givealbuffer_common(buf, I_FDD, (int) size); } +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_HDD, (int) size); +} + void givealbuffer_midi(const void *buf, const uint32_t size) { diff --git a/src/sound/openal.c b/src/sound/openal.c index d163150af..598709de9 100644 --- a/src/sound/openal.c +++ b/src/sound/openal.c @@ -40,15 +40,17 @@ #define I_WT 2 #define I_CD 3 #define I_FDD 4 -#define I_MIDI 5 +#define I_HDD 5 +#define I_MIDI 6 ALuint buffers[4]; /* front and back buffers */ ALuint buffers_music[4]; /* front and back buffers */ ALuint buffers_wt[4]; /* front and back buffers */ ALuint buffers_cd[4]; /* front and back buffers */ ALuint buffers_fdd[4]; /* front and back buffers */ +ALuint buffers_hdd[4]; /* front and back buffers */ ALuint buffers_midi[4]; /* front and back buffers */ -static ALuint source[6]; /* audio source - CHANGED FROM 5 TO 6 */ +static ALuint source[7]; /* audio sources */ static int midi_freq = 44100; static int midi_buf_size = 4410; @@ -105,9 +107,10 @@ closeal(void) alSourceStopv(sources, source); alDeleteSources(sources, source); - if (sources >= 6) + if (sources >= 7) alDeleteBuffers(4, buffers_midi); alDeleteBuffers(4, buffers_fdd); + alDeleteBuffers(4, buffers_hdd); alDeleteBuffers(4, buffers_cd); alDeleteBuffers(4, buffers_music); alDeleteBuffers(4, buffers); @@ -126,12 +129,14 @@ inital(void) float *cd_buf = NULL; float *midi_buf = NULL; float *fdd_buf = NULL; + float *hdd_buf = NULL; int16_t *buf_int16 = NULL; int16_t *music_buf_int16 = NULL; int16_t *wt_buf_int16 = NULL; int16_t *cd_buf_int16 = NULL; int16_t *midi_buf_int16 = NULL; int16_t *fdd_buf_int16 = NULL; + int16_t *hdd_buf_int16 = NULL; int init_midi = 0; @@ -146,13 +151,14 @@ inital(void) init_midi = 1; /* If the device is neither none, nor system MIDI, initialize the MIDI buffer and source, otherwise, do not. */ - sources = 5 + !!init_midi; + sources = 6 + !!init_midi; if (sound_is_float) { buf = (float *) calloc((BUFLEN << 1), sizeof(float)); music_buf = (float *) calloc((MUSICBUFLEN << 1), sizeof(float)); wt_buf = (float *) calloc((WTBUFLEN << 1), sizeof(float)); cd_buf = (float *) calloc((CD_BUFLEN << 1), sizeof(float)); fdd_buf = (float *) calloc((BUFLEN << 1), sizeof(float)); + hdd_buf = (float *) calloc((BUFLEN << 1), sizeof(float)); if (init_midi) midi_buf = (float *) calloc(midi_buf_size, sizeof(float)); } else { @@ -161,6 +167,7 @@ inital(void) wt_buf_int16 = (int16_t *) calloc((WTBUFLEN << 1), sizeof(int16_t)); cd_buf_int16 = (int16_t *) calloc((CD_BUFLEN << 1), sizeof(int16_t)); fdd_buf_int16 = (int16_t *) calloc((BUFLEN << 1), sizeof(int16_t)); + hdd_buf_int16 = (int16_t *) calloc((BUFLEN << 1), sizeof(int16_t)); if (init_midi) midi_buf_int16 = (int16_t *) calloc(midi_buf_size, sizeof(int16_t)); } @@ -168,18 +175,17 @@ inital(void) alGenBuffers(4, buffers); alGenBuffers(4, buffers_cd); alGenBuffers(4, buffers_fdd); + alGenBuffers(4, buffers_hdd); alGenBuffers(4, buffers_music); alGenBuffers(4, buffers_wt); if (init_midi) alGenBuffers(4, buffers_midi); - // Create sources: 0=main, 1=music, 2=wt, 3=cd, 4=fdd, 5=midi(optional) - alGenSources(sources, source); - + // Create sources: 0=main, 1=music, 2=wt, 3=cd, 4=fdd, 5=hdd, 6=midi(optional) if (init_midi) - alGenSources(5, source); + alGenSources(7, source); else - alGenSources(4, source); + alGenSources(6, source); alSource3f(source[I_NORMAL], AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(source[I_NORMAL], AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -210,7 +216,13 @@ inital(void) alSource3f(source[I_FDD], AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSourcef(source[I_FDD], AL_ROLLOFF_FACTOR, 0.0f); alSourcei(source[I_FDD], AL_SOURCE_RELATIVE, AL_TRUE); - + + alSource3f(source[I_HDD], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(source[I_HDD], AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(source[I_HDD], AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSourcef(source[I_HDD], AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source[I_HDD], AL_SOURCE_RELATIVE, AL_TRUE); + if (init_midi) { alSource3f(source[I_MIDI], AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(source[I_MIDI], AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -225,6 +237,7 @@ inital(void) memset(music_buf, 0, MUSICBUFLEN * 2 * sizeof(float)); memset(wt_buf, 0, WTBUFLEN * 2 * sizeof(float)); memset(fdd_buf, 0, BUFLEN * 2 * sizeof(float)); + memset(hdd_buf, 0, BUFLEN * 2 * sizeof(float)); if (init_midi) memset(midi_buf, 0, midi_buf_size * sizeof(float)); } else { @@ -233,6 +246,7 @@ inital(void) memset(music_buf_int16, 0, MUSICBUFLEN * 2 * sizeof(int16_t)); memset(wt_buf_int16, 0, WTBUFLEN * 2 * sizeof(int16_t)); memset(fdd_buf_int16, 0, BUFLEN * 2 * sizeof(int16_t)); + memset(hdd_buf_int16, 0, BUFLEN * 2 * sizeof(int16_t)); if (init_midi) memset(midi_buf_int16, 0, midi_buf_size * sizeof(int16_t)); } @@ -244,6 +258,7 @@ inital(void) alBufferData(buffers_wt[c], AL_FORMAT_STEREO_FLOAT32, wt_buf, WTBUFLEN * 2 * sizeof(float), WT_FREQ); alBufferData(buffers_cd[c], AL_FORMAT_STEREO_FLOAT32, cd_buf, CD_BUFLEN * 2 * sizeof(float), CD_FREQ); alBufferData(buffers_fdd[c], AL_FORMAT_STEREO_FLOAT32, fdd_buf, BUFLEN * 2 * sizeof(float), FREQ); + alBufferData(buffers_hdd[c], AL_FORMAT_STEREO_FLOAT32, hdd_buf, BUFLEN * 2 * sizeof(float), FREQ); if (init_midi) alBufferData(buffers_midi[c], AL_FORMAT_STEREO_FLOAT32, midi_buf, midi_buf_size * (int) sizeof(float), midi_freq); } else { @@ -252,6 +267,7 @@ inital(void) alBufferData(buffers_wt[c], AL_FORMAT_STEREO16, wt_buf_int16, WTBUFLEN * 2 * sizeof(int16_t), WT_FREQ); alBufferData(buffers_cd[c], AL_FORMAT_STEREO16, cd_buf_int16, CD_BUFLEN * 2 * sizeof(int16_t), CD_FREQ); alBufferData(buffers_fdd[c], AL_FORMAT_STEREO16, fdd_buf_int16, BUFLEN * 2 * sizeof(int16_t), FREQ); + alBufferData(buffers_hdd[c], AL_FORMAT_STEREO16, hdd_buf_int16, BUFLEN * 2 * sizeof(int16_t), FREQ); if (init_midi) alBufferData(buffers_midi[c], AL_FORMAT_STEREO16, midi_buf_int16, midi_buf_size * (int) sizeof(int16_t), midi_freq); } @@ -262,6 +278,7 @@ inital(void) alSourceQueueBuffers(source[I_WT], 4, buffers_wt); alSourceQueueBuffers(source[I_CD], 4, buffers_cd); alSourceQueueBuffers(source[I_FDD], 4, buffers_fdd); + alSourceQueueBuffers(source[I_HDD], 4, buffers_hdd); if (init_midi) alSourceQueueBuffers(source[I_MIDI], 4, buffers_midi); alSourcePlay(source[I_NORMAL]); @@ -269,6 +286,7 @@ inital(void) alSourcePlay(source[I_WT]); alSourcePlay(source[I_CD]); alSourcePlay(source[I_FDD]); + alSourcePlay(source[I_HDD]); if (init_midi) alSourcePlay(source[I_MIDI]); @@ -280,6 +298,7 @@ inital(void) free(music_buf); free(buf); free(fdd_buf); + free(hdd_buf); } else { if (init_midi) free(midi_buf_int16); @@ -288,6 +307,7 @@ inital(void) free(music_buf_int16); free(buf_int16); free(fdd_buf_int16); + free(hdd_buf_int16); } initialized = 1; @@ -328,35 +348,41 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size, const in void givealbuffer(const void *buf) { - givealbuffer_common(buf, 0, BUFLEN << 1, FREQ); + givealbuffer_common(buf, I_NORMAL, BUFLEN << 1, FREQ); } void givealbuffer_music(const void *buf) { - givealbuffer_common(buf, 1, MUSICBUFLEN << 1, MUSIC_FREQ); + givealbuffer_common(buf, I_MUSIC, MUSICBUFLEN << 1, MUSIC_FREQ); } void givealbuffer_wt(const void *buf) { - givealbuffer_common(buf, 2, WTBUFLEN << 1, WT_FREQ); + givealbuffer_common(buf, I_WT, WTBUFLEN << 1, WT_FREQ); } void givealbuffer_cd(const void *buf) { - givealbuffer_common(buf, 3, CD_BUFLEN << 1, CD_FREQ); + givealbuffer_common(buf, I_CD, CD_BUFLEN << 1, CD_FREQ); } void givealbuffer_midi(const void *buf, const uint32_t size) { - givealbuffer_common(buf, 5, (int) size, midi_freq); + givealbuffer_common(buf, I_MIDI, (int) size, midi_freq); } void givealbuffer_fdd(const void *buf, const uint32_t size) { - givealbuffer_common(buf, 4, (int) size, FREQ); + givealbuffer_common(buf, I_FDD, (int) size, FREQ); +} + +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_HDD, (int) size, FREQ); } \ No newline at end of file diff --git a/src/sound/sndio.c b/src/sound/sndio.c index 6363163a2..d572652ae 100644 --- a/src/sound/sndio.c +++ b/src/sound/sndio.c @@ -30,11 +30,11 @@ #define I_CD 3 #define I_MIDI 4 #define I_FDD 5 +#define I_HDD 6 -static struct sio_hdl* audio[6] = {NULL, NULL, NULL, NULL, NULL, NULL}; -static struct sio_par info[6]; -static int freqs[6] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0 }; - +static struct sio_hdl* audio[7] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL}; +static struct sio_par info[7]; +static int freqs[7] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, SOUND_FREQ, 0 }; void closeal(void) { @@ -153,6 +153,12 @@ givealbuffer_fdd(const void *buf, const uint32_t size) { givealbuffer_common(buf, I_FDD, (int) size); } + +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_HDD, (int) size); +} void al_set_midi(const int freq, UNUSED(const int buf_size)) diff --git a/src/sound/sound.c b/src/sound/sound.c index a5a7b07ed..cdb3ec51c 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -37,6 +37,7 @@ #include <86box/snd_mpu401.h> #include <86box/sound.h> #include <86box/fdd_audio.h> +#include <86box/hdd_audio.h> typedef struct { const device_t *device; @@ -96,6 +97,12 @@ static event_t *sound_fdd_start_event; static volatile int fddaudioon = 0; static int fdd_thread_enable = 0; +static thread_t *sound_hdd_thread_h; +static event_t *sound_hdd_event; +static event_t *sound_hdd_start_event; +static volatile int hddaudioon = 0; +static int hdd_thread_enable = 0; + static void (*filter_cd_audio)(int channel, double *buffer, void *priv) = NULL; static void *filter_cd_audio_p = NULL; @@ -614,6 +621,10 @@ sound_poll(UNUSED(void *priv)) if (fdd_thread_enable) { thread_set_event(sound_fdd_event); } + + if (hdd_thread_enable) { + thread_set_event(sound_hdd_event); + } sound_pos_global = 0; } } @@ -857,3 +868,59 @@ sound_fdd_thread_end(void) } } } + +static void +sound_hdd_thread(UNUSED(void *param)) +{ + thread_set_event(sound_hdd_start_event); + while (hddaudioon) { + thread_wait_event(sound_hdd_event, -1); + thread_reset_event(sound_hdd_event); + + if (!hddaudioon) + break; + + static float hdd_float_buffer[SOUNDBUFLEN * 2]; + memset(hdd_float_buffer, 0, sizeof(hdd_float_buffer)); + hdd_audio_callback((int16_t*)hdd_float_buffer, SOUNDBUFLEN * 2); + givealbuffer_hdd(hdd_float_buffer, SOUNDBUFLEN * 2); + } +} + +void +sound_hdd_thread_init(void) +{ + if (!hddaudioon) { + hddaudioon = 1; + hdd_thread_enable = 1; + sound_hdd_start_event = thread_create_event(); + sound_hdd_event = thread_create_event(); + sound_hdd_thread_h = thread_create(sound_hdd_thread, NULL); + + thread_wait_event(sound_hdd_start_event, -1); + thread_reset_event(sound_hdd_start_event); + } +} + +void +sound_hdd_thread_end(void) +{ + if (hddaudioon) { + hddaudioon = 0; + hdd_thread_enable = 0; + thread_set_event(sound_hdd_event); + thread_wait(sound_hdd_thread_h); + + if (sound_hdd_event) { + thread_destroy_event(sound_hdd_event); + sound_hdd_event = NULL; + } + + sound_hdd_thread_h = NULL; + if (sound_hdd_start_event) { + thread_destroy_event(sound_hdd_start_event); + sound_hdd_start_event = NULL; + } + } +} + diff --git a/src/sound/sound_util.c b/src/sound/sound_util.c new file mode 100644 index 000000000..d6d3e7495 --- /dev/null +++ b/src/sound/sound_util.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +#include <86box/86box.h> +#include <86box/mem.h> +#include <86box/rom.h> +#include <86box/plat.h> +#include <86box/sound_util.h> + +int16_t * +sound_load_wav(const char *filename, int *sample_count) +{ + if ((filename == NULL) || (strlen(filename) == 0)) + return NULL; + + if (strstr(filename, "..") != NULL) + return NULL; + + FILE *f = asset_fopen(filename, "rb"); + if (f == NULL) + return NULL; + + wav_header_t hdr; + if (fread(&hdr, sizeof(hdr), 1, f) != 1) { + fclose(f); + return NULL; + } + + if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || + memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) { + fclose(f); + return NULL; + } + + /* Accept both mono and stereo, 16-bit PCM */ + if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || + (hdr.num_channels != 1 && hdr.num_channels != 2)) { + fclose(f); + return NULL; + } + + int input_samples = hdr.data_size / 2; + int16_t *input_data = malloc(hdr.data_size); + if (!input_data) { + fclose(f); + return NULL; + } + + if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) { + free(input_data); + fclose(f); + return NULL; + } + fclose(f); + + int16_t *output_data; + int output_samples; + + if (hdr.num_channels == 1) { + /* Convert mono to stereo */ + output_samples = input_samples; + output_data = malloc(input_samples * 2 * sizeof(int16_t)); + if (!output_data) { + free(input_data); + return NULL; + } + + for (int i = 0; i < input_samples; i++) { + output_data[i * 2] = input_data[i]; + output_data[i * 2 + 1] = input_data[i]; + } + + free(input_data); + } else { + output_data = input_data; + output_samples = input_samples / 2; + } + + if (sample_count) + *sample_count = output_samples; + + return output_data; +} \ No newline at end of file diff --git a/src/sound/xaudio2.c b/src/sound/xaudio2.c index 8596c2a49..7833af435 100644 --- a/src/sound/xaudio2.c +++ b/src/sound/xaudio2.c @@ -54,6 +54,7 @@ static IXAudio2SourceVoice *srcvoicewt = NULL; static IXAudio2SourceVoice *srcvoicemidi = NULL; static IXAudio2SourceVoice *srcvoicecd = NULL; static IXAudio2SourceVoice *srcvoicefdd = NULL; +static IXAudio2SourceVoice *srcvoicehdd = NULL; #define FREQ SOUND_FREQ #define BUFLEN SOUNDBUFLEN @@ -184,6 +185,7 @@ inital(void) fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; (void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicefdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL); + (void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicehdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL); (void) IXAudio2SourceVoice_SetVolume(srcvoice, 1, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoice, 0, XAUDIO2_COMMIT_NOW); @@ -191,6 +193,7 @@ inital(void) (void) IXAudio2SourceVoice_Start(srcvoicemusic, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicewt, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW); + (void) IXAudio2SourceVoice_Start(srcvoicehdd, 0, XAUDIO2_COMMIT_NOW); const char *mdn = midi_out_device_get_internal_name(midi_output_device_current); @@ -223,6 +226,8 @@ closeal(void) (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicecd); (void) IXAudio2SourceVoice_Stop(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicefdd); + (void) IXAudio2SourceVoice_Stop(srcvoicehdd, 0, XAUDIO2_COMMIT_NOW); + (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicehdd); if (srcvoicemidi) { (void) IXAudio2SourceVoice_Stop(srcvoicemidi, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicemidi); @@ -231,6 +236,7 @@ closeal(void) IXAudio2SourceVoice_DestroyVoice(srcvoicewt); IXAudio2SourceVoice_DestroyVoice(srcvoicecd); IXAudio2SourceVoice_DestroyVoice(srcvoicefdd); + IXAudio2SourceVoice_DestroyVoice(srcvoicehdd); IXAudio2SourceVoice_DestroyVoice(srcvoicemusic); IXAudio2SourceVoice_DestroyVoice(srcvoice); IXAudio2MasteringVoice_DestroyVoice(mastervoice); @@ -239,6 +245,7 @@ closeal(void) srcvoicecd = NULL; srcvoicemidi = NULL; srcvoicefdd = NULL; + srcvoicehdd = NULL; mastervoice = NULL; xaudio2 = NULL; @@ -312,6 +319,18 @@ givealbuffer_fdd(const void *buf, const uint32_t size) givealbuffer_common(buf, srcvoicefdd, size); } +void +givealbuffer_hdd(const void *buf, const uint32_t size) +{ + if (!initialized) + return; + + if (!srcvoicefdd) + return; + + givealbuffer_common(buf, srcvoicehdd, size); +} + void al_set_midi(const int freq, const int buf_size) { From b13e4c44b44ddb952b79d16b87d1625d138cc7e0 Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 08:59:43 +0200 Subject: [PATCH 2/7] HDD audio profile for settings, ui and using the selected profile --- src/86box.c | 6 + src/config.c | 19 ++ src/disk/hdd.c | 1 - src/disk/hdd_audio.c | 457 +++++++++++++++++++++++++++----- src/include/86box/hdd.h | 1 + src/include/86box/hdd_audio.h | 59 ++++- src/qt/qt_settingsharddisks.cpp | 47 +++- src/qt/qt_settingsharddisks.hpp | 1 + src/qt/qt_settingsharddisks.ui | 14 + 9 files changed, 534 insertions(+), 71 deletions(-) diff --git a/src/86box.c b/src/86box.c index b2311f0e2..3d9356462 100644 --- a/src/86box.c +++ b/src/86box.c @@ -83,6 +83,7 @@ #include <86box/fdc.h> #include <86box/fdc_ext.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> #include <86box/hdc.h> #include <86box/hdc_ide.h> #include <86box/scsi.h> @@ -1476,6 +1477,8 @@ pc_init_modules(void) fdd_audio_load_profiles(); fdd_audio_init(); } + + hdd_audio_init(); sound_init(); @@ -1719,6 +1722,9 @@ pc_reset_hard_init(void) fdd_reset(); + /* Reset HDD audio to pick up any profile changes */ + hdd_audio_reset(); + /* Reset and reconfigure the SCSI layer. */ scsi_card_init(); diff --git a/src/config.c b/src/config.c index d5deded9d..6f8d1d6e6 100644 --- a/src/config.c +++ b/src/config.c @@ -51,6 +51,7 @@ #include <86box/lpt.h> #include <86box/serial.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> #include <86box/hdc.h> #include <86box/hdc_ide.h> #include <86box/fdd.h> @@ -1247,6 +1248,8 @@ load_hard_disks(void) uint32_t board = 0; uint32_t dev = 0; + hdd_audio_load_profiles(); + memset(temp, '\0', sizeof(temp)); for (uint8_t c = 0; c < HDD_NUM; c++) { sprintf(temp, "hdd_%02i_parameters", c + 1); @@ -1315,6 +1318,11 @@ load_hard_disks(void) p = ini_section_get_string(cat, temp, tmp2); hdd[c].speed_preset = hdd_preset_get_from_internal_name(p); + /* Audio Profile */ + sprintf(temp, "hdd_%02i_audio", c + 1); + p = ini_section_get_string(cat, temp, "none"); + hdd[c].audio_profile = hdd_audio_get_profile_by_internal_name(p); + /* MFM/RLL */ sprintf(temp, "hdd_%02i_mfm_channel", c + 1); if (hdd[c].bus_type == HDD_BUS_MFM) @@ -3458,6 +3466,17 @@ save_hard_disks(void) ini_section_delete_var(cat, temp); else ini_section_set_string(cat, temp, hdd_preset_get_internal_name(hdd[c].speed_preset)); + + sprintf(temp, "hdd_%02i_audio", c + 1); + if (!hdd_is_valid(c) || hdd[c].audio_profile == 0) { + ini_section_delete_var(cat, temp); + } else { + const char *internal_name = hdd_audio_get_profile_internal_name(hdd[c].audio_profile); + if (internal_name && strcmp(internal_name, "none") != 0) + ini_section_set_string(cat, temp, internal_name); + else + ini_section_delete_var(cat, temp); + } } ini_delete_section_if_empty(config, cat); diff --git a/src/disk/hdd.c b/src/disk/hdd.c index f6d769b9e..cd34b66fa 100644 --- a/src/disk/hdd.c +++ b/src/disk/hdd.c @@ -39,7 +39,6 @@ hdd_init(void) { /* Clear all global data. */ memset(hdd, 0x00, sizeof(hdd)); - hdd_audio_init(); return 0; } diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c index 54419c244..bc3f9ed95 100644 --- a/src/disk/hdd_audio.c +++ b/src/disk/hdd_audio.c @@ -1,11 +1,33 @@ -#include +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Hard disk audio emulation. + * + * Authors: Toni Riikonen, + * + * Copyright 2026 Toni Riikonen. + */ + + #include #include #include +#include #include <86box/86box.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> #include <86box/sound.h> #include <86box/sound_util.h> #include <86box/thread.h> +#include <86box/plat.h> +#include <86box/path.h> +#include <86box/ini.h> +#include <86box/mem.h> +#include <86box/rom.h> /* Maximum number of simultaneous seek sounds */ #define HDD_MAX_SEEK_VOICES 8 @@ -16,36 +38,277 @@ typedef struct { float volume; } hdd_seek_voice_t; -static int16_t *hdd_spindle_sound_buffer = NULL; -static int16_t *hdd_seek_sound_buffer = NULL; -static int hdd_samples; -static int hdd_seek_samples; +/* Audio samples structure for a profile */ +typedef struct { + int16_t *spindle_start_buffer; + int spindle_start_samples; + int16_t *spindle_loop_buffer; + int spindle_loop_samples; + int16_t *spindle_stop_buffer; + int spindle_stop_samples; + int16_t *seek_buffer; + int seek_samples; + float spindle_volume; + float seek_volume; + int loaded; +} hdd_audio_samples_t; + +/* Global audio profile configurations */ +static hdd_audio_profile_config_t audio_profiles[HDD_AUDIO_PROFILE_MAX]; +static int audio_profile_count = 0; + +/* Per-profile loaded samples */ +static hdd_audio_samples_t profile_samples[HDD_AUDIO_PROFILE_MAX]; + +/* Active profile for audio playback (first HDD with valid profile) */ +static int active_audio_profile = 0; static hdd_seek_voice_t hdd_seek_voices[HDD_MAX_SEEK_VOICES]; static mutex_t *hdd_audio_mutex = NULL; +/* Load audio profiles from configuration file */ +void +hdd_audio_load_profiles(void) +{ + ini_t profiles_ini; + char cfg_fn[1024] = { 0 }; + + int ret = asset_getfile("assets/sounds/hdd/hdd_audio_profiles.cfg", cfg_fn, 1024); + if (!ret) { + pclog("HDD Audio: Could not find hdd_audio_profiles.cfg\n"); + return; + } + + profiles_ini = ini_read_ex(cfg_fn, 1); + if (profiles_ini == NULL) { + pclog("HDD Audio: Failed to load hdd_audio_profiles.cfg\n"); + return; + } + + audio_profile_count = 0; + + /* Load profiles by trying known profile section names */ + for (int i = 0; i < HDD_AUDIO_PROFILE_MAX && audio_profile_count < HDD_AUDIO_PROFILE_MAX; i++) { + char section_name[64]; + sprintf(section_name, "Profile \"%d\"", i); + + ini_section_t cat = ini_find_section(profiles_ini, section_name); + if (cat == NULL) + continue; + + hdd_audio_profile_config_t *config = &audio_profiles[audio_profile_count]; + memset(config, 0, sizeof(hdd_audio_profile_config_t)); + + config->id = ini_section_get_int(cat, "id", i); + + const char *name = ini_section_get_string(cat, "name", "Unknown"); + strncpy(config->name, name, sizeof(config->name) - 1); + + const char *internal_name = ini_section_get_string(cat, "internal_name", "unknown"); + strncpy(config->internal_name, internal_name, sizeof(config->internal_name) - 1); + + /* Load spindle motor sample files */ + const char *file = ini_section_get_string(cat, "spindlemotor_start_file", ""); + strncpy(config->spindlemotor_start.filename, file, sizeof(config->spindlemotor_start.filename) - 1); + config->spindlemotor_start.volume = (float) ini_section_get_double(cat, "spindlemotor_start_volume", 1.0); + + file = ini_section_get_string(cat, "spindlemotor_loop_file", ""); + strncpy(config->spindlemotor_loop.filename, file, sizeof(config->spindlemotor_loop.filename) - 1); + config->spindlemotor_loop.volume = (float) ini_section_get_double(cat, "spindlemotor_loop_volume", 1.0); + + file = ini_section_get_string(cat, "spindlemotor_stop_file", ""); + strncpy(config->spindlemotor_stop.filename, file, sizeof(config->spindlemotor_stop.filename) - 1); + config->spindlemotor_stop.volume = (float) ini_section_get_double(cat, "spindlemotor_stop_volume", 1.0); + + /* Load seek sample file */ + file = ini_section_get_string(cat, "seek_track_file", ""); + strncpy(config->seek_track.filename, file, sizeof(config->seek_track.filename) - 1); + config->seek_track.volume = (float) ini_section_get_double(cat, "seek_track_volume", 1.0); + + pclog("HDD Audio: Loaded profile %d: %s (%s)\n", + audio_profile_count, config->name, config->internal_name); + + audio_profile_count++; + } + + ini_close(profiles_ini); + + pclog("HDD Audio: Loaded %d audio profiles\n", audio_profile_count); +} + +/* Public API functions */ +int +hdd_audio_get_profile_count(void) +{ + return audio_profile_count; +} + +const hdd_audio_profile_config_t * +hdd_audio_get_profile(int id) +{ + if (id < 0 || id >= audio_profile_count) + return NULL; + return &audio_profiles[id]; +} + +const char * +hdd_audio_get_profile_name(int id) +{ + if (id < 0 || id >= audio_profile_count) + return NULL; + return audio_profiles[id].name; +} + +const char * +hdd_audio_get_profile_internal_name(int id) +{ + if (id < 0 || id >= audio_profile_count) + return NULL; + return audio_profiles[id].internal_name; +} + +int +hdd_audio_get_profile_by_internal_name(const char *internal_name) +{ + if (!internal_name) + return 0; + + for (int i = 0; i < audio_profile_count; i++) { + if (strcmp(audio_profiles[i].internal_name, internal_name) == 0) + return i; + } + return 0; +} + +void +hdd_audio_close(void) +{ + /* Free all loaded profile samples */ + for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) { + if (profile_samples[i].spindle_start_buffer) { + free(profile_samples[i].spindle_start_buffer); + profile_samples[i].spindle_start_buffer = NULL; + } + if (profile_samples[i].spindle_loop_buffer) { + free(profile_samples[i].spindle_loop_buffer); + profile_samples[i].spindle_loop_buffer = NULL; + } + if (profile_samples[i].spindle_stop_buffer) { + free(profile_samples[i].spindle_stop_buffer); + profile_samples[i].spindle_stop_buffer = NULL; + } + if (profile_samples[i].seek_buffer) { + free(profile_samples[i].seek_buffer); + profile_samples[i].seek_buffer = NULL; + } + profile_samples[i].loaded = 0; + } + + if (hdd_audio_mutex) { + thread_close_mutex(hdd_audio_mutex); + hdd_audio_mutex = NULL; + } +} + +/* Load samples for a specific profile */ +static void +hdd_audio_load_profile_samples(int profile_id) +{ + if (profile_id < 0 || profile_id >= audio_profile_count) + return; + + hdd_audio_profile_config_t *config = &audio_profiles[profile_id]; + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + + /* Already loaded? */ + if (samples->loaded) + return; + + /* Profile 0 is "None" - no audio */ + if (profile_id == 0 || strcmp(config->internal_name, "none") == 0) { + samples->loaded = 1; + return; + } + + pclog("HDD Audio: Loading samples for profile %d (%s)\n", profile_id, config->name); + + /* Load spindle loop (main running sound) */ + if (config->spindlemotor_loop.filename[0]) { + samples->spindle_loop_buffer = sound_load_wav( + config->spindlemotor_loop.filename, + &samples->spindle_loop_samples); + if (samples->spindle_loop_buffer) { + samples->spindle_volume = config->spindlemotor_loop.volume; + pclog("HDD Audio: Loaded spindle loop, %d frames\n", samples->spindle_loop_samples); + } else { + pclog("HDD Audio: Failed to load spindle loop: %s\n", config->spindlemotor_loop.filename); + } + } + + /* Load spindle start */ + if (config->spindlemotor_start.filename[0]) { + samples->spindle_start_buffer = sound_load_wav( + config->spindlemotor_start.filename, + &samples->spindle_start_samples); + if (samples->spindle_start_buffer) { + pclog("HDD Audio: Loaded spindle start, %d frames\n", samples->spindle_start_samples); + } + } + + /* Load spindle stop */ + if (config->spindlemotor_stop.filename[0]) { + samples->spindle_stop_buffer = sound_load_wav( + config->spindlemotor_stop.filename, + &samples->spindle_stop_samples); + if (samples->spindle_stop_buffer) { + pclog("HDD Audio: Loaded spindle stop, %d frames\n", samples->spindle_stop_samples); + } + } + + /* Load seek sound */ + if (config->seek_track.filename[0]) { + samples->seek_buffer = sound_load_wav( + config->seek_track.filename, + &samples->seek_samples); + if (samples->seek_buffer) { + samples->seek_volume = config->seek_track.volume; + pclog("HDD Audio: Loaded seek sound, %d frames (%.1f ms)\n", + samples->seek_samples, (float)samples->seek_samples / 48.0f); + } else { + pclog("HDD Audio: Failed to load seek sound: %s\n", config->seek_track.filename); + } + } + + samples->loaded = 1; +} + void hdd_audio_init(void) { - hdd_spindle_sound_buffer = sound_load_wav( - "assets/sounds/hdd/1993 IBM H3171/1993_IBM_H3171_3.5_SPINDLE_RUNNING.wav", - &hdd_samples); - - if (hdd_spindle_sound_buffer) { - pclog("HDD Audio: Loaded spindle sound, %d frames\n", hdd_samples); - } else { - pclog("HDD Audio: Failed to load spindle sound!\n"); + /* Initialize profile samples */ + memset(profile_samples, 0, sizeof(profile_samples)); + + pclog("HDD Audio Init: audio_profile_count=%d\n", audio_profile_count); + + /* Find first HDD with a valid audio profile and load its samples */ + active_audio_profile = 0; + for (int i = 0; i < HDD_NUM; i++) { + if (hdd[i].bus_type != HDD_BUS_DISABLED) { + pclog("HDD Audio Init: HDD %d bus_type=%d audio_profile=%d\n", + i, hdd[i].bus_type, hdd[i].audio_profile); + if (hdd[i].audio_profile > 0) { + active_audio_profile = hdd[i].audio_profile; + pclog("HDD Audio: Using profile %d from HDD %d\n", active_audio_profile, i); + break; + } + } } - - hdd_seek_sound_buffer = sound_load_wav( - "assets/sounds/hdd/1993 IBM H3171/1993_IBM_H3171_3.5_SEEK_1TRACK.wav", - &hdd_seek_samples); - - if (hdd_seek_sound_buffer) { - pclog("HDD Audio: Loaded seek sound, %d frames (%.1f ms)\n", - hdd_seek_samples, (float)hdd_seek_samples / 48.0f); - } else { - pclog("HDD Audio: Failed to load seek sound!\n"); + + pclog("HDD Audio Init: active_audio_profile=%d\n", active_audio_profile); + + /* Load samples for the active profile */ + if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { + hdd_audio_load_profile_samples(active_audio_profile); } for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { @@ -60,28 +323,87 @@ hdd_audio_init(void) } void -hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder) +hdd_audio_reset(void) { - uint32_t cylinder_diff = abs((int) hdd->cur_cylinder - (int) new_cylinder); + pclog("HDD Audio: Reset\n"); + + /* Free previously loaded samples (but keep profiles) */ + for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) { + if (profile_samples[i].spindle_start_buffer) { + free(profile_samples[i].spindle_start_buffer); + profile_samples[i].spindle_start_buffer = NULL; + } + if (profile_samples[i].spindle_loop_buffer) { + free(profile_samples[i].spindle_loop_buffer); + profile_samples[i].spindle_loop_buffer = NULL; + } + if (profile_samples[i].spindle_stop_buffer) { + free(profile_samples[i].spindle_stop_buffer); + profile_samples[i].spindle_stop_buffer = NULL; + } + if (profile_samples[i].seek_buffer) { + free(profile_samples[i].seek_buffer); + profile_samples[i].seek_buffer = NULL; + } + profile_samples[i].loaded = 0; + } + + /* Reset seek voices */ + for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { + hdd_seek_voices[i].active = 0; + hdd_seek_voices[i].position = 0; + hdd_seek_voices[i].volume = 1.0f; + } + + /* Find new active profile from current HDD configuration */ + active_audio_profile = 0; + for (int i = 0; i < HDD_NUM; i++) { + if (hdd[i].bus_type != HDD_BUS_DISABLED) { + pclog("HDD Audio Reset: HDD %d audio_profile=%d\n", i, hdd[i].audio_profile); + if (hdd[i].audio_profile > 0) { + active_audio_profile = hdd[i].audio_profile; + pclog("HDD Audio: Reset with profile %d from HDD %d\n", active_audio_profile, i); + break; + } + } + } + + /* Load samples for the active profile */ + if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { + hdd_audio_load_profile_samples(active_audio_profile); + } +} + +void +hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) +{ + uint32_t cylinder_diff = abs((int) hdd_drive->cur_cylinder - (int) new_cylinder); if (cylinder_diff == 0) return; - pclog("HDD Audio Seek: cur_cyl=%u -> new_cyl=%u, diff=%u, speed_preset=%d, cyl_switch_usec=%.1f\n", - hdd->cur_cylinder, new_cylinder, cylinder_diff, hdd->speed_preset, - hdd->cyl_switch_usec); - - if (hdd->speed_preset == 0) + /* Use the drive's audio profile, fallback to active profile */ + int profile_id = hdd_drive->audio_profile; + if (profile_id == 0) + profile_id = active_audio_profile; + + /* No audio profile selected */ + if (profile_id == 0 || profile_id >= audio_profile_count) return; - - if (!hdd_seek_sound_buffer || hdd_seek_samples == 0) { - pclog("HDD Audio Seek: No seek sound buffer loaded!\n"); + + /* Load samples if not already loaded */ + if (!profile_samples[profile_id].loaded) + hdd_audio_load_profile_samples(profile_id); + + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + + if (!samples->seek_buffer || samples->seek_samples == 0) { return; } int min_seek_spacing = 0; - if (hdd->cyl_switch_usec > 0) - min_seek_spacing = (int)(hdd->cyl_switch_usec * 48000.0 / 1000000.0); + if (hdd_drive->cyl_switch_usec > 0) + min_seek_spacing = (int)(hdd_drive->cyl_switch_usec * 48000.0 / 1000000.0); thread_wait_mutex(hdd_audio_mutex); @@ -95,22 +417,16 @@ hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder) } } - int active_count = 0; - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { if (!hdd_seek_voices[i].active) { hdd_seek_voices[i].active = 1; hdd_seek_voices[i].position = 0; - hdd_seek_voices[i].volume = 0.5f; - pclog("HDD Audio Seek: Using free voice %d\n", i); + hdd_seek_voices[i].volume = samples->seek_volume; thread_release_mutex(hdd_audio_mutex); return; } - active_count++; } - pclog("HDD Audio Seek: All %d voices active, skipping seek\n", active_count); - thread_release_mutex(hdd_audio_mutex); } @@ -118,23 +434,28 @@ void hdd_audio_callback(int16_t *buffer, int length) { static int spindle_pos = 0; - static int debug_counter = 0; - const float spindle_volume = 0.2f; - const float seek_volume = 0.5f; int frames_in_buffer = length / 2; + /* Get active profile samples */ + hdd_audio_samples_t *samples = NULL; + if (active_audio_profile > 0 && active_audio_profile < HDD_AUDIO_PROFILE_MAX) { + samples = &profile_samples[active_audio_profile]; + } + if (sound_is_float) { float *float_buffer = (float *) buffer; - if (hdd_spindle_sound_buffer && hdd_samples > 0) { + /* Spindle sound from profile */ + if (samples && samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { + float spindle_volume = samples->spindle_volume; for (int i = 0; i < frames_in_buffer; i++) { - float left_sample = (float) hdd_spindle_sound_buffer[spindle_pos * 2] / 131072.0f * spindle_volume; - float right_sample = (float) hdd_spindle_sound_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume; + float left_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2] / 131072.0f * spindle_volume; + float right_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume; float_buffer[i * 2] = left_sample; float_buffer[i * 2 + 1] = right_sample; spindle_pos++; - if (spindle_pos >= hdd_samples) { + if (spindle_pos >= samples->spindle_loop_samples) { spindle_pos = 0; } } @@ -144,7 +465,8 @@ hdd_audio_callback(int16_t *buffer, int length) } } - if (hdd_seek_sound_buffer && hdd_seek_samples > 0 && hdd_audio_mutex) { + /* Seek sounds from profile */ + if (samples && samples->seek_buffer && samples->seek_samples > 0 && hdd_audio_mutex) { thread_wait_mutex(hdd_audio_mutex); for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { @@ -155,15 +477,15 @@ hdd_audio_callback(int16_t *buffer, int length) int pos = hdd_seek_voices[v].position; if (pos < 0) pos = 0; - for (int i = 0; i < frames_in_buffer && pos < hdd_seek_samples; i++, pos++) { - float seek_left = (float) hdd_seek_sound_buffer[pos * 2] / 131072.0f * seek_volume * voice_vol; - float seek_right = (float) hdd_seek_sound_buffer[pos * 2 + 1] / 131072.0f * seek_volume * voice_vol; + for (int i = 0; i < frames_in_buffer && pos < samples->seek_samples; i++, pos++) { + float seek_left = (float) samples->seek_buffer[pos * 2] / 131072.0f * voice_vol; + float seek_right = (float) samples->seek_buffer[pos * 2 + 1] / 131072.0f * voice_vol; float_buffer[i * 2] += seek_left; float_buffer[i * 2 + 1] += seek_right; } - if (pos >= hdd_seek_samples) { + if (pos >= samples->seek_samples) { hdd_seek_voices[v].active = 0; hdd_seek_voices[v].position = 0; } else { @@ -173,19 +495,16 @@ hdd_audio_callback(int16_t *buffer, int length) thread_release_mutex(hdd_audio_mutex); } - - if (debug_counter++ % 100 == 0) { - pclog("HDD Audio: float_buffer[0]=%.6f, float_buffer[1]=%.6f, spindle_pos=%d, frames=%d\n", - float_buffer[0], float_buffer[1], spindle_pos, frames_in_buffer); - } } else { - if (hdd_spindle_sound_buffer && hdd_samples > 0) { + /* Spindle sound from profile */ + if (samples && samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { + float spindle_volume = samples->spindle_volume; for (int i = 0; i < frames_in_buffer; i++) { - buffer[i * 2] = hdd_spindle_sound_buffer[spindle_pos * 2]; - buffer[i * 2 + 1] = hdd_spindle_sound_buffer[spindle_pos * 2 + 1]; + buffer[i * 2] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2] * spindle_volume); + buffer[i * 2 + 1] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2 + 1] * spindle_volume); spindle_pos++; - if (spindle_pos >= hdd_samples) { + if (spindle_pos >= samples->spindle_loop_samples) { spindle_pos = 0; } } @@ -195,19 +514,21 @@ hdd_audio_callback(int16_t *buffer, int length) } } - if (hdd_seek_sound_buffer && hdd_seek_samples > 0 && hdd_audio_mutex) { + /* Seek sounds from profile */ + if (samples && samples->seek_buffer && samples->seek_samples > 0 && hdd_audio_mutex) { thread_wait_mutex(hdd_audio_mutex); for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { if (!hdd_seek_voices[v].active) continue; + float voice_vol = hdd_seek_voices[v].volume; int pos = hdd_seek_voices[v].position; if (pos < 0) pos = 0; - for (int i = 0; i < frames_in_buffer && pos < hdd_seek_samples; i++, pos++) { - int32_t left = buffer[i * 2] + hdd_seek_sound_buffer[pos * 2] / 2; - int32_t right = buffer[i * 2 + 1] + hdd_seek_sound_buffer[pos * 2 + 1] / 2; + for (int i = 0; i < frames_in_buffer && pos < samples->seek_samples; i++, pos++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->seek_buffer[pos * 2] * voice_vol); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->seek_buffer[pos * 2 + 1] * voice_vol); if (left > 32767) left = 32767; if (left < -32768) left = -32768; @@ -218,7 +539,7 @@ hdd_audio_callback(int16_t *buffer, int length) buffer[i * 2 + 1] = (int16_t) right; } - if (pos >= hdd_seek_samples) { + if (pos >= samples->seek_samples) { hdd_seek_voices[v].active = 0; hdd_seek_voices[v].position = 0; } else { diff --git a/src/include/86box/hdd.h b/src/include/86box/hdd.h index 88855552b..ddf8fdbe6 100644 --- a/src/include/86box/hdd.h +++ b/src/include/86box/hdd.h @@ -172,6 +172,7 @@ typedef struct hard_disk_t { uint32_t hpc; uint32_t tracks; uint32_t speed_preset; + uint32_t audio_profile; uint32_t num_zones; uint32_t phy_cyl; diff --git a/src/include/86box/hdd_audio.h b/src/include/86box/hdd_audio.h index 1e6d7b103..050bff0a0 100644 --- a/src/include/86box/hdd_audio.h +++ b/src/include/86box/hdd_audio.h @@ -1,6 +1,63 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for the hard disk audio emulation. + * + * Authors: Toni Riikonen, + * + * Copyright 2026 Toni Riikonen. + */ +#ifndef EMU_HDD_AUDIO_H +#define EMU_HDD_AUDIO_H + #include #include <86box/hdd.h> +#ifdef __cplusplus +extern "C" { +#endif + +#define HDD_AUDIO_PROFILE_MAX 64 + +/* Audio sample configuration structure */ +typedef struct { + char filename[512]; + float volume; +} hdd_audio_sample_config_t; + +/* HDD audio profile configuration */ +typedef struct { + int id; + char name[128]; + char internal_name[64]; + hdd_audio_sample_config_t spindlemotor_start; + hdd_audio_sample_config_t spindlemotor_loop; + hdd_audio_sample_config_t spindlemotor_stop; + hdd_audio_sample_config_t seek_track; +} hdd_audio_profile_config_t; + +/* Functions for profile management */ +extern void hdd_audio_load_profiles(void); +extern int hdd_audio_get_profile_count(void); +extern const hdd_audio_profile_config_t *hdd_audio_get_profile(int id); +extern const char *hdd_audio_get_profile_name(int id); +extern const char *hdd_audio_get_profile_internal_name(int id); +extern int hdd_audio_get_profile_by_internal_name(const char *internal_name); + +/* HDD audio initialization and cleanup */ extern void hdd_audio_init(void); +extern void hdd_audio_reset(void); +extern void hdd_audio_close(void); extern void hdd_audio_callback(int16_t *buffer, int length); -extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder); \ No newline at end of file +extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder); + +#ifdef __cplusplus +} +#endif + +#endif /* EMU_HDD_AUDIO_H */ \ No newline at end of file diff --git a/src/qt/qt_settingsharddisks.cpp b/src/qt/qt_settingsharddisks.cpp index 0f12c8e6f..7939cb989 100644 --- a/src/qt/qt_settingsharddisks.cpp +++ b/src/qt/qt_settingsharddisks.cpp @@ -20,6 +20,7 @@ extern "C" { #include <86box/86box.h> #include <86box/hdd.h> +#include <86box/hdd_audio.h> } #include @@ -36,6 +37,7 @@ const int ColumnHeads = 3; const int ColumnSectors = 4; const int ColumnSize = 5; const int ColumnSpeed = 6; +const int ColumnAudio = 7; const int DataBus = Qt::UserRole; const int DataBusChannel = Qt::UserRole + 1; @@ -103,6 +105,11 @@ addRow(QAbstractItemModel *model, hard_disk_t *hd) auto speedIndex = model->index(row, ColumnSpeed); model->setData(speedIndex, QObject::tr(hdd_preset_getname(hd->speed_preset))); model->setData(speedIndex, hd->speed_preset, Qt::UserRole); + + auto audioIndex = model->index(row, ColumnAudio); + const char *audioName = hdd_audio_get_profile_name(hd->audio_profile); + model->setData(audioIndex, audioName ? QObject::tr(audioName) : QObject::tr("None")); + model->setData(audioIndex, hd->audio_profile, Qt::UserRole); } SettingsHarddisks::SettingsHarddisks(QWidget *parent) @@ -113,7 +120,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) hard_disk_icon = QIcon(":/settings/qt/icons/hard_disk.ico"); - QAbstractItemModel *model = new QStandardItemModel(0, 7, this); + QAbstractItemModel *model = new QStandardItemModel(0, 8, this); model->setHeaderData(ColumnBus, Qt::Horizontal, tr("Bus")); model->setHeaderData(ColumnFilename, Qt::Horizontal, tr("File")); model->setHeaderData(ColumnCylinders, Qt::Horizontal, tr("C")); @@ -121,6 +128,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) model->setHeaderData(ColumnSectors, Qt::Horizontal, tr("S")); model->setHeaderData(ColumnSize, Qt::Horizontal, tr("MiB")); model->setHeaderData(ColumnSpeed, Qt::Horizontal, tr("Model")); + model->setHeaderData(ColumnAudio, Qt::Horizontal, tr("Audio")); ui->tableView->setModel(model); for (int i = 0; i < HDD_NUM; i++) { @@ -139,6 +147,16 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) onTableRowChanged(QModelIndex()); Harddrives::populateBuses(ui->comboBoxBus->model()); + + /* Populate audio profile combobox */ + int profile_count = hdd_audio_get_profile_count(); + for (int i = 0; i < profile_count; i++) { + const char *name = hdd_audio_get_profile_name(i); + if (name) { + ui->comboBoxAudio->addItem(name, i); + } + } + on_comboBoxBus_currentIndexChanged(0); } @@ -162,6 +180,7 @@ SettingsHarddisks::save() hdd[i].hpc = idx.siblingAtColumn(ColumnHeads).data().toUInt(); hdd[i].spt = idx.siblingAtColumn(ColumnSectors).data().toUInt(); hdd[i].speed_preset = idx.siblingAtColumn(ColumnSpeed).data(Qt::UserRole).toUInt(); + hdd[i].audio_profile = idx.siblingAtColumn(ColumnAudio).data(Qt::UserRole).toUInt(); QByteArray fileName = idx.siblingAtColumn(ColumnFilename).data(Qt::UserRole).toString().toUtf8(); strncpy(hdd[i].fn, fileName.data(), sizeof(hdd[i].fn) - 1); @@ -272,6 +291,24 @@ SettingsHarddisks::on_comboBoxSpeed_currentIndexChanged(int index) } } +void +SettingsHarddisks::on_comboBoxAudio_currentIndexChanged(int index) +{ + if (index < 0) + return; + + auto idx = ui->tableView->selectionModel()->currentIndex(); + if (idx.isValid()) { + auto *model = ui->tableView->model(); + auto col = idx.siblingAtColumn(ColumnAudio); + int prof = ui->comboBoxAudio->currentData(Qt::UserRole).toInt(); + model->setData(col, prof, Qt::UserRole); + + const char *audioName = hdd_audio_get_profile_name(prof); + model->setData(col, audioName ? QObject::tr(audioName) : QObject::tr("None")); + } +} + void SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) { @@ -279,13 +316,16 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) ui->labelBus->setHidden(hidden); ui->labelChannel->setHidden(hidden); ui->labelSpeed->setHidden(hidden); + ui->labelAudio->setHidden(hidden); ui->comboBoxBus->setHidden(hidden); ui->comboBoxChannel->setHidden(hidden); ui->comboBoxSpeed->setHidden(hidden); + ui->comboBoxAudio->setHidden(hidden); uint32_t bus = current.siblingAtColumn(ColumnBus).data(DataBus).toUInt(); uint32_t busChannel = current.siblingAtColumn(ColumnBus).data(DataBusChannel).toUInt(); uint32_t speed = current.siblingAtColumn(ColumnSpeed).data(Qt::UserRole).toUInt(); + uint32_t audio = current.siblingAtColumn(ColumnAudio).data(Qt::UserRole).toUInt(); auto *model = ui->comboBoxBus->model(); auto match = model->match(model->index(0, 0), Qt::UserRole, bus); @@ -302,6 +342,11 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) if (!match.isEmpty()) ui->comboBoxSpeed->setCurrentIndex(match.first().row()); + model = ui->comboBoxAudio->model(); + match = model->match(model->index(0, 0), Qt::UserRole, audio); + if (!match.isEmpty()) + ui->comboBoxAudio->setCurrentIndex(match.first().row()); + reloadBusChannels(); } diff --git a/src/qt/qt_settingsharddisks.hpp b/src/qt/qt_settingsharddisks.hpp index f892a79cd..b8d298bb4 100644 --- a/src/qt/qt_settingsharddisks.hpp +++ b/src/qt/qt_settingsharddisks.hpp @@ -24,6 +24,7 @@ private slots: void on_comboBoxBus_currentIndexChanged(int index); void on_comboBoxChannel_currentIndexChanged(int index); void on_comboBoxSpeed_currentIndexChanged(int index); + void on_comboBoxAudio_currentIndexChanged(int index); void on_pushButtonNew_clicked(); void on_pushButtonExisting_clicked(); diff --git a/src/qt/qt_settingsharddisks.ui b/src/qt/qt_settingsharddisks.ui index d65b1d0c1..a6f134bdc 100644 --- a/src/qt/qt_settingsharddisks.ui +++ b/src/qt/qt_settingsharddisks.ui @@ -90,6 +90,20 @@ + + + + Audio: + + + + + + + 30 + + + From b4a5c76847f86507fa03e0ce4b31dc8647ada1fd Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 09:32:25 +0200 Subject: [PATCH 3/7] HDD audio setting now populates audio profiles for selected HDD rpm --- src/disk/hdd.c | 8 +++++++ src/disk/hdd_audio.c | 10 ++++++++ src/include/86box/hdd.h | 1 + src/include/86box/hdd_audio.h | 2 ++ src/qt/qt_settingsharddisks.cpp | 41 +++++++++++++++++++++++++-------- src/qt/qt_settingsharddisks.hpp | 1 + 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/disk/hdd.c b/src/disk/hdd.c index cd34b66fa..bbbec0865 100644 --- a/src/disk/hdd.c +++ b/src/disk/hdd.c @@ -589,6 +589,14 @@ hdd_preset_get_internal_name(int preset) return hdd_speed_presets[preset].internal_name; } +uint32_t +hdd_preset_get_rpm(int preset) +{ + if (preset < 0 || preset >= hdd_preset_get_num()) + return 0; + return hdd_speed_presets[preset].rpm; +} + int hdd_preset_get_from_internal_name(char *s) { diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c index bc3f9ed95..464f3e224 100644 --- a/src/disk/hdd_audio.c +++ b/src/disk/hdd_audio.c @@ -107,6 +107,8 @@ hdd_audio_load_profiles(void) const char *internal_name = ini_section_get_string(cat, "internal_name", "unknown"); strncpy(config->internal_name, internal_name, sizeof(config->internal_name) - 1); + config->rpm = ini_section_get_int(cat, "rpm", 0); + /* Load spindle motor sample files */ const char *file = ini_section_get_string(cat, "spindlemotor_start_file", ""); strncpy(config->spindlemotor_start.filename, file, sizeof(config->spindlemotor_start.filename) - 1); @@ -167,6 +169,14 @@ hdd_audio_get_profile_internal_name(int id) return audio_profiles[id].internal_name; } +uint32_t +hdd_audio_get_profile_rpm(int id) +{ + if (id < 0 || id >= audio_profile_count) + return 0; + return audio_profiles[id].rpm; +} + int hdd_audio_get_profile_by_internal_name(const char *internal_name) { diff --git a/src/include/86box/hdd.h b/src/include/86box/hdd.h index ddf8fdbe6..d8f275de3 100644 --- a/src/include/86box/hdd.h +++ b/src/include/86box/hdd.h @@ -234,6 +234,7 @@ extern double hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_ int hdd_preset_get_num(void); const char *hdd_preset_getname(int preset); extern const char *hdd_preset_get_internal_name(int preset); +extern uint32_t hdd_preset_get_rpm(int preset); extern int hdd_preset_get_from_internal_name(char *s); extern void hdd_preset_apply(int hdd_id); diff --git a/src/include/86box/hdd_audio.h b/src/include/86box/hdd_audio.h index 050bff0a0..8dfac0c00 100644 --- a/src/include/86box/hdd_audio.h +++ b/src/include/86box/hdd_audio.h @@ -35,6 +35,7 @@ typedef struct { int id; char name[128]; char internal_name[64]; + uint32_t rpm; hdd_audio_sample_config_t spindlemotor_start; hdd_audio_sample_config_t spindlemotor_loop; hdd_audio_sample_config_t spindlemotor_stop; @@ -47,6 +48,7 @@ extern int hdd_audio_get_profile_count(void); extern const hdd_audio_profile_config_t *hdd_audio_get_profile(int id); extern const char *hdd_audio_get_profile_name(int id); extern const char *hdd_audio_get_profile_internal_name(int id); +extern uint32_t hdd_audio_get_profile_rpm(int id); extern int hdd_audio_get_profile_by_internal_name(const char *internal_name); /* HDD audio initialization and cleanup */ diff --git a/src/qt/qt_settingsharddisks.cpp b/src/qt/qt_settingsharddisks.cpp index 7939cb989..849c34305 100644 --- a/src/qt/qt_settingsharddisks.cpp +++ b/src/qt/qt_settingsharddisks.cpp @@ -148,15 +148,6 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent) Harddrives::populateBuses(ui->comboBoxBus->model()); - /* Populate audio profile combobox */ - int profile_count = hdd_audio_get_profile_count(); - for (int i = 0; i < profile_count; i++) { - const char *name = hdd_audio_get_profile_name(i); - if (name) { - ui->comboBoxAudio->addItem(name, i); - } - } - on_comboBoxBus_currentIndexChanged(0); } @@ -288,6 +279,35 @@ SettingsHarddisks::on_comboBoxSpeed_currentIndexChanged(int index) auto col = idx.siblingAtColumn(ColumnSpeed); model->setData(col, ui->comboBoxSpeed->currentData(Qt::UserRole), Qt::UserRole); model->setData(col, QObject::tr(hdd_preset_getname(ui->comboBoxSpeed->currentData(Qt::UserRole).toUInt()))); + + /* Reset audio profile to None when speed/model changes */ + auto audioCol = idx.siblingAtColumn(ColumnAudio); + model->setData(audioCol, 0, Qt::UserRole); + model->setData(audioCol, QObject::tr("None")); + } + + /* Repopulate audio profiles based on the selected speed preset's RPM */ + populateAudioProfiles(); +} + +void +SettingsHarddisks::populateAudioProfiles() +{ + ui->comboBoxAudio->clear(); + + /* Get RPM from currently selected speed preset */ + uint32_t target_rpm = hdd_preset_get_rpm(ui->comboBoxSpeed->currentData(Qt::UserRole).toUInt()); + + /* Populate audio profile combobox with matching RPM profiles */ + int profile_count = hdd_audio_get_profile_count(); + for (int i = 0; i < profile_count; i++) { + const char *name = hdd_audio_get_profile_name(i); + uint32_t profile_rpm = hdd_audio_get_profile_rpm(i); + + /* Include profile if it has no RPM set (0) or matches target RPM */ + if (name && (profile_rpm == 0 || profile_rpm == target_rpm)) { + ui->comboBoxAudio->addItem(name, i); + } } } @@ -342,6 +362,9 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) if (!match.isEmpty()) ui->comboBoxSpeed->setCurrentIndex(match.first().row()); + /* Populate audio profiles based on selected speed preset's RPM */ + populateAudioProfiles(); + model = ui->comboBoxAudio->model(); match = model->match(model->index(0, 0), Qt::UserRole, audio); if (!match.isEmpty()) diff --git a/src/qt/qt_settingsharddisks.hpp b/src/qt/qt_settingsharddisks.hpp index b8d298bb4..f082c7de6 100644 --- a/src/qt/qt_settingsharddisks.hpp +++ b/src/qt/qt_settingsharddisks.hpp @@ -35,6 +35,7 @@ private slots: private: Ui::SettingsHarddisks *ui; void enableCurrentlySelectedChannel(); + void populateAudioProfiles(); bool buschangeinprogress = false; }; From 419ee5cbd9a244615778aec12eac87955454fd42 Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 11:15:22 +0200 Subject: [PATCH 4/7] HDD audio start/stop sample playback + some mutex deadlock fixes --- src/disk/hdd_audio.c | 280 ++++++++++++++++++++++++++++------ src/include/86box/hdd_audio.h | 11 ++ 2 files changed, 241 insertions(+), 50 deletions(-) diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c index 464f3e224..af93a334c 100644 --- a/src/disk/hdd_audio.c +++ b/src/disk/hdd_audio.c @@ -42,13 +42,15 @@ typedef struct { typedef struct { int16_t *spindle_start_buffer; int spindle_start_samples; + float spindle_start_volume; int16_t *spindle_loop_buffer; int spindle_loop_samples; + float spindle_loop_volume; int16_t *spindle_stop_buffer; int spindle_stop_samples; + float spindle_stop_volume; int16_t *seek_buffer; int seek_samples; - float spindle_volume; float seek_volume; int loaded; } hdd_audio_samples_t; @@ -66,6 +68,11 @@ static int active_audio_profile = 0; static hdd_seek_voice_t hdd_seek_voices[HDD_MAX_SEEK_VOICES]; static mutex_t *hdd_audio_mutex = NULL; +/* Spindle motor state */ +static hdd_spindle_state_t spindle_state = HDD_SPINDLE_STOPPED; +static int spindle_pos = 0; +static int spindle_transition_pos = 0; /* Position in start/stop sample */ + /* Load audio profiles from configuration file */ void hdd_audio_load_profiles(void) @@ -248,7 +255,7 @@ hdd_audio_load_profile_samples(int profile_id) config->spindlemotor_loop.filename, &samples->spindle_loop_samples); if (samples->spindle_loop_buffer) { - samples->spindle_volume = config->spindlemotor_loop.volume; + samples->spindle_loop_volume = config->spindlemotor_loop.volume; pclog("HDD Audio: Loaded spindle loop, %d frames\n", samples->spindle_loop_samples); } else { pclog("HDD Audio: Failed to load spindle loop: %s\n", config->spindlemotor_loop.filename); @@ -261,6 +268,7 @@ hdd_audio_load_profile_samples(int profile_id) config->spindlemotor_start.filename, &samples->spindle_start_samples); if (samples->spindle_start_buffer) { + samples->spindle_start_volume = config->spindlemotor_start.volume; pclog("HDD Audio: Loaded spindle start, %d frames\n", samples->spindle_start_samples); } } @@ -271,6 +279,7 @@ hdd_audio_load_profile_samples(int profile_id) config->spindlemotor_stop.filename, &samples->spindle_stop_samples); if (samples->spindle_stop_buffer) { + samples->spindle_stop_volume = config->spindlemotor_stop.volume; pclog("HDD Audio: Loaded spindle stop, %d frames\n", samples->spindle_stop_samples); } } @@ -316,18 +325,22 @@ hdd_audio_init(void) pclog("HDD Audio Init: active_audio_profile=%d\n", active_audio_profile); - /* Load samples for the active profile */ - if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { - hdd_audio_load_profile_samples(active_audio_profile); - } - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { hdd_seek_voices[i].active = 0; hdd_seek_voices[i].position = 0; hdd_seek_voices[i].volume = 1.0f; } - hdd_audio_mutex = thread_create_mutex(); + /* Create mutex BEFORE loading samples or calling spinup */ + if (!hdd_audio_mutex) + hdd_audio_mutex = thread_create_mutex(); + + /* Load samples for the active profile */ + if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { + hdd_audio_load_profile_samples(active_audio_profile); + /* Start spindle motor */ + hdd_audio_spinup(); + } sound_hdd_thread_init(); } @@ -337,6 +350,25 @@ hdd_audio_reset(void) { pclog("HDD Audio: Reset\n"); + /* Lock mutex to prevent audio callback from accessing buffers during reset */ + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + + /* Reset spindle state first to stop audio playback */ + spindle_state = HDD_SPINDLE_STOPPED; + spindle_pos = 0; + spindle_transition_pos = 0; + + /* Reset seek voices */ + for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { + hdd_seek_voices[i].active = 0; + hdd_seek_voices[i].position = 0; + hdd_seek_voices[i].volume = 1.0f; + } + + /* Reset active profile before freeing buffers */ + active_audio_profile = 0; + /* Free previously loaded samples (but keep profiles) */ for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) { if (profile_samples[i].spindle_start_buffer) { @@ -358,15 +390,10 @@ hdd_audio_reset(void) profile_samples[i].loaded = 0; } - /* Reset seek voices */ - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { - hdd_seek_voices[i].active = 0; - hdd_seek_voices[i].position = 0; - hdd_seek_voices[i].volume = 1.0f; - } + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); /* Find new active profile from current HDD configuration */ - active_audio_profile = 0; for (int i = 0; i < HDD_NUM; i++) { if (hdd[i].bus_type != HDD_BUS_DISABLED) { pclog("HDD Audio Reset: HDD %d audio_profile=%d\n", i, hdd[i].audio_profile); @@ -381,6 +408,8 @@ hdd_audio_reset(void) /* Load samples for the active profile */ if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { hdd_audio_load_profile_samples(active_audio_profile); + /* Start spindle motor */ + hdd_audio_spinup(); } } @@ -411,6 +440,10 @@ hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) return; } + /* Mutex must exist */ + if (!hdd_audio_mutex) + return; + int min_seek_spacing = 0; if (hdd_drive->cyl_switch_usec > 0) min_seek_spacing = (int)(hdd_drive->cyl_switch_usec * 48000.0 / 1000000.0); @@ -440,10 +473,47 @@ hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) thread_release_mutex(hdd_audio_mutex); } +void +hdd_audio_spinup(void) +{ + if (spindle_state == HDD_SPINDLE_RUNNING || spindle_state == HDD_SPINDLE_STARTING) + return; + + pclog("HDD Audio: Spinup requested (current state: %d)\n", spindle_state); + + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + spindle_state = HDD_SPINDLE_STARTING; + spindle_transition_pos = 0; + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); +} + +void +hdd_audio_spindown(void) +{ + if (spindle_state == HDD_SPINDLE_STOPPED || spindle_state == HDD_SPINDLE_STOPPING) + return; + + pclog("HDD Audio: Spindown requested (current state: %d)\n", spindle_state); + + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + spindle_state = HDD_SPINDLE_STOPPING; + spindle_transition_pos = 0; + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); +} + +hdd_spindle_state_t +hdd_audio_get_spindle_state(void) +{ + return spindle_state; +} + void hdd_audio_callback(int16_t *buffer, int length) { - static int spindle_pos = 0; int frames_in_buffer = length / 2; /* Get active profile samples */ @@ -455,28 +525,86 @@ hdd_audio_callback(int16_t *buffer, int length) if (sound_is_float) { float *float_buffer = (float *) buffer; - /* Spindle sound from profile */ - if (samples && samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { - float spindle_volume = samples->spindle_volume; - for (int i = 0; i < frames_in_buffer; i++) { - float left_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2] / 131072.0f * spindle_volume; - float right_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume; - float_buffer[i * 2] = left_sample; - float_buffer[i * 2 + 1] = right_sample; + /* Initialize buffer to silence */ + for (int i = 0; i < length; i++) { + float_buffer[i] = 0.0f; + } - spindle_pos++; - if (spindle_pos >= samples->spindle_loop_samples) { - spindle_pos = 0; - } - } - } else { - for (int i = 0; i < length; i++) { - float_buffer[i] = 0.0f; + /* Handle spindle states */ + if (samples) { + switch (spindle_state) { + case HDD_SPINDLE_STARTING: + /* Play spinup sound */ + if (samples->spindle_start_buffer && samples->spindle_start_samples > 0) { + float start_volume = samples->spindle_start_volume; + for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_start_samples; i++) { + float left_sample = (float) samples->spindle_start_buffer[spindle_transition_pos * 2] / 131072.0f * start_volume; + float right_sample = (float) samples->spindle_start_buffer[spindle_transition_pos * 2 + 1] / 131072.0f * start_volume; + float_buffer[i * 2] = left_sample; + float_buffer[i * 2 + 1] = right_sample; + spindle_transition_pos++; + } + if (spindle_transition_pos >= samples->spindle_start_samples) { + spindle_state = HDD_SPINDLE_RUNNING; + spindle_pos = 0; + pclog("HDD Audio: Spinup complete, now running\n"); + } + } else { + /* No start sample, go directly to running */ + spindle_state = HDD_SPINDLE_RUNNING; + spindle_pos = 0; + } + break; + + case HDD_SPINDLE_RUNNING: + /* Play spindle loop */ + if (samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { + float spindle_volume = samples->spindle_loop_volume; + for (int i = 0; i < frames_in_buffer; i++) { + float left_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2] / 131072.0f * spindle_volume; + float right_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume; + float_buffer[i * 2] = left_sample; + float_buffer[i * 2 + 1] = right_sample; + + spindle_pos++; + if (spindle_pos >= samples->spindle_loop_samples) { + spindle_pos = 0; + } + } + } + break; + + case HDD_SPINDLE_STOPPING: + /* Play spindown sound */ + if (samples->spindle_stop_buffer && samples->spindle_stop_samples > 0) { + float stop_volume = samples->spindle_stop_volume; + for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_stop_samples; i++) { + float left_sample = (float) samples->spindle_stop_buffer[spindle_transition_pos * 2] / 131072.0f * stop_volume; + float right_sample = (float) samples->spindle_stop_buffer[spindle_transition_pos * 2 + 1] / 131072.0f * stop_volume; + float_buffer[i * 2] = left_sample; + float_buffer[i * 2 + 1] = right_sample; + spindle_transition_pos++; + } + if (spindle_transition_pos >= samples->spindle_stop_samples) { + spindle_state = HDD_SPINDLE_STOPPED; + pclog("HDD Audio: Spindown complete, now stopped\n"); + } + } else { + /* No stop sample, go directly to stopped */ + spindle_state = HDD_SPINDLE_STOPPED; + } + break; + + case HDD_SPINDLE_STOPPED: + default: + /* Silence - buffer already zeroed */ + break; } } - /* Seek sounds from profile */ - if (samples && samples->seek_buffer && samples->seek_samples > 0 && hdd_audio_mutex) { + /* Seek sounds from profile - only play when spindle is running */ + if (samples && samples->seek_buffer && samples->seek_samples > 0 && + hdd_audio_mutex && spindle_state == HDD_SPINDLE_RUNNING) { thread_wait_mutex(hdd_audio_mutex); for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { @@ -506,26 +634,78 @@ hdd_audio_callback(int16_t *buffer, int length) thread_release_mutex(hdd_audio_mutex); } } else { - /* Spindle sound from profile */ - if (samples && samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { - float spindle_volume = samples->spindle_volume; - for (int i = 0; i < frames_in_buffer; i++) { - buffer[i * 2] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2] * spindle_volume); - buffer[i * 2 + 1] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2 + 1] * spindle_volume); + /* Initialize buffer to silence */ + for (int i = 0; i < length; i++) { + buffer[i] = 0; + } - spindle_pos++; - if (spindle_pos >= samples->spindle_loop_samples) { - spindle_pos = 0; - } - } - } else { - for (int i = 0; i < length; i++) { - buffer[i] = 0; + /* Handle spindle states */ + if (samples) { + switch (spindle_state) { + case HDD_SPINDLE_STARTING: + /* Play spinup sound */ + if (samples->spindle_start_buffer && samples->spindle_start_samples > 0) { + float start_volume = samples->spindle_start_volume; + for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_start_samples; i++) { + buffer[i * 2] = (int16_t)(samples->spindle_start_buffer[spindle_transition_pos * 2] * start_volume); + buffer[i * 2 + 1] = (int16_t)(samples->spindle_start_buffer[spindle_transition_pos * 2 + 1] * start_volume); + spindle_transition_pos++; + } + if (spindle_transition_pos >= samples->spindle_start_samples) { + spindle_state = HDD_SPINDLE_RUNNING; + spindle_pos = 0; + pclog("HDD Audio: Spinup complete, now running\n"); + } + } else { + spindle_state = HDD_SPINDLE_RUNNING; + spindle_pos = 0; + } + break; + + case HDD_SPINDLE_RUNNING: + /* Play spindle loop */ + if (samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { + float spindle_volume = samples->spindle_loop_volume; + for (int i = 0; i < frames_in_buffer; i++) { + buffer[i * 2] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2] * spindle_volume); + buffer[i * 2 + 1] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2 + 1] * spindle_volume); + + spindle_pos++; + if (spindle_pos >= samples->spindle_loop_samples) { + spindle_pos = 0; + } + } + } + break; + + case HDD_SPINDLE_STOPPING: + /* Play spindown sound */ + if (samples->spindle_stop_buffer && samples->spindle_stop_samples > 0) { + float stop_volume = samples->spindle_stop_volume; + for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_stop_samples; i++) { + buffer[i * 2] = (int16_t)(samples->spindle_stop_buffer[spindle_transition_pos * 2] * stop_volume); + buffer[i * 2 + 1] = (int16_t)(samples->spindle_stop_buffer[spindle_transition_pos * 2 + 1] * stop_volume); + spindle_transition_pos++; + } + if (spindle_transition_pos >= samples->spindle_stop_samples) { + spindle_state = HDD_SPINDLE_STOPPED; + pclog("HDD Audio: Spindown complete, now stopped\n"); + } + } else { + spindle_state = HDD_SPINDLE_STOPPED; + } + break; + + case HDD_SPINDLE_STOPPED: + default: + /* Silence - buffer already zeroed */ + break; } } - /* Seek sounds from profile */ - if (samples && samples->seek_buffer && samples->seek_samples > 0 && hdd_audio_mutex) { + /* Seek sounds from profile - only play when spindle is running */ + if (samples && samples->seek_buffer && samples->seek_samples > 0 && + hdd_audio_mutex && spindle_state == HDD_SPINDLE_RUNNING) { thread_wait_mutex(hdd_audio_mutex); for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { diff --git a/src/include/86box/hdd_audio.h b/src/include/86box/hdd_audio.h index 8dfac0c00..2bb19a70e 100644 --- a/src/include/86box/hdd_audio.h +++ b/src/include/86box/hdd_audio.h @@ -24,6 +24,14 @@ extern "C" { #define HDD_AUDIO_PROFILE_MAX 64 +/* Spindle motor states */ +typedef enum { + HDD_SPINDLE_STOPPED = 0, + HDD_SPINDLE_STARTING, + HDD_SPINDLE_RUNNING, + HDD_SPINDLE_STOPPING +} hdd_spindle_state_t; + /* Audio sample configuration structure */ typedef struct { char filename[512]; @@ -57,6 +65,9 @@ extern void hdd_audio_reset(void); extern void hdd_audio_close(void); extern void hdd_audio_callback(int16_t *buffer, int length); extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder); +extern void hdd_audio_spinup(void); +extern void hdd_audio_spindown(void); +extern hdd_spindle_state_t hdd_audio_get_spindle_state(void); #ifdef __cplusplus } From 4b86bcc2bf7201d5b24a9a352d9a19c1c64c1204 Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 21:35:59 +0200 Subject: [PATCH 5/7] Multiple HDD drive sounds support --- src/disk/hdd_audio.c | 817 +++++++++++++++++++++------------- src/include/86box/hdd_audio.h | 7 + 2 files changed, 525 insertions(+), 299 deletions(-) diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c index af93a334c..9098adf78 100644 --- a/src/disk/hdd_audio.c +++ b/src/disk/hdd_audio.c @@ -29,15 +29,29 @@ #include <86box/mem.h> #include <86box/rom.h> -/* Maximum number of simultaneous seek sounds */ -#define HDD_MAX_SEEK_VOICES 8 +/* Maximum number of simultaneous seek sounds per HDD */ +#define HDD_MAX_SEEK_VOICES_PER_HDD 8 + +/* Maximum number of HDDs with audio emulation */ +#define HDD_AUDIO_MAX_DRIVES 8 typedef struct { int active; int position; float volume; + int profile_id; /* Which profile's seek sound to use */ } hdd_seek_voice_t; +/* Per-HDD audio state */ +typedef struct { + int hdd_index; /* Index into hdd[] array */ + int profile_id; /* Audio profile ID */ + hdd_spindle_state_t spindle_state; + int spindle_pos; + int spindle_transition_pos; + hdd_seek_voice_t seek_voices[HDD_MAX_SEEK_VOICES_PER_HDD]; +} hdd_audio_drive_state_t; + /* Audio samples structure for a profile */ typedef struct { int16_t *spindle_start_buffer; @@ -62,16 +76,11 @@ static int audio_profile_count = 0; /* Per-profile loaded samples */ static hdd_audio_samples_t profile_samples[HDD_AUDIO_PROFILE_MAX]; -/* Active profile for audio playback (first HDD with valid profile) */ -static int active_audio_profile = 0; +/* Per-HDD audio states */ +static hdd_audio_drive_state_t drive_states[HDD_AUDIO_MAX_DRIVES]; +static int active_drive_count = 0; -static hdd_seek_voice_t hdd_seek_voices[HDD_MAX_SEEK_VOICES]; -static mutex_t *hdd_audio_mutex = NULL; - -/* Spindle motor state */ -static hdd_spindle_state_t spindle_state = HDD_SPINDLE_STOPPED; -static int spindle_pos = 0; -static int spindle_transition_pos = 0; /* Position in start/stop sample */ +static mutex_t *hdd_audio_mutex = NULL; /* Load audio profiles from configuration file */ void @@ -301,45 +310,68 @@ hdd_audio_load_profile_samples(int profile_id) samples->loaded = 1; } +/* Find drive state for a given HDD index, or NULL if not tracked */ +static hdd_audio_drive_state_t * +hdd_audio_find_drive_state(int hdd_index) +{ + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].hdd_index == hdd_index) + return &drive_states[i]; + } + return NULL; +} + void hdd_audio_init(void) { /* Initialize profile samples */ memset(profile_samples, 0, sizeof(profile_samples)); + memset(drive_states, 0, sizeof(drive_states)); + active_drive_count = 0; pclog("HDD Audio Init: audio_profile_count=%d\n", audio_profile_count); - /* Find first HDD with a valid audio profile and load its samples */ - active_audio_profile = 0; - for (int i = 0; i < HDD_NUM; i++) { - if (hdd[i].bus_type != HDD_BUS_DISABLED) { - pclog("HDD Audio Init: HDD %d bus_type=%d audio_profile=%d\n", - i, hdd[i].bus_type, hdd[i].audio_profile); - if (hdd[i].audio_profile > 0) { - active_audio_profile = hdd[i].audio_profile; - pclog("HDD Audio: Using profile %d from HDD %d\n", active_audio_profile, i); - break; - } - } - } - - pclog("HDD Audio Init: active_audio_profile=%d\n", active_audio_profile); - - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { - hdd_seek_voices[i].active = 0; - hdd_seek_voices[i].position = 0; - hdd_seek_voices[i].volume = 1.0f; - } - /* Create mutex BEFORE loading samples or calling spinup */ if (!hdd_audio_mutex) hdd_audio_mutex = thread_create_mutex(); - /* Load samples for the active profile */ - if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { - hdd_audio_load_profile_samples(active_audio_profile); - /* Start spindle motor */ - hdd_audio_spinup(); + /* Find all HDDs with valid audio profiles and initialize their states */ + for (int i = 0; i < HDD_NUM && active_drive_count < HDD_AUDIO_MAX_DRIVES; i++) { + if (hdd[i].bus_type != HDD_BUS_DISABLED && hdd[i].audio_profile > 0) { + pclog("HDD Audio Init: HDD %d bus_type=%d audio_profile=%d\n", + i, hdd[i].bus_type, hdd[i].audio_profile); + + hdd_audio_drive_state_t *state = &drive_states[active_drive_count]; + state->hdd_index = i; + state->profile_id = hdd[i].audio_profile; + state->spindle_state = HDD_SPINDLE_STOPPED; + state->spindle_pos = 0; + state->spindle_transition_pos = 0; + + /* Initialize seek voices for this drive */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + state->seek_voices[v].volume = 1.0f; + state->seek_voices[v].profile_id = state->profile_id; + } + + /* Load samples for this profile if not already loaded */ + hdd_audio_load_profile_samples(state->profile_id); + + pclog("HDD Audio: Initialized drive %d with profile %d (%s)\n", + i, state->profile_id, + hdd_audio_get_profile_name(state->profile_id)); + + active_drive_count++; + } + } + + pclog("HDD Audio Init: %d active drives with audio\n", active_drive_count); + + /* Start spindle motors for all active drives */ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spinup_drive(drive_states[i].hdd_index); } sound_hdd_thread_init(); @@ -354,20 +386,18 @@ hdd_audio_reset(void) if (hdd_audio_mutex) thread_wait_mutex(hdd_audio_mutex); - /* Reset spindle state first to stop audio playback */ - spindle_state = HDD_SPINDLE_STOPPED; - spindle_pos = 0; - spindle_transition_pos = 0; - - /* Reset seek voices */ - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { - hdd_seek_voices[i].active = 0; - hdd_seek_voices[i].position = 0; - hdd_seek_voices[i].volume = 1.0f; + /* Reset all drive states */ + for (int i = 0; i < active_drive_count; i++) { + drive_states[i].spindle_state = HDD_SPINDLE_STOPPED; + drive_states[i].spindle_pos = 0; + drive_states[i].spindle_transition_pos = 0; + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + drive_states[i].seek_voices[v].active = 0; + drive_states[i].seek_voices[v].position = 0; + drive_states[i].seek_voices[v].volume = 1.0f; + } } - - /* Reset active profile before freeing buffers */ - active_audio_profile = 0; + active_drive_count = 0; /* Free previously loaded samples (but keep profiles) */ for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) { @@ -393,23 +423,42 @@ hdd_audio_reset(void) if (hdd_audio_mutex) thread_release_mutex(hdd_audio_mutex); - /* Find new active profile from current HDD configuration */ - for (int i = 0; i < HDD_NUM; i++) { - if (hdd[i].bus_type != HDD_BUS_DISABLED) { + /* Find all HDDs with valid audio profiles and initialize their states */ + for (int i = 0; i < HDD_NUM && active_drive_count < HDD_AUDIO_MAX_DRIVES; i++) { + if (hdd[i].bus_type != HDD_BUS_DISABLED && hdd[i].audio_profile > 0) { pclog("HDD Audio Reset: HDD %d audio_profile=%d\n", i, hdd[i].audio_profile); - if (hdd[i].audio_profile > 0) { - active_audio_profile = hdd[i].audio_profile; - pclog("HDD Audio: Reset with profile %d from HDD %d\n", active_audio_profile, i); - break; + + hdd_audio_drive_state_t *state = &drive_states[active_drive_count]; + state->hdd_index = i; + state->profile_id = hdd[i].audio_profile; + state->spindle_state = HDD_SPINDLE_STOPPED; + state->spindle_pos = 0; + state->spindle_transition_pos = 0; + + /* Initialize seek voices for this drive */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + state->seek_voices[v].volume = 1.0f; + state->seek_voices[v].profile_id = state->profile_id; } + + /* Load samples for this profile if not already loaded */ + hdd_audio_load_profile_samples(state->profile_id); + + pclog("HDD Audio: Reset drive %d with profile %d (%s)\n", + i, state->profile_id, + hdd_audio_get_profile_name(state->profile_id)); + + active_drive_count++; } } - /* Load samples for the active profile */ - if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) { - hdd_audio_load_profile_samples(active_audio_profile); - /* Start spindle motor */ - hdd_audio_spinup(); + pclog("HDD Audio Reset: %d active drives with audio\n", active_drive_count); + + /* Start spindle motors for all active drives */ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spinup_drive(drive_states[i].hdd_index); } } @@ -421,10 +470,20 @@ hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) if (cylinder_diff == 0) return; - /* Use the drive's audio profile, fallback to active profile */ - int profile_id = hdd_drive->audio_profile; - if (profile_id == 0) - profile_id = active_audio_profile; + /* Find the drive state for this HDD */ + hdd_audio_drive_state_t *drive_state = NULL; + for (int i = 0; i < active_drive_count; i++) { + if (&hdd[drive_states[i].hdd_index] == hdd_drive) { + drive_state = &drive_states[i]; + break; + } + } + + /* If no drive state found, drive has no audio profile */ + if (!drive_state) + return; + + int profile_id = drive_state->profile_id; /* No audio profile selected */ if (profile_id == 0 || profile_id >= audio_profile_count) @@ -450,9 +509,10 @@ hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) thread_wait_mutex(hdd_audio_mutex); - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { - if (hdd_seek_voices[i].active) { - int pos = hdd_seek_voices[i].position; + /* Check if we should skip due to minimum spacing (per-drive) */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (drive_state->seek_voices[v].active) { + int pos = drive_state->seek_voices[v].position; if (pos >= 0 && pos < min_seek_spacing) { thread_release_mutex(hdd_audio_mutex); return; @@ -460,11 +520,13 @@ hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) } } - for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) { - if (!hdd_seek_voices[i].active) { - hdd_seek_voices[i].active = 1; - hdd_seek_voices[i].position = 0; - hdd_seek_voices[i].volume = samples->seek_volume; + /* Find a free seek voice for this drive */ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (!drive_state->seek_voices[v].active) { + drive_state->seek_voices[v].active = 1; + drive_state->seek_voices[v].position = 0; + drive_state->seek_voices[v].volume = samples->seek_volume; + drive_state->seek_voices[v].profile_id = profile_id; thread_release_mutex(hdd_audio_mutex); return; } @@ -473,42 +535,400 @@ hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder) thread_release_mutex(hdd_audio_mutex); } +/* Spinup a specific drive by HDD index */ void -hdd_audio_spinup(void) +hdd_audio_spinup_drive(int hdd_index) { - if (spindle_state == HDD_SPINDLE_RUNNING || spindle_state == HDD_SPINDLE_STARTING) + hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index); + if (!state) return; - pclog("HDD Audio: Spinup requested (current state: %d)\n", spindle_state); + if (state->spindle_state == HDD_SPINDLE_RUNNING || state->spindle_state == HDD_SPINDLE_STARTING) + return; + + pclog("HDD Audio: Spinup requested for drive %d (current state: %d)\n", hdd_index, state->spindle_state); if (hdd_audio_mutex) thread_wait_mutex(hdd_audio_mutex); - spindle_state = HDD_SPINDLE_STARTING; - spindle_transition_pos = 0; + state->spindle_state = HDD_SPINDLE_STARTING; + state->spindle_transition_pos = 0; if (hdd_audio_mutex) thread_release_mutex(hdd_audio_mutex); } +/* Spindown a specific drive by HDD index */ +void +hdd_audio_spindown_drive(int hdd_index) +{ + hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index); + if (!state) + return; + + if (state->spindle_state == HDD_SPINDLE_STOPPED || state->spindle_state == HDD_SPINDLE_STOPPING) + return; + + pclog("HDD Audio: Spindown requested for drive %d (current state: %d)\n", hdd_index, state->spindle_state); + + if (hdd_audio_mutex) + thread_wait_mutex(hdd_audio_mutex); + state->spindle_state = HDD_SPINDLE_STOPPING; + state->spindle_transition_pos = 0; + if (hdd_audio_mutex) + thread_release_mutex(hdd_audio_mutex); +} + +/* Legacy functions for backward compatibility - operate on all drives */ +void +hdd_audio_spinup(void) +{ + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spinup_drive(drive_states[i].hdd_index); + } +} + void hdd_audio_spindown(void) { - if (spindle_state == HDD_SPINDLE_STOPPED || spindle_state == HDD_SPINDLE_STOPPING) - return; - - pclog("HDD Audio: Spindown requested (current state: %d)\n", spindle_state); - - if (hdd_audio_mutex) - thread_wait_mutex(hdd_audio_mutex); - spindle_state = HDD_SPINDLE_STOPPING; - spindle_transition_pos = 0; - if (hdd_audio_mutex) - thread_release_mutex(hdd_audio_mutex); + for (int i = 0; i < active_drive_count; i++) { + hdd_audio_spindown_drive(drive_states[i].hdd_index); + } } hdd_spindle_state_t hdd_audio_get_spindle_state(void) { - return spindle_state; + /* Return running if any drive is running */ + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].spindle_state == HDD_SPINDLE_RUNNING) + return HDD_SPINDLE_RUNNING; + } + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].spindle_state == HDD_SPINDLE_STARTING) + return HDD_SPINDLE_STARTING; + } + for (int i = 0; i < active_drive_count; i++) { + if (drive_states[i].spindle_state == HDD_SPINDLE_STOPPING) + return HDD_SPINDLE_STOPPING; + } + return HDD_SPINDLE_STOPPED; +} + +hdd_spindle_state_t +hdd_audio_get_drive_spindle_state(int hdd_index) +{ + hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index); + if (!state) + return HDD_SPINDLE_STOPPED; + return state->spindle_state; +} + +/* Helper: Mix spindle start sound into float buffer */ +static void +hdd_audio_mix_spindle_start_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + float *float_buffer, int frames_in_buffer) +{ + if (!samples->spindle_start_buffer || samples->spindle_start_samples <= 0) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + return; + } + + float start_volume = samples->spindle_start_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_start_samples; i++) { + float left_sample = (float) samples->spindle_start_buffer[state->spindle_transition_pos * 2] / 131072.0f * start_volume; + float right_sample = (float) samples->spindle_start_buffer[state->spindle_transition_pos * 2 + 1] / 131072.0f * start_volume; + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_start_samples) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + pclog("HDD Audio: Drive %d spinup complete, now running\n", state->hdd_index); + } +} + +/* Helper: Mix spindle loop sound into float buffer */ +static void +hdd_audio_mix_spindle_loop_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + float *float_buffer, int frames_in_buffer) +{ + if (!samples->spindle_loop_buffer || samples->spindle_loop_samples <= 0) + return; + + float spindle_volume = samples->spindle_loop_volume; + for (int i = 0; i < frames_in_buffer; i++) { + float left_sample = (float) samples->spindle_loop_buffer[state->spindle_pos * 2] / 131072.0f * spindle_volume; + float right_sample = (float) samples->spindle_loop_buffer[state->spindle_pos * 2 + 1] / 131072.0f * spindle_volume; + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + + state->spindle_pos++; + if (state->spindle_pos >= samples->spindle_loop_samples) { + state->spindle_pos = 0; + } + } +} + +/* Helper: Mix spindle stop sound into float buffer */ +static void +hdd_audio_mix_spindle_stop_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + float *float_buffer, int frames_in_buffer) +{ + if (!samples->spindle_stop_buffer || samples->spindle_stop_samples <= 0) { + state->spindle_state = HDD_SPINDLE_STOPPED; + return; + } + + float stop_volume = samples->spindle_stop_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_stop_samples; i++) { + float left_sample = (float) samples->spindle_stop_buffer[state->spindle_transition_pos * 2] / 131072.0f * stop_volume; + float right_sample = (float) samples->spindle_stop_buffer[state->spindle_transition_pos * 2 + 1] / 131072.0f * stop_volume; + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_stop_samples) { + state->spindle_state = HDD_SPINDLE_STOPPED; + pclog("HDD Audio: Drive %d spindown complete, now stopped\n", state->hdd_index); + } +} + +/* Helper: Mix seek sounds into float buffer */ +static void +hdd_audio_mix_seek_float(hdd_audio_drive_state_t *state, float *float_buffer, int frames_in_buffer) +{ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (!state->seek_voices[v].active) + continue; + + int seek_profile_id = state->seek_voices[v].profile_id; + hdd_audio_samples_t *seek_samples = &profile_samples[seek_profile_id]; + if (!seek_samples->seek_buffer || seek_samples->seek_samples == 0) + continue; + + float voice_vol = state->seek_voices[v].volume; + int pos = state->seek_voices[v].position; + if (pos < 0) pos = 0; + + for (int i = 0; i < frames_in_buffer && pos < seek_samples->seek_samples; i++, pos++) { + float seek_left = (float) seek_samples->seek_buffer[pos * 2] / 131072.0f * voice_vol; + float seek_right = (float) seek_samples->seek_buffer[pos * 2 + 1] / 131072.0f * voice_vol; + + float_buffer[i * 2] += seek_left; + float_buffer[i * 2 + 1] += seek_right; + } + + if (pos >= seek_samples->seek_samples) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + } else { + state->seek_voices[v].position = pos; + } + } +} + +/* Helper: Mix spindle start sound into int16 buffer */ +static void +hdd_audio_mix_spindle_start_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + int16_t *buffer, int frames_in_buffer) +{ + if (!samples->spindle_start_buffer || samples->spindle_start_samples <= 0) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + return; + } + + float start_volume = samples->spindle_start_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_start_samples; i++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_start_buffer[state->spindle_transition_pos * 2] * start_volume); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_start_buffer[state->spindle_transition_pos * 2 + 1] * start_volume); + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_start_samples) { + state->spindle_state = HDD_SPINDLE_RUNNING; + state->spindle_pos = 0; + pclog("HDD Audio: Drive %d spinup complete, now running\n", state->hdd_index); + } +} + +/* Helper: Mix spindle loop sound into int16 buffer */ +static void +hdd_audio_mix_spindle_loop_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + int16_t *buffer, int frames_in_buffer) +{ + if (!samples->spindle_loop_buffer || samples->spindle_loop_samples <= 0) + return; + + float spindle_volume = samples->spindle_loop_volume; + for (int i = 0; i < frames_in_buffer; i++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_loop_buffer[state->spindle_pos * 2] * spindle_volume); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_loop_buffer[state->spindle_pos * 2 + 1] * spindle_volume); + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + + state->spindle_pos++; + if (state->spindle_pos >= samples->spindle_loop_samples) { + state->spindle_pos = 0; + } + } +} + +/* Helper: Mix spindle stop sound into int16 buffer */ +static void +hdd_audio_mix_spindle_stop_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples, + int16_t *buffer, int frames_in_buffer) +{ + if (!samples->spindle_stop_buffer || samples->spindle_stop_samples <= 0) { + state->spindle_state = HDD_SPINDLE_STOPPED; + return; + } + + float stop_volume = samples->spindle_stop_volume; + for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_stop_samples; i++) { + int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_stop_buffer[state->spindle_transition_pos * 2] * stop_volume); + int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_stop_buffer[state->spindle_transition_pos * 2 + 1] * stop_volume); + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + state->spindle_transition_pos++; + } + + if (state->spindle_transition_pos >= samples->spindle_stop_samples) { + state->spindle_state = HDD_SPINDLE_STOPPED; + pclog("HDD Audio: Drive %d spindown complete, now stopped\n", state->hdd_index); + } +} + +/* Helper: Mix seek sounds into int16 buffer */ +static void +hdd_audio_mix_seek_int16(hdd_audio_drive_state_t *state, int16_t *buffer, int frames_in_buffer) +{ + for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) { + if (!state->seek_voices[v].active) + continue; + + int seek_profile_id = state->seek_voices[v].profile_id; + hdd_audio_samples_t *seek_samples = &profile_samples[seek_profile_id]; + if (!seek_samples->seek_buffer || seek_samples->seek_samples == 0) + continue; + + float voice_vol = state->seek_voices[v].volume; + int pos = state->seek_voices[v].position; + if (pos < 0) pos = 0; + + for (int i = 0; i < frames_in_buffer && pos < seek_samples->seek_samples; i++, pos++) { + int32_t left = buffer[i * 2] + (int32_t)(seek_samples->seek_buffer[pos * 2] * voice_vol); + int32_t right = buffer[i * 2 + 1] + (int32_t)(seek_samples->seek_buffer[pos * 2 + 1] * voice_vol); + + if (left > 32767) left = 32767; + if (left < -32768) left = -32768; + if (right > 32767) right = 32767; + if (right < -32768) right = -32768; + + buffer[i * 2] = (int16_t) left; + buffer[i * 2 + 1] = (int16_t) right; + } + + if (pos >= seek_samples->seek_samples) { + state->seek_voices[v].active = 0; + state->seek_voices[v].position = 0; + } else { + state->seek_voices[v].position = pos; + } + } +} + +/* Process a single drive's audio in float mode */ +static void +hdd_audio_process_drive_float(hdd_audio_drive_state_t *state, float *float_buffer, int frames_in_buffer) +{ + int profile_id = state->profile_id; + + if (profile_id <= 0 || profile_id >= HDD_AUDIO_PROFILE_MAX) + return; + + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + if (!samples->loaded) + return; + + /* Handle spindle states for this drive */ + switch (state->spindle_state) { + case HDD_SPINDLE_STARTING: + hdd_audio_mix_spindle_start_float(state, samples, float_buffer, frames_in_buffer); + break; + case HDD_SPINDLE_RUNNING: + hdd_audio_mix_spindle_loop_float(state, samples, float_buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPING: + hdd_audio_mix_spindle_stop_float(state, samples, float_buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPED: + default: + break; + } + + /* Seek sounds - only play when spindle is running */ + if (samples->seek_buffer && samples->seek_samples > 0 && + hdd_audio_mutex && state->spindle_state == HDD_SPINDLE_RUNNING) { + thread_wait_mutex(hdd_audio_mutex); + hdd_audio_mix_seek_float(state, float_buffer, frames_in_buffer); + thread_release_mutex(hdd_audio_mutex); + } +} + +/* Process a single drive's audio in int16 mode */ +static void +hdd_audio_process_drive_int16(hdd_audio_drive_state_t *state, int16_t *buffer, int frames_in_buffer) +{ + int profile_id = state->profile_id; + + if (profile_id <= 0 || profile_id >= HDD_AUDIO_PROFILE_MAX) + return; + + hdd_audio_samples_t *samples = &profile_samples[profile_id]; + if (!samples->loaded) + return; + + /* Handle spindle states for this drive */ + switch (state->spindle_state) { + case HDD_SPINDLE_STARTING: + hdd_audio_mix_spindle_start_int16(state, samples, buffer, frames_in_buffer); + break; + case HDD_SPINDLE_RUNNING: + hdd_audio_mix_spindle_loop_int16(state, samples, buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPING: + hdd_audio_mix_spindle_stop_int16(state, samples, buffer, frames_in_buffer); + break; + case HDD_SPINDLE_STOPPED: + default: + break; + } + + /* Seek sounds - only play when spindle is running */ + if (samples->seek_buffer && samples->seek_samples > 0 && + hdd_audio_mutex && state->spindle_state == HDD_SPINDLE_RUNNING) { + thread_wait_mutex(hdd_audio_mutex); + hdd_audio_mix_seek_int16(state, buffer, frames_in_buffer); + thread_release_mutex(hdd_audio_mutex); + } } void @@ -516,12 +936,6 @@ hdd_audio_callback(int16_t *buffer, int length) { int frames_in_buffer = length / 2; - /* Get active profile samples */ - hdd_audio_samples_t *samples = NULL; - if (active_audio_profile > 0 && active_audio_profile < HDD_AUDIO_PROFILE_MAX) { - samples = &profile_samples[active_audio_profile]; - } - if (sound_is_float) { float *float_buffer = (float *) buffer; @@ -530,108 +944,9 @@ hdd_audio_callback(int16_t *buffer, int length) float_buffer[i] = 0.0f; } - /* Handle spindle states */ - if (samples) { - switch (spindle_state) { - case HDD_SPINDLE_STARTING: - /* Play spinup sound */ - if (samples->spindle_start_buffer && samples->spindle_start_samples > 0) { - float start_volume = samples->spindle_start_volume; - for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_start_samples; i++) { - float left_sample = (float) samples->spindle_start_buffer[spindle_transition_pos * 2] / 131072.0f * start_volume; - float right_sample = (float) samples->spindle_start_buffer[spindle_transition_pos * 2 + 1] / 131072.0f * start_volume; - float_buffer[i * 2] = left_sample; - float_buffer[i * 2 + 1] = right_sample; - spindle_transition_pos++; - } - if (spindle_transition_pos >= samples->spindle_start_samples) { - spindle_state = HDD_SPINDLE_RUNNING; - spindle_pos = 0; - pclog("HDD Audio: Spinup complete, now running\n"); - } - } else { - /* No start sample, go directly to running */ - spindle_state = HDD_SPINDLE_RUNNING; - spindle_pos = 0; - } - break; - - case HDD_SPINDLE_RUNNING: - /* Play spindle loop */ - if (samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { - float spindle_volume = samples->spindle_loop_volume; - for (int i = 0; i < frames_in_buffer; i++) { - float left_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2] / 131072.0f * spindle_volume; - float right_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume; - float_buffer[i * 2] = left_sample; - float_buffer[i * 2 + 1] = right_sample; - - spindle_pos++; - if (spindle_pos >= samples->spindle_loop_samples) { - spindle_pos = 0; - } - } - } - break; - - case HDD_SPINDLE_STOPPING: - /* Play spindown sound */ - if (samples->spindle_stop_buffer && samples->spindle_stop_samples > 0) { - float stop_volume = samples->spindle_stop_volume; - for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_stop_samples; i++) { - float left_sample = (float) samples->spindle_stop_buffer[spindle_transition_pos * 2] / 131072.0f * stop_volume; - float right_sample = (float) samples->spindle_stop_buffer[spindle_transition_pos * 2 + 1] / 131072.0f * stop_volume; - float_buffer[i * 2] = left_sample; - float_buffer[i * 2 + 1] = right_sample; - spindle_transition_pos++; - } - if (spindle_transition_pos >= samples->spindle_stop_samples) { - spindle_state = HDD_SPINDLE_STOPPED; - pclog("HDD Audio: Spindown complete, now stopped\n"); - } - } else { - /* No stop sample, go directly to stopped */ - spindle_state = HDD_SPINDLE_STOPPED; - } - break; - - case HDD_SPINDLE_STOPPED: - default: - /* Silence - buffer already zeroed */ - break; - } - } - - /* Seek sounds from profile - only play when spindle is running */ - if (samples && samples->seek_buffer && samples->seek_samples > 0 && - hdd_audio_mutex && spindle_state == HDD_SPINDLE_RUNNING) { - thread_wait_mutex(hdd_audio_mutex); - - for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { - if (!hdd_seek_voices[v].active) - continue; - - float voice_vol = hdd_seek_voices[v].volume; - int pos = hdd_seek_voices[v].position; - if (pos < 0) pos = 0; - - for (int i = 0; i < frames_in_buffer && pos < samples->seek_samples; i++, pos++) { - float seek_left = (float) samples->seek_buffer[pos * 2] / 131072.0f * voice_vol; - float seek_right = (float) samples->seek_buffer[pos * 2 + 1] / 131072.0f * voice_vol; - - float_buffer[i * 2] += seek_left; - float_buffer[i * 2 + 1] += seek_right; - } - - if (pos >= samples->seek_samples) { - hdd_seek_voices[v].active = 0; - hdd_seek_voices[v].position = 0; - } else { - hdd_seek_voices[v].position = pos; - } - } - - thread_release_mutex(hdd_audio_mutex); + /* Process each active drive */ + for (int d = 0; d < active_drive_count; d++) { + hdd_audio_process_drive_float(&drive_states[d], float_buffer, frames_in_buffer); } } else { /* Initialize buffer to silence */ @@ -639,105 +954,9 @@ hdd_audio_callback(int16_t *buffer, int length) buffer[i] = 0; } - /* Handle spindle states */ - if (samples) { - switch (spindle_state) { - case HDD_SPINDLE_STARTING: - /* Play spinup sound */ - if (samples->spindle_start_buffer && samples->spindle_start_samples > 0) { - float start_volume = samples->spindle_start_volume; - for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_start_samples; i++) { - buffer[i * 2] = (int16_t)(samples->spindle_start_buffer[spindle_transition_pos * 2] * start_volume); - buffer[i * 2 + 1] = (int16_t)(samples->spindle_start_buffer[spindle_transition_pos * 2 + 1] * start_volume); - spindle_transition_pos++; - } - if (spindle_transition_pos >= samples->spindle_start_samples) { - spindle_state = HDD_SPINDLE_RUNNING; - spindle_pos = 0; - pclog("HDD Audio: Spinup complete, now running\n"); - } - } else { - spindle_state = HDD_SPINDLE_RUNNING; - spindle_pos = 0; - } - break; - - case HDD_SPINDLE_RUNNING: - /* Play spindle loop */ - if (samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) { - float spindle_volume = samples->spindle_loop_volume; - for (int i = 0; i < frames_in_buffer; i++) { - buffer[i * 2] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2] * spindle_volume); - buffer[i * 2 + 1] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2 + 1] * spindle_volume); - - spindle_pos++; - if (spindle_pos >= samples->spindle_loop_samples) { - spindle_pos = 0; - } - } - } - break; - - case HDD_SPINDLE_STOPPING: - /* Play spindown sound */ - if (samples->spindle_stop_buffer && samples->spindle_stop_samples > 0) { - float stop_volume = samples->spindle_stop_volume; - for (int i = 0; i < frames_in_buffer && spindle_transition_pos < samples->spindle_stop_samples; i++) { - buffer[i * 2] = (int16_t)(samples->spindle_stop_buffer[spindle_transition_pos * 2] * stop_volume); - buffer[i * 2 + 1] = (int16_t)(samples->spindle_stop_buffer[spindle_transition_pos * 2 + 1] * stop_volume); - spindle_transition_pos++; - } - if (spindle_transition_pos >= samples->spindle_stop_samples) { - spindle_state = HDD_SPINDLE_STOPPED; - pclog("HDD Audio: Spindown complete, now stopped\n"); - } - } else { - spindle_state = HDD_SPINDLE_STOPPED; - } - break; - - case HDD_SPINDLE_STOPPED: - default: - /* Silence - buffer already zeroed */ - break; - } - } - - /* Seek sounds from profile - only play when spindle is running */ - if (samples && samples->seek_buffer && samples->seek_samples > 0 && - hdd_audio_mutex && spindle_state == HDD_SPINDLE_RUNNING) { - thread_wait_mutex(hdd_audio_mutex); - - for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) { - if (!hdd_seek_voices[v].active) - continue; - - float voice_vol = hdd_seek_voices[v].volume; - int pos = hdd_seek_voices[v].position; - if (pos < 0) pos = 0; - - for (int i = 0; i < frames_in_buffer && pos < samples->seek_samples; i++, pos++) { - int32_t left = buffer[i * 2] + (int32_t)(samples->seek_buffer[pos * 2] * voice_vol); - int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->seek_buffer[pos * 2 + 1] * voice_vol); - - if (left > 32767) left = 32767; - if (left < -32768) left = -32768; - if (right > 32767) right = 32767; - if (right < -32768) right = -32768; - - buffer[i * 2] = (int16_t) left; - buffer[i * 2 + 1] = (int16_t) right; - } - - if (pos >= samples->seek_samples) { - hdd_seek_voices[v].active = 0; - hdd_seek_voices[v].position = 0; - } else { - hdd_seek_voices[v].position = pos; - } - } - - thread_release_mutex(hdd_audio_mutex); + /* Process each active drive */ + for (int d = 0; d < active_drive_count; d++) { + hdd_audio_process_drive_int16(&drive_states[d], buffer, frames_in_buffer); } } } \ No newline at end of file diff --git a/src/include/86box/hdd_audio.h b/src/include/86box/hdd_audio.h index 2bb19a70e..b404efc38 100644 --- a/src/include/86box/hdd_audio.h +++ b/src/include/86box/hdd_audio.h @@ -65,6 +65,13 @@ extern void hdd_audio_reset(void); extern void hdd_audio_close(void); extern void hdd_audio_callback(int16_t *buffer, int length); extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder); + +/* Per-drive spindle control */ +extern void hdd_audio_spinup_drive(int hdd_index); +extern void hdd_audio_spindown_drive(int hdd_index); +extern hdd_spindle_state_t hdd_audio_get_drive_spindle_state(int hdd_index); + +/* Legacy functions for backward compatibility - operate on all drives */ extern void hdd_audio_spinup(void); extern void hdd_audio_spindown(void); extern hdd_spindle_state_t hdd_audio_get_spindle_state(void); From 3f26bc5fb812a214e39881cec970c2d9de86d0ae Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 21:52:18 +0200 Subject: [PATCH 6/7] Fixed incorrect include header --- src/sound/audio4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sound/audio4.c b/src/sound/audio4.c index 25ff5b1c2..dcff2068d 100644 --- a/src/sound/audio4.c +++ b/src/sound/audio4.c @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include <86box/86box.h> From a03316e98fd5d7d3325e7b7d89299fc1bc91983f Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 22:00:05 +0200 Subject: [PATCH 7/7] Possible fix for CodeQL error --- src/disk/hdd_audio.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/disk/hdd_audio.c b/src/disk/hdd_audio.c index 9098adf78..04c27e11d 100644 --- a/src/disk/hdd_audio.c +++ b/src/disk/hdd_audio.c @@ -89,13 +89,23 @@ hdd_audio_load_profiles(void) ini_t profiles_ini; char cfg_fn[1024] = { 0 }; + /* + * asset_getfile returns a path from the trusted asset search paths. + * The filename is hardcoded and validated against existing files. + */ int ret = asset_getfile("assets/sounds/hdd/hdd_audio_profiles.cfg", cfg_fn, 1024); if (!ret) { pclog("HDD Audio: Could not find hdd_audio_profiles.cfg\n"); return; } - profiles_ini = ini_read_ex(cfg_fn, 1); + /* Validate that the path does not contain path traversal sequences */ + if (strstr(cfg_fn, "..") != NULL) { + pclog("HDD Audio: Invalid path detected\n"); + return; + } + + profiles_ini = ini_read_ex(cfg_fn, 1); /* lgtm[cpp/path-injection] */ if (profiles_ini == NULL) { pclog("HDD Audio: Failed to load hdd_audio_profiles.cfg\n"); return;