From 56ffe0cab8c18d5b123986bbcb3449d4b37adba9 Mon Sep 17 00:00:00 2001 From: Toni Riikonen Date: Tue, 28 Oct 2025 21:38:57 +0200 Subject: [PATCH] Separate audio sample support for each seek 0-79, 79->0. --- src/floppy/fdd.c | 16 +- src/floppy/fdd_audio.c | 782 +++++++++++++++------------------- src/include/86box/fdd_audio.h | 20 +- 3 files changed, 364 insertions(+), 454 deletions(-) diff --git a/src/floppy/fdd.c b/src/floppy/fdd.c index 59f1dc916..bde431d69 100644 --- a/src/floppy/fdd.c +++ b/src/floppy/fdd.c @@ -390,10 +390,7 @@ fdd_seek(int drive, int track_diff) else { /* Trigger appropriate audio for track movements */ int actual_track_diff = abs(old_track - fdd[drive].track); - if (actual_track_diff == 1) { - /* Single track movement */ - fdd_audio_play_single_track_step(drive, old_track, fdd[drive].track); - } else if (actual_track_diff > 1) { + if (actual_track_diff > 0) { /* Multi-track seek */ fdd_audio_play_multi_track_seek(drive, old_track, fdd[drive].track); } @@ -413,12 +410,11 @@ fdd_seek(int drive, int track_diff) int is_seek_down = (fdd[drive].track < old_track); /* Get seek timings from audio profile configuration with direction awareness */ - double initial_seek_time = fdd_audio_get_seek_time(drive, 1, actual_track_diff, is_seek_down); - double track_seek_time = fdd_audio_get_seek_time(drive, 0, actual_track_diff, is_seek_down); - fdd_log("Seek timing for drive %d: initial %.2f µs, per track %.2f µs (%s)\n", - drive, initial_seek_time, track_seek_time, is_seek_down ? "DOWN" : "UP"); - uint64_t seek_time_us = (initial_seek_time + (abs(actual_track_diff) * track_seek_time)) * TIMER_USEC; - timer_set_delay_u64(&fdd_seek_timer[drive], seek_time_us); + double seek_time_us = fdd_audio_get_seek_time(drive, 1, actual_track_diff, is_seek_down); + fdd_log("Seek timing for drive %d: %.2f µs (%s)\n", + drive, seek_time_us, is_seek_down ? "DOWN" : "UP"); + uint64_t seek_delay_us = seek_time_us * TIMER_USEC; + timer_set_delay_u64(&fdd_seek_timer[drive], seek_delay_us); } } diff --git a/src/floppy/fdd_audio.c b/src/floppy/fdd_audio.c index c4403093f..5cb9455c0 100644 --- a/src/floppy/fdd_audio.c +++ b/src/floppy/fdd_audio.c @@ -36,14 +36,14 @@ /* Global audio profile configurations */ static fdd_audio_profile_config_t audio_profiles[FDD_AUDIO_PROFILE_MAX]; -static int audio_profile_count = 0; +static int audio_profile_count = 0; /* Audio sample structure */ typedef struct { - char filename[512]; + char filename[512]; int16_t *buffer; - int samples; - float volume; + int samples; + float volume; } audio_sample_t; typedef struct { @@ -65,10 +65,9 @@ typedef struct { audio_sample_t spindlemotor_start; audio_sample_t spindlemotor_loop; audio_sample_t spindlemotor_stop; - audio_sample_t single_track_step; - audio_sample_t single_track_step_down; - audio_sample_t multi_track_seek; - audio_sample_t multi_track_seek_down; + /* Individual seek samples for each track count (indexed 0-78 for 1-79 tracks) */ + audio_sample_t seek_up[MAX_SEEK_SAMPLES]; + audio_sample_t seek_down[MAX_SEEK_SAMPLES]; } drive_audio_samples_t; /* Dynamic sample storage for each profile */ @@ -80,11 +79,8 @@ static motor_state_t spindlemotor_state[FDD_NUM] = {}; static float spindlemotor_fade_volume[FDD_NUM] = {}; static int spindlemotor_fade_samples_remaining[FDD_NUM] = {}; -/* Single step audio state for each drive */ -static single_step_state_t single_step_state[FDD_NUM] = {}; - /* Multi-track seek audio state for each drive */ -static multi_seek_state_t multi_seek_state[FDD_NUM] = {}; +static multi_seek_state_t seek_state[FDD_NUM] = {}; extern uint64_t motoron[FDD_NUM]; extern char exe_path[2048]; @@ -94,7 +90,7 @@ extern int fdd_get_audio_profile(int drive); /* Forward declaration */ static int16_t *load_wav(const char *filename, int *sample_count); -#ifdef ENABLE_FDD_LOG +# ifdef ENABLE_FDD_LOG int fdc_do_log = ENABLE_FDD_LOG; static void @@ -114,7 +110,7 @@ fdd_log(const char *fmt, ...) /* Logging function for audio profile parameters */ static void -fdd_audio_log_profile_params(int drive, const fdd_audio_profile_config_t* profile) +fdd_audio_log_profile_params(int drive, const fdd_audio_profile_config_t *profile) { if (!profile) { fdd_log("FDD Audio Drive %d: No profile assigned\n", drive); @@ -125,32 +121,30 @@ fdd_audio_log_profile_params(int drive, const fdd_audio_profile_config_t* profil fdd_log(" Profile ID: %d\n", profile->id); fdd_log(" Profile Name: %s\n", profile->name); fdd_log(" Internal Name: %s\n", profile->internal_name); - + fdd_log(" Sample Files:\n"); - fdd_log(" Spindle Start: %s (volume: %.2f)\n", - profile->spindlemotor_start.filename, profile->spindlemotor_start.volume); - fdd_log(" Spindle Loop: %s (volume: %.2f)\n", - profile->spindlemotor_loop.filename, profile->spindlemotor_loop.volume); - fdd_log(" Spindle Stop: %s (volume: %.2f)\n", - profile->spindlemotor_stop.filename, profile->spindlemotor_stop.volume); - fdd_log(" Single Step: %s (volume: %.2f)\n", - profile->single_track_step.filename, profile->single_track_step.volume); - fdd_log(" Single Step Down: %s (volume: %.2f)\n", - profile->single_track_step_down.filename, profile->single_track_step_down.volume); - fdd_log(" Multi Seek: %s (volume: %.2f)\n", - profile->multi_track_seek.filename, profile->multi_track_seek.volume); - fdd_log(" Multi Seek Down: %s (volume: %.2f)\n", - profile->multi_track_seek_down.filename, profile->multi_track_seek_down.volume); - - fdd_log(" Timing Parameters:\n"); - fdd_log(" Samples Per Track: %d samples\n", profile->samples_per_track); - fdd_log(" Total Tracks: %d\n", profile->total_tracks); - fdd_log(" Initial Seek Time: %.1f µs\n", profile->initial_seek_time); - fdd_log(" Initial Seek Time (PCjr): %.1f µs\n", profile->initial_seek_time_pcjr); - fdd_log(" Track Seek Time: %.1f µs\n", profile->track_seek_time); - fdd_log(" Track Seek Time (PCjr): %.1f µs\n", profile->track_seek_time_pcjr); - fdd_log(" Initial Seek Down Time: %.1f µs\n", profile->initial_seek_down_time); - fdd_log(" Track Seek Down Time: %.1f µs\n", profile->track_seek_down_time); + fdd_log(" Spindle Start: %s (volume: %.2f)\n", + profile->spindlemotor_start.filename, profile->spindlemotor_start.volume); + fdd_log(" Spindle Loop: %s (volume: %.2f)\n", + profile->spindlemotor_loop.filename, profile->spindlemotor_loop.volume); + fdd_log(" Spindle Stop: %s (volume: %.2f)\n", + profile->spindlemotor_stop.filename, profile->spindlemotor_stop.volume); + + /* Log a few sample seek files as examples */ + int max_tracks = (profile->total_tracks == 40) ? 39 : 79; + fdd_log(" Individual seek samples (up to %d tracks):\n", max_tracks); + for (int i = 0; i < max_tracks && i < 5; i++) { + if (profile->seek_up[i].filename[0]) { + fdd_log(" Seek up %d track(s): %s (volume: %.2f)\n", + i + 1, profile->seek_up[i].filename, profile->seek_up[i].volume); + } + if (profile->seek_down[i].filename[0]) { + fdd_log(" Seek down %d track(s): %s (volume: %.2f)\n", + i + 1, profile->seek_down[i].filename, profile->seek_down[i].volume); + } + } + if (max_tracks > 5) + fdd_log(" ... and %d more seek samples\n", (max_tracks - 5) * 2); } /* Log audio profile parameters for a specific drive */ @@ -162,9 +156,9 @@ fdd_audio_log_drive_profile(int drive) return; } - int profile_id = fdd_get_audio_profile(drive); - const fdd_audio_profile_config_t* profile = fdd_audio_get_profile(profile_id); - + int profile_id = fdd_get_audio_profile(drive); + const fdd_audio_profile_config_t *profile = fdd_audio_get_profile(profile_id); + fdd_log("FDD Audio Drive %d: Using profile %d\n", drive, profile_id); fdd_audio_log_profile_params(drive, profile); } @@ -174,32 +168,32 @@ static void fdd_audio_log_active_profiles(void) { fdd_log("FDD Audio: Checking active drive configurations...\n"); - int active_drive_count = 0; - + int active_drive_count = 0; + for (int drive = 0; drive < FDD_NUM; drive++) { if (fdd_get_type(drive) == 0) continue; - + active_drive_count++; - int profile_id = fdd_get_audio_profile(drive); + int profile_id = fdd_get_audio_profile(drive); if (profile_id >= 0 && profile_id < audio_profile_count) { fdd_log("FDD Audio: Drive %d (configured) uses profile %d\n", drive, profile_id); fdd_audio_log_profile_params(drive, &audio_profiles[profile_id]); } } - + if (active_drive_count == 0) { fdd_log("FDD Audio: No drives configured - no audio profiles to log\n"); return; } - + fdd_log("FDD Audio: Active audio profiles for %d configured drive(s):\n", active_drive_count); } void fdd_audio_load_profiles(void) { - char config_path[2048]; + char config_path[2048]; ini_t profiles_ini; /* Validate exe_path to prevent directory traversal attacks */ @@ -213,7 +207,7 @@ fdd_audio_load_profiles(void) fdd_log("FDD Audio: Directory traversal detected in exe_path\n"); return; } - + path_append_filename(config_path, exe_path, "roms/floppy/fdd_audio_profiles.cfg"); /* Additional validation of the final path */ @@ -229,16 +223,16 @@ fdd_audio_load_profiles(void) } audio_profile_count = 0; - + /* Load profiles by trying known profile section names */ for (int i = 0; i < FDD_AUDIO_PROFILE_MAX && audio_profile_count < FDD_AUDIO_PROFILE_MAX; i++) { char section_name[64]; snprintf(section_name, sizeof(section_name), "Profile \"%d\"", i); - + ini_section_t section = ini_find_section(profiles_ini, section_name); if (section) { fdd_audio_profile_config_t *profile = &audio_profiles[audio_profile_count]; - + /* Load profile configuration */ profile->id = ini_section_get_int(section, "id", audio_profile_count); @@ -266,164 +260,133 @@ fdd_audio_load_profiles(void) profile->spindlemotor_stop.filename[sizeof(profile->spindlemotor_stop.filename) - 1] = '\0'; profile->spindlemotor_stop.volume = ini_section_get_double(section, "spindlemotor_stop_volume", 1.0); - filename = ini_section_get_string(section, "single_track_step_file", ""); - strncpy(profile->single_track_step.filename, filename, sizeof(profile->single_track_step.filename) - 1); - profile->single_track_step.filename[sizeof(profile->single_track_step.filename) - 1] = '\0'; - profile->single_track_step.volume = ini_section_get_double(section, "single_track_step_volume", 1.0); + /* Load seek samples for each track count */ + for (int track_count = 1; track_count <= MAX_SEEK_SAMPLES; track_count++) { + char key[128]; - filename = ini_section_get_string(section, "single_track_step_down_file", ""); - strncpy(profile->single_track_step_down.filename, filename, sizeof(profile->single_track_step_down.filename) - 1); - profile->single_track_step_down.filename[sizeof(profile->single_track_step_down.filename) - 1] = '\0'; - profile->single_track_step_down.volume = ini_section_get_double(section, "single_track_step_volume", 1.0); + /* Seek up samples */ + snprintf(key, sizeof(key), "seek_up_%dtrack_file", track_count); + filename = ini_section_get_string(section, key, ""); + strncpy(profile->seek_up[track_count - 1].filename, filename, + sizeof(profile->seek_up[track_count - 1].filename) - 1); + profile->seek_up[track_count - 1].filename[sizeof(profile->seek_up[track_count - 1].filename) - 1] = '\0'; - filename = ini_section_get_string(section, "multi_track_seek_file", ""); - strncpy(profile->multi_track_seek.filename, filename, sizeof(profile->multi_track_seek.filename) - 1); - profile->multi_track_seek.filename[sizeof(profile->multi_track_seek.filename) - 1] = '\0'; - profile->multi_track_seek.volume = ini_section_get_double(section, "multi_track_seek_volume", 1.0); + snprintf(key, sizeof(key), "seek_up_%dtrack_volume", track_count); + profile->seek_up[track_count - 1].volume = ini_section_get_double(section, key, 1.0); - filename = ini_section_get_string(section, "multi_track_seek_down_file", ""); - strncpy(profile->multi_track_seek_down.filename, filename, sizeof(profile->multi_track_seek_down.filename) - 1); - profile->multi_track_seek_down.filename[sizeof(profile->multi_track_seek_down.filename) - 1] = '\0'; - profile->multi_track_seek_down.volume = ini_section_get_double(section, "multi_track_seek_volume", 1.0); + /* Seek down samples */ + snprintf(key, sizeof(key), "seek_down_%dtrack_file", track_count); + filename = ini_section_get_string(section, key, ""); + strncpy(profile->seek_down[track_count - 1].filename, filename, + sizeof(profile->seek_down[track_count - 1].filename) - 1); + profile->seek_down[track_count - 1].filename[sizeof(profile->seek_down[track_count - 1].filename) - 1] = '\0'; + + snprintf(key, sizeof(key), "seek_down_%dtrack_volume", track_count); + profile->seek_down[track_count - 1].volume = ini_section_get_double(section, key, 1.0); + } /* Load timing configurations */ - profile->samples_per_track = ini_section_get_int(section, "samples_per_track", 297); - profile->total_tracks = ini_section_get_int(section, "total_tracks", 80); - profile->initial_seek_time = ini_section_get_double(section, "initial_seek_time", 15000.0); - profile->initial_seek_time_pcjr = ini_section_get_double(section, "initial_seek_time_pcjr", 40000.0); - profile->track_seek_time = ini_section_get_double(section, "track_seek_time", 6000.0); - profile->track_seek_time_pcjr = ini_section_get_double(section, "track_seek_time_pcjr", 10000.0); - profile->initial_seek_down_time = ini_section_get_double(section, "initial_seek_down_time", 15000.0); - profile->track_seek_down_time = ini_section_get_double(section, "track_seek_down_time", 6000.0); + profile->total_tracks = ini_section_get_int(section, "total_tracks", 80); + audio_profile_count++; } } - + ini_close(profiles_ini); - + fdd_log("FDD Audio: Loaded %d audio profiles from %s\n", audio_profile_count, config_path); } -static void load_profile_samples(int profile_id) { +static void +load_profile_samples(int profile_id) +{ if (profile_id <= 0 || profile_id >= audio_profile_count) return; - - fdd_audio_profile_config_t *config = &audio_profiles[profile_id]; - drive_audio_samples_t *samples = &profile_samples[profile_id]; - - fdd_log("FDD Audio: Loading samples for profile %d (%s)\n", - profile_id, config->name); - + + fdd_audio_profile_config_t *config = &audio_profiles[profile_id]; + drive_audio_samples_t *samples = &profile_samples[profile_id]; + + fdd_log("FDD Audio: Loading samples for profile %d (%s)\n", + profile_id, config->name); + /* Load samples if not already loaded */ 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.samples); + samples->spindlemotor_start.buffer = 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", - config->spindlemotor_start.filename, - samples->spindlemotor_start.samples, - config->spindlemotor_start.volume); + fdd_log(" Loaded spindlemotor_start: %s (%d samples, volume %.2f)\n", + config->spindlemotor_start.filename, + samples->spindlemotor_start.samples, + config->spindlemotor_start.volume); } else { - fdd_log(" Failed to load spindlemotor_start: %s\n", - config->spindlemotor_start.filename); + fdd_log(" Failed to load spindlemotor_start: %s\n", + config->spindlemotor_start.filename); } } - + 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.samples); + samples->spindlemotor_loop.buffer = 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", - config->spindlemotor_loop.filename, - samples->spindlemotor_loop.samples, - config->spindlemotor_loop.volume); + fdd_log(" Loaded spindlemotor_loop: %s (%d samples, volume %.2f)\n", + config->spindlemotor_loop.filename, + samples->spindlemotor_loop.samples, + config->spindlemotor_loop.volume); } else { - fdd_log(" Failed to load spindlemotor_loop: %s\n", - config->spindlemotor_loop.filename); + fdd_log(" Failed to load spindlemotor_loop: %s\n", + config->spindlemotor_loop.filename); } } - + 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.samples); + samples->spindlemotor_stop.buffer = 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", - config->spindlemotor_stop.filename, - samples->spindlemotor_stop.samples, - config->spindlemotor_stop.volume); + fdd_log(" Loaded spindlemotor_stop: %s (%d samples, volume %.2f)\n", + config->spindlemotor_stop.filename, + samples->spindlemotor_stop.samples, + config->spindlemotor_stop.volume); } else { - fdd_log(" Failed to load spindlemotor_stop: %s\n", - config->spindlemotor_stop.filename); + fdd_log(" Failed to load spindlemotor_stop: %s\n", + config->spindlemotor_stop.filename); } } - - if (samples->single_track_step.buffer == NULL && config->single_track_step.filename[0]) { - strcpy(samples->single_track_step.filename, config->single_track_step.filename); - samples->single_track_step.volume = config->single_track_step.volume; - samples->single_track_step.buffer = load_wav(config->single_track_step.filename, - &samples->single_track_step.samples); - if (samples->single_track_step.buffer) { - fdd_log(" Loaded single_track_step: %s (%d samples, volume %.2f)\n", - config->single_track_step.filename, - samples->single_track_step.samples, - config->single_track_step.volume); - } else { - fdd_log(" Failed to load single_track_step: %s\n", - config->single_track_step.filename); + + /* Load individual seek samples for each track count */ + int max_tracks = (config->total_tracks == 40) ? 39 : 79; + for (int track_count = 1; track_count <= max_tracks; track_count++) { + int idx = track_count - 1; + + /* Load seek up sample */ + 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].samples); + if (samples->seek_up[idx].buffer) { + fdd_log(" Loaded seek_up[%d]: %s (%d samples, volume %.2f)\n", + track_count, config->seek_up[idx].filename, + samples->seek_up[idx].samples, config->seek_up[idx].volume); + } } - } - - if (samples->single_track_step_down.buffer == NULL && config->single_track_step_down.filename[0]) { - strcpy(samples->single_track_step_down.filename, config->single_track_step_down.filename); - samples->single_track_step_down.volume = config->single_track_step_down.volume; - samples->single_track_step_down.buffer = load_wav(config->single_track_step_down.filename, - &samples->single_track_step_down.samples); - if (samples->single_track_step_down.buffer) { - fdd_log(" Loaded single_track_step_down: %s (%d samples, volume %.2f)\n", - config->single_track_step_down.filename, - samples->single_track_step_down.samples, - config->single_track_step_down.volume); - } else { - fdd_log(" Failed to load single_track_step_down: %s\n", - config->single_track_step_down.filename); - } - } - - if (samples->multi_track_seek.buffer == NULL && config->multi_track_seek.filename[0]) { - strcpy(samples->multi_track_seek.filename, config->multi_track_seek.filename); - samples->multi_track_seek.volume = config->multi_track_seek.volume; - samples->multi_track_seek.buffer = load_wav(config->multi_track_seek.filename, - &samples->multi_track_seek.samples); - if (samples->multi_track_seek.buffer) { - fdd_log(" Loaded multi_track_seek: %s (%d samples, volume %.2f)\n", - config->multi_track_seek.filename, - samples->multi_track_seek.samples, - config->multi_track_seek.volume); - } else { - fdd_log(" Failed to load multi_track_seek: %s\n", - config->multi_track_seek.filename); - } - } - - if (samples->multi_track_seek_down.buffer == NULL && config->multi_track_seek_down.filename[0]) { - strcpy(samples->multi_track_seek_down.filename, config->multi_track_seek_down.filename); - samples->multi_track_seek_down.volume = config->multi_track_seek_down.volume; - samples->multi_track_seek_down.buffer = load_wav(config->multi_track_seek_down.filename, - &samples->multi_track_seek_down.samples); - if (samples->multi_track_seek_down.buffer) { - fdd_log(" Loaded multi_track_seek_down: %s (%d samples, volume %.2f)\n", - config->multi_track_seek_down.filename, - samples->multi_track_seek_down.samples, - config->multi_track_seek_down.volume); - } else { - fdd_log(" Failed to load multi_track_seek_down: %s\n", - config->multi_track_seek_down.filename); + + /* Load seek down sample */ + 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].samples); + if (samples->seek_down[idx].buffer) { + fdd_log(" Loaded seek_down[%d]: %s (%d samples, volume %.2f)\n", + track_count, config->seek_down[idx].filename, + samples->seek_down[idx].samples, config->seek_down[idx].volume); + } } } } @@ -440,23 +403,31 @@ get_drive_samples(int drive) } /* Public API functions */ -int fdd_audio_get_profile_count(void) { +int +fdd_audio_get_profile_count(void) +{ return audio_profile_count; } -const fdd_audio_profile_config_t* fdd_audio_get_profile(int id) { +const fdd_audio_profile_config_t * +fdd_audio_get_profile(int id) +{ if (id < 0 || id >= audio_profile_count) return NULL; return &audio_profiles[id]; } -const char* fdd_audio_get_profile_name(int id) { +const char * +fdd_audio_get_profile_name(int id) +{ if (id < 0 || id >= audio_profile_count) return NULL; return audio_profiles[id].name; } -const char* fdd_audio_get_profile_internal_name(int id) { +const char * +fdd_audio_get_profile_internal_name(int id) +{ if (id < 0 || id >= audio_profile_count) return NULL; return audio_profiles[id].internal_name; @@ -478,28 +449,33 @@ fdd_audio_get_profile_by_internal_name(const char *internal_name) return 0; } -double fdd_audio_get_seek_time(int drive, int is_initial, int track_count, int is_seek_down) { +double +fdd_audio_get_seek_time(int drive, int is_initial, int track_count, int is_seek_down) +{ int profile_id = fdd_get_audio_profile(drive); if (profile_id <= 0 || profile_id >= audio_profile_count) { - /* Return default values */ + /* Return default values when no profile */ return is_initial ? 15000.0 : 6000.0; } - - fdd_audio_profile_config_t *profile = &audio_profiles[profile_id]; - - /* Check if using PCjr timing */ - extern fdc_t *fdd_fdc; - int is_pcjr = (fdd_fdc && (fdd_fdc->flags & FDC_FLAG_PCJR)); - - if (is_pcjr) { - return is_initial ? profile->initial_seek_time_pcjr : profile->track_seek_time_pcjr; - } - if (is_seek_down) { - return is_initial ? profile->initial_seek_down_time : profile->track_seek_down_time; + /* Get the loaded samples for this profile */ + drive_audio_samples_t *samples = &profile_samples[profile_id]; + if (!samples) + return is_initial ? 15000.0 : 6000.0; + + /* For multi-track seeks, return the appropriate sample duration */ + if (track_count > 0 && track_count <= MAX_SEEK_SAMPLES) { + int idx = track_count - 1; + audio_sample_t *sample = is_seek_down ? &samples->seek_down[idx] : &samples->seek_up[idx]; + + if (sample && sample->buffer && sample->samples > 0) { + /* Convert samples to microseconds (48kHz sample rate) */ + return (double) sample->samples * 1000000.0 / 48000.0; + } } - return is_initial ? profile->initial_seek_time : profile->track_seek_time; + /* Fallback: estimate based on track count */ + return is_initial ? 15000.0 : (6000.0 * track_count); } void @@ -515,16 +491,12 @@ fdd_audio_init(void) spindlemotor_fade_volume[i] = 1.0f; spindlemotor_fade_samples_remaining[i] = 0; - /* Initialize single step state */ - single_step_state[i].position = 0; - single_step_state[i].active = 0; - /* Initialize multi-track seek state */ - multi_seek_state[i].position = 0; - multi_seek_state[i].active = 0; - multi_seek_state[i].duration_samples = 0; - multi_seek_state[i].from_track = -1; - multi_seek_state[i].to_track = -1; + seek_state[i].position = 0; + seek_state[i].active = 0; + seek_state[i].duration_samples = 0; + seek_state[i].from_track = -1; + seek_state[i].to_track = -1; } /* Preload audio samples for each drive's selected profile */ @@ -540,7 +512,7 @@ fdd_audio_init(void) /* Initialize sound thread */ sound_fdd_thread_init(); - + fdd_log("FDD Audio: Initialization complete\n"); } @@ -548,7 +520,7 @@ void fdd_audio_close(void) { fdd_log("FDD Audio: Shutting down audio system\n"); - + /* Free loaded profile samples */ for (int profile_id = 0; profile_id < audio_profile_count; profile_id++) { drive_audio_samples_t *samples = &profile_samples[profile_id]; @@ -568,30 +540,24 @@ fdd_audio_close(void) samples->spindlemotor_stop.buffer = NULL; samples->spindlemotor_stop.samples = 0; } - if (samples->single_track_step.buffer) { - free(samples->single_track_step.buffer); - samples->single_track_step.buffer = NULL; - samples->single_track_step.samples = 0; - } - if (samples->single_track_step_down.buffer) { - free(samples->single_track_step_down.buffer); - samples->single_track_step_down.buffer = NULL; - samples->single_track_step_down.samples = 0; - } - if (samples->multi_track_seek.buffer) { - free(samples->multi_track_seek.buffer); - samples->multi_track_seek.buffer = NULL; - samples->multi_track_seek.samples = 0; - } - if (samples->multi_track_seek_down.buffer) { - free(samples->multi_track_seek_down.buffer); - samples->multi_track_seek_down.buffer = NULL; - samples->multi_track_seek_down.samples = 0; + + /* Free individual seek samples */ + for (int track_count = 0; track_count < MAX_SEEK_SAMPLES; track_count++) { + if (samples->seek_up[track_count].buffer) { + free(samples->seek_up[track_count].buffer); + samples->seek_up[track_count].buffer = NULL; + samples->seek_up[track_count].samples = 0; + } + if (samples->seek_down[track_count].buffer) { + free(samples->seek_down[track_count].buffer); + samples->seek_down[track_count].buffer = NULL; + samples->seek_down[track_count].samples = 0; + } } } sound_fdd_thread_end(); - + fdd_log("FDD Audio: Shutdown complete\n"); } @@ -634,41 +600,6 @@ fdd_audio_set_motor_enable(int drive, int motor_enable) } } -void -fdd_audio_play_single_track_step(int drive, int from_track, int to_track) -{ - if (!fdd_sounds_enabled || fdd_get_turbo(drive)) - return; - - if (drive < 0 || drive >= FDD_NUM) - return; - if (abs(from_track - to_track) != 1) - return; /* Only single track movements */ - - drive_audio_samples_t *samples = get_drive_samples(drive); - if (!samples) - return; - - int is_seek_down = (to_track < from_track); - - fdd_log("FDD Audio Drive %d: Single track step %d -> %d (%s)\n", - drive, from_track, to_track, is_seek_down ? "DOWN" : "UP"); - - /* Choose the appropriate sample based on direction */ - audio_sample_t *sample_to_use; - if (is_seek_down && samples->single_track_step_down.buffer) { - sample_to_use = &samples->single_track_step_down; - } else { - sample_to_use = &samples->single_track_step; - } - - /* Only play if we have the appropriate sample */ - if (sample_to_use && sample_to_use->buffer) { - single_step_state[drive].position = 0; - single_step_state[drive].active = 1; - } -} - void fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) { @@ -679,58 +610,62 @@ fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) return; int track_diff = abs(from_track - to_track); - if (track_diff <= 1) - return; /* Use single step for 1 track movements */ + if (track_diff < 1) + return; drive_audio_samples_t *samples = get_drive_samples(drive); if (!samples) return; int is_seek_down = (to_track < from_track); - - /* Choose the appropriate sample based on direction */ - audio_sample_t *sample_to_use; - if (is_seek_down && samples->multi_track_seek_down.buffer) { - sample_to_use = &samples->multi_track_seek_down; - } else { - sample_to_use = &samples->multi_track_seek; + + /* Get the profile to check total_tracks */ + int profile_id = fdd_get_audio_profile(drive); + if (profile_id < 1 || profile_id >= audio_profile_count) + return; + + fdd_audio_profile_config_t *profile = &audio_profiles[profile_id]; + + /* Determine the maximum available seek sample for this profile */ + int max_seek_tracks = (profile->total_tracks == 40) ? 39 : 79; + + /* Clamp track_diff to the maximum available sample + * This allows 40-track drives to seek beyond track 39 (e.g., to track 42) + * using the 39-track sample, and 80-track drives to seek beyond track 79 + * (e.g., to track 83) using the 79-track sample */ + if (track_diff > max_seek_tracks) { + fdd_log("FDD Audio Drive %d: Seek request for %d tracks exceeds maximum %d, clamping to %d\n", + drive, track_diff, max_seek_tracks, max_seek_tracks); + track_diff = max_seek_tracks; } + /* Get the appropriate seek sample */ + int idx = track_diff - 1; /* Array is 0-indexed, track counts start at 1 */ + audio_sample_t *sample_to_use = is_seek_down ? &samples->seek_down[idx] : &samples->seek_up[idx]; + /* Only proceed if we have the appropriate sample */ if (!sample_to_use || !sample_to_use->buffer || sample_to_use->samples == 0) return; /* Check if a seek is already active */ - if (multi_seek_state[drive].active && - multi_seek_state[drive].from_track == from_track && - multi_seek_state[drive].to_track == to_track) { + if (seek_state[drive].active && seek_state[drive].from_track == from_track && seek_state[drive].to_track == to_track) { return; } - fdd_log("FDD Audio Drive %d: Multi-track seek %d -> %d (%d tracks, %s)\n", - drive, from_track, to_track, track_diff, is_seek_down ? "DOWN" : "UP"); + fdd_log("FDD Audio Drive %d: Multi-track seek %d -> %d (%d tracks, %s)\n", + drive, from_track, to_track, track_diff, is_seek_down ? "DOWN" : "UP"); - /* Get timing from configuration */ - int profile_id = fdd_get_audio_profile(drive); - int duration_samples; - - if (profile_id < 1 || profile_id >= audio_profile_count) - return; - - /* Use configured timing */ - duration_samples = track_diff * audio_profiles[profile_id].samples_per_track; - fdd_log("FDD Audio Drive %d: Seek duration %d samples (%d tracks * %d samples/track)\n", - drive, duration_samples, track_diff, audio_profiles[profile_id].samples_per_track); - /* Clamp to maximum available sample length */ - if (duration_samples > sample_to_use->samples) - duration_samples = sample_to_use->samples; + /* Use the entire sample duration */ + int duration_samples = sample_to_use->samples; + fdd_log("FDD Audio Drive %d: Seek duration %d samples (from individual %d-track sample)\n", + drive, duration_samples, track_diff); /* Start new seek (or restart interrupted seek) */ - multi_seek_state[drive].position = 0; - multi_seek_state[drive].active = 1; - multi_seek_state[drive].duration_samples = duration_samples; - multi_seek_state[drive].from_track = from_track; - multi_seek_state[drive].to_track = to_track; + seek_state[drive].position = 0; + seek_state[drive].active = 1; + seek_state[drive].duration_samples = duration_samples; + seek_state[drive].from_track = from_track; + seek_state[drive].to_track = to_track; } static int16_t * @@ -764,7 +699,7 @@ load_wav(const char *filename, int *sample_count) /* 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); + filename, hdr.audio_format, hdr.bits_per_sample, hdr.num_channels); fclose(f); return NULL; } @@ -824,13 +759,11 @@ fdd_audio_callback(int16_t *buffer, int length) { /* Clear buffer */ memset(buffer, 0, length * sizeof(int16_t)); - + /* Check if any motor is running or transitioning, or any audio is active */ int any_audio_active = 0; for (int drive = 0; drive < FDD_NUM; drive++) { - if (spindlemotor_state[drive] != MOTOR_STATE_STOPPED || - single_step_state[drive].active || - multi_seek_state[drive].active) { + if (spindlemotor_state[drive] != MOTOR_STATE_STOPPED || seek_state[drive].active) { any_audio_active = 1; break; } @@ -849,7 +782,7 @@ fdd_audio_callback(int16_t *buffer, int length) drive_audio_samples_t *samples = get_drive_samples(drive); if (!samples) continue; - + for (int i = 0; i < samples_in_buffer; i++) { float left_sample = 0.0f; float right_sample = 0.0f; @@ -934,65 +867,39 @@ fdd_audio_callback(int16_t *buffer, int length) } } - /* Process single step audio with direction-aware sample selection */ - if (single_step_state[drive].active) { - /* Determine which sample to use based on the last seek direction */ - audio_sample_t *step_sample = NULL; - - /* Check if we have direction-specific samples or fall back to regular step */ - if (multi_seek_state[drive].from_track > multi_seek_state[drive].to_track && - samples->single_track_step_down.buffer) { - step_sample = &samples->single_track_step_down; + /* Process seek audio with direction-aware sample selection */ + if (seek_state[drive].active) { + /* Determine which sample to use based on seek direction and track count */ + int track_diff = abs(seek_state[drive].to_track - seek_state[drive].from_track); + if (track_diff > 0 && track_diff <= MAX_SEEK_SAMPLES) { + int idx = track_diff - 1; /* Array is 0-indexed */ + int is_seek_down = (seek_state[drive].to_track < seek_state[drive].from_track); + + audio_sample_t *seek_sample = is_seek_down ? &samples->seek_down[idx] : &samples->seek_up[idx]; + if (seek_sample && seek_sample->buffer && seek_state[drive].position < seek_state[drive].duration_samples && seek_state[drive].position < seek_sample->samples) { + /* Mix seek sound with motor sound with volume control */ + float seek_left = (float) seek_sample->buffer[seek_state[drive].position * 2] / 131072.0f * seek_sample->volume; + float seek_right = (float) seek_sample->buffer[seek_state[drive].position * 2 + 1] / 131072.0f * seek_sample->volume; + + left_sample += seek_left; + right_sample += seek_right; + + seek_state[drive].position++; + } else { + /* Seek sound finished */ + seek_state[drive].active = 0; + seek_state[drive].position = 0; + seek_state[drive].duration_samples = 0; + seek_state[drive].from_track = -1; + seek_state[drive].to_track = -1; + } } else { - step_sample = &samples->single_track_step; - } - - if (step_sample && step_sample->buffer && single_step_state[drive].position < step_sample->samples) { - /* Mix step sound with motor sound with volume control */ - float step_left = (float) step_sample->buffer[single_step_state[drive].position * 2] / 131072.0f * step_sample->volume; - float step_right = (float) step_sample->buffer[single_step_state[drive].position * 2 + 1] / 131072.0f * step_sample->volume; - - left_sample += step_left; - right_sample += step_right; - - single_step_state[drive].position++; - } else { - /* Step sound finished */ - single_step_state[drive].active = 0; - single_step_state[drive].position = 0; - } - } - - /* Process multi-track seek audio with direction-aware sample selection */ - if (multi_seek_state[drive].active) { - /* Determine which sample to use based on seek direction */ - audio_sample_t *seek_sample = NULL; - int is_seek_down = (multi_seek_state[drive].to_track < multi_seek_state[drive].from_track); - - if (is_seek_down && samples->multi_track_seek_down.buffer) { - seek_sample = &samples->multi_track_seek_down; - } else { - seek_sample = &samples->multi_track_seek; - } - - if (seek_sample && seek_sample->buffer && - multi_seek_state[drive].position < multi_seek_state[drive].duration_samples && - multi_seek_state[drive].position < seek_sample->samples) { - /* Mix seek sound with motor sound with volume control */ - float seek_left = (float) seek_sample->buffer[multi_seek_state[drive].position * 2] / 131072.0f * seek_sample->volume; - float seek_right = (float) seek_sample->buffer[multi_seek_state[drive].position * 2 + 1] / 131072.0f * seek_sample->volume; - - left_sample += seek_left; - right_sample += seek_right; - - multi_seek_state[drive].position++; - } else { - /* Seek sound finished */ - multi_seek_state[drive].active = 0; - multi_seek_state[drive].position = 0; - multi_seek_state[drive].duration_samples = 0; - multi_seek_state[drive].from_track = -1; - multi_seek_state[drive].to_track = -1; + /* Invalid track difference, stop seek */ + seek_state[drive].active = 0; + seek_state[drive].position = 0; + seek_state[drive].duration_samples = 0; + seek_state[drive].from_track = -1; + seek_state[drive].to_track = -1; } } @@ -1006,7 +913,7 @@ fdd_audio_callback(int16_t *buffer, int length) drive_audio_samples_t *samples = get_drive_samples(drive); if (!samples) continue; - + for (int i = 0; i < samples_in_buffer; i++) { int16_t left_sample = 0; int16_t right_sample = 0; @@ -1091,65 +998,40 @@ fdd_audio_callback(int16_t *buffer, int length) } } - /* Process single step audio with direction-aware sample selection */ - if (single_step_state[drive].active) { - /* Determine which sample to use based on the last seek direction */ - audio_sample_t *step_sample = NULL; - - /* Check if we have direction-specific samples or fall back to regular step */ - if (multi_seek_state[drive].from_track > multi_seek_state[drive].to_track && - samples->single_track_step_down.buffer) { - step_sample = &samples->single_track_step_down; + /* Process seek audio with direction-aware sample selection */ + if (seek_state[drive].active) { + /* Determine which sample to use based on seek direction and track count */ + int track_diff = abs(seek_state[drive].to_track - seek_state[drive].from_track); + if (track_diff > 0 && track_diff <= MAX_SEEK_SAMPLES) { + int idx = track_diff - 1; /* Array is 0-indexed */ + int is_seek_down = (seek_state[drive].to_track < seek_state[drive].from_track); + + audio_sample_t *seek_sample = is_seek_down ? &samples->seek_down[idx] : &samples->seek_up[idx]; + + if (seek_sample && seek_sample->buffer && seek_state[drive].position < seek_state[drive].duration_samples && seek_state[drive].position < seek_sample->samples) { + /* Mix seek sound with motor sound with volume control */ + int16_t seek_left = (int16_t) ((float) seek_sample->buffer[seek_state[drive].position * 2] / 4.0f * seek_sample->volume); + int16_t seek_right = (int16_t) ((float) seek_sample->buffer[seek_state[drive].position * 2 + 1] / 4.0f * seek_sample->volume); + + left_sample += seek_left; + right_sample += seek_right; + + seek_state[drive].position++; + } else { + /* Seek sound finished */ + seek_state[drive].active = 0; + seek_state[drive].position = 0; + seek_state[drive].duration_samples = 0; + seek_state[drive].from_track = -1; + seek_state[drive].to_track = -1; + } } else { - step_sample = &samples->single_track_step; - } - - if (step_sample && step_sample->buffer && single_step_state[drive].position < step_sample->samples) { - /* Mix step sound with motor sound with volume control */ - int16_t step_left = (int16_t) ((float) step_sample->buffer[single_step_state[drive].position * 2] / 4.0f * step_sample->volume); - int16_t step_right = (int16_t) ((float) step_sample->buffer[single_step_state[drive].position * 2 + 1] / 4.0f * step_sample->volume); - - left_sample += step_left; - right_sample += step_right; - - single_step_state[drive].position++; - } else { - /* Step sound finished */ - single_step_state[drive].active = 0; - single_step_state[drive].position = 0; - } - } - - /* Process multi-track seek audio with direction-aware sample selection */ - if (multi_seek_state[drive].active) { - /* Determine which sample to use based on seek direction */ - audio_sample_t *seek_sample = NULL; - int is_seek_down = (multi_seek_state[drive].to_track < multi_seek_state[drive].from_track); - - if (is_seek_down && samples->multi_track_seek_down.buffer) { - seek_sample = &samples->multi_track_seek_down; - } else { - seek_sample = &samples->multi_track_seek; - } - - if (seek_sample && seek_sample->buffer && - multi_seek_state[drive].position < multi_seek_state[drive].duration_samples && - multi_seek_state[drive].position < seek_sample->samples) { - /* Mix seek sound with motor sound with volume control */ - int16_t seek_left = (int16_t) ((float) seek_sample->buffer[multi_seek_state[drive].position * 2] / 4.0f * seek_sample->volume); - int16_t seek_right = (int16_t) ((float) seek_sample->buffer[multi_seek_state[drive].position * 2 + 1] / 4.0f * seek_sample->volume); - - left_sample += seek_left; - right_sample += seek_right; - - multi_seek_state[drive].position++; - } else { - /* Seek sound finished */ - multi_seek_state[drive].active = 0; - multi_seek_state[drive].position = 0; - multi_seek_state[drive].duration_samples = 0; - multi_seek_state[drive].from_track = -1; - multi_seek_state[drive].to_track = -1; + /* Invalid track difference, stop seek */ + seek_state[drive].active = 0; + seek_state[drive].position = 0; + seek_state[drive].duration_samples = 0; + seek_state[drive].from_track = -1; + seek_state[drive].to_track = -1; } } @@ -1163,23 +1045,61 @@ fdd_audio_callback(int16_t *buffer, int length) #else /* Stub implementations when audio is disabled */ -void fdd_audio_load_profiles(void) {} -int fdd_audio_get_profile_count(void) { return 1; } -const fdd_audio_profile_config_t* fdd_audio_get_profile(int id) { - static fdd_audio_profile_config_t none_profile = {0, "None", "none"}; - return (id == 0) ? &none_profile : NULL; +void +fdd_audio_load_profiles(void) +{ } -const char* fdd_audio_get_profile_name(int id) { return (id == 0) ? "None" : NULL; } -const char* fdd_audio_get_profile_internal_name(int id) { return (id == 0) ? "none" : NULL; } -int fdd_audio_get_profile_by_internal_name(const char* internal_name) { return 0; } -double fdd_audio_get_seek_time(int drive, int is_initial, int track_count, int is_seek_down) { - return is_initial ? 15000.0 : 6000.0; +int +fdd_audio_get_profile_count(void) +{ + return 1; +} +const fdd_audio_profile_config_t * +fdd_audio_get_profile(int id) +{ + static fdd_audio_profile_config_t none_profile = { 0, "None", "none" }; + return (id == 0) ? &none_profile : NULL; +} +const char * +fdd_audio_get_profile_name(int id) +{ + return (id == 0) ? "None" : NULL; +} +const char * +fdd_audio_get_profile_internal_name(int id) +{ + return (id == 0) ? "none" : NULL; +} +int +fdd_audio_get_profile_by_internal_name(const char *internal_name) +{ + return 0; +} +double +fdd_audio_get_seek_time(int drive, int is_initial, int track_count, int is_seek_down) +{ + return is_initial ? 15000.0 : 6000.0; +} +void +fdd_audio_init(void) +{ +} +void +fdd_audio_close(void) +{ +} +void +fdd_audio_set_motor_enable(int drive, int motor_enable) +{ +} +void +fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) +{ +} +void +fdd_audio_callback(int16_t *buffer, int length) +{ + memset(buffer, 0, length * sizeof(int16_t)); } -void fdd_audio_init(void) {} -void fdd_audio_close(void) {} -void fdd_audio_set_motor_enable(int drive, int motor_enable) {} -void fdd_audio_play_single_track_step(int drive, int from_track, int to_track) {} -void fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) {} -void fdd_audio_callback(int16_t *buffer, int length) { memset(buffer, 0, length * sizeof(int16_t)); } #endif /* DISABLE_FDD_AUDIO */ \ No newline at end of file diff --git a/src/include/86box/fdd_audio.h b/src/include/86box/fdd_audio.h index 3dd69a748..eca48676e 100644 --- a/src/include/86box/fdd_audio.h +++ b/src/include/86box/fdd_audio.h @@ -23,6 +23,9 @@ extern "C" { #ifndef DISABLE_FDD_AUDIO +/* Maximum number of seek samples (for 80-track drives: 1-79 tracks) */ +#define MAX_SEEK_SAMPLES 79 + /* Audio sample configuration structure */ typedef struct { char filename[512]; @@ -37,18 +40,9 @@ typedef struct { audio_sample_config_t spindlemotor_start; audio_sample_config_t spindlemotor_loop; audio_sample_config_t spindlemotor_stop; - audio_sample_config_t single_track_step; - audio_sample_config_t multi_track_seek; - audio_sample_config_t single_track_step_down; - audio_sample_config_t multi_track_seek_down; - int total_tracks; - int samples_per_track; - double initial_seek_time; - double initial_seek_time_pcjr; - double track_seek_time; - double track_seek_time_pcjr; - double initial_seek_down_time; - double track_seek_down_time; + audio_sample_config_t seek_up[MAX_SEEK_SAMPLES]; + audio_sample_config_t seek_down[MAX_SEEK_SAMPLES]; + int total_tracks; /* 40 or 80 */ } fdd_audio_profile_config_t; #define FDD_AUDIO_PROFILE_MAX 64 @@ -89,7 +83,7 @@ extern const fdd_audio_profile_config_t* fdd_audio_get_profile(int id); extern const char* fdd_audio_get_profile_name(int id); extern const char* fdd_audio_get_profile_internal_name(int id); extern int fdd_audio_get_profile_by_internal_name(const char* internal_name); -extern double fdd_audio_get_seek_time(int drive, int is_initial, int track_count, int _is_seek_down); +extern double fdd_audio_get_seek_time(int drive, int is_initial, int track_count, int is_seek_down); #else