From 419ee5cbd9a244615778aec12eac87955454fd42 Mon Sep 17 00:00:00 2001 From: Domppari Date: Sun, 4 Jan 2026 11:15:22 +0200 Subject: [PATCH] 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 }