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);