mirror of
https://github.com/86Box/86Box.git
synced 2026-02-22 01:25:33 -07:00
Configurable FDD sound emulation timings (#6263)
* Initial spindle emulation working for windows atleast * Spingle motor spin-up, spin-down implemented with smooth transitions to motor-on loop. * Moved fdd audio emulation to a separate file * Multiple drives sound emulation * Single sector movement sound emulations implemented * Rename project to Immersive86Box and update details Updated README to reflect the new project name and added details about the Immersive86Box features and future plans. * Revise contribution guidelines in CONTRIBUTING.md * Update vulnerability reporting instructions * System fan-sound next feature after basic fdd sound emulation is ready * v0.5 multitrack audio seek sfx * Removed unnecessary stuff * no .vs folder for git * Added currently used fdd sound effects and readme.txt for source of the files and intallation instructions * Add audio emulation installation instructions Added instructions for audio emulation installation. * Code and audio samples merged * Simplify audio emulation installation instructions * FDC seeking fixed, not instant anymore drive is set to busy during the operation and when it's finished at call fdc to set the appropriate fdc flags. Also added time logic to fdd to calculate seek duration and a callback function for it. * FDD sound samples volume control * Menu options to enable / disable fdd sound for all drives. DISABLE_FDD_AUDIO definition added, to disable the feature via cmake/build. * Revert readme etc. changes * Revert "Revise contribution guidelines in CONTRIBUTING.md" This reverts commit98a0478225. * Revert "Update vulnerability reporting instructions" This reverts commit7d32cb659b. * Fixed merge issue * Removed excess files * Fixed PCJr seeking not to break the FDC implementation. Now seeking will take the "correct" amount of time for each system and the seek time is based on the track count. E.g. 40 track FDD system causes 40 track seek time to be 80/40 * 6ms * 40 tracks + 50ms = 480ms + 50ms -> 530ms. 80 track system full seek is 80/80 * 6ms * 80 + 50ms = 530ms, 40 track seek would take 240 + 50 = 290ms. * Fixed PS/1, PS/2 and PS/55 FDD issues. * FDD_AUDIO: Updating samples looked in executablePath/samples and if now found there, looks in the executable directory * Updated installation instructions * Removed samples path strcat use * fdd_audio 5.25 samples and support added * FDD audio timing/volume tunings * Timing fixes for authentity and special longer timings for PCJr * Fixed second drive motor keeps running when first drive is only accessed. * Fixed PCJr random failure issue, timings * CodeQL fix for load_wav-function. Check the filename to be proper filename * Revert "Fixed second drive motor keeps running when first drive is only accessed." This reverts commit307b173ae7. * Teac 5.25" drive samples added. Added per drive audio selection to FDD settings. * Fixed merge issue * Fixed readme to include only Mitsumi samples under CC-BY license * Fixes merge problem * Fixes #6220: OS/2 Warp 3.0 install disk issue and NT 3.1 floppy disk issues. Changed the fdc->stat bit to 0x10 which states fdc busy. Previous implementation with seek time 0, set the ready flag 0x80 immediadely - which was correct for that time, but now as the drive is busy during seek, the value should 0x10. Implemented a backup for fdd commands during fdd seek to be processes after the seek is completed. * Fixed FDD reset * Floppy disk audio now gets settings from fdd_audio_profiles.cfg stored in roms/floppy -directory. Floppy settings audio seledtions are also populated by the settings from this config. * Changed samples per track, not seek_duration_per_track * Removed commented code. Returned total_track in configration * Fixed CodeQL errors --------- Co-authored-by: Toni Riikonen <domppari@hotmail.com>
This commit is contained in:
@@ -79,6 +79,7 @@
|
||||
#include <86box/mouse.h>
|
||||
#include <86box/gameport.h>
|
||||
#include <86box/fdd.h>
|
||||
#include <86box/fdd_audio.h>
|
||||
#include <86box/fdc.h>
|
||||
#include <86box/fdc_ext.h>
|
||||
#include <86box/hdd.h>
|
||||
@@ -1380,6 +1381,11 @@ pc_init_modules(void)
|
||||
video_init();
|
||||
|
||||
fdd_init();
|
||||
|
||||
if (fdd_sounds_enabled) {
|
||||
fdd_audio_load_profiles();
|
||||
fdd_audio_init();
|
||||
}
|
||||
|
||||
sound_init();
|
||||
|
||||
|
||||
35
src/config.c
35
src/config.c
@@ -54,6 +54,7 @@
|
||||
#include <86box/hdc.h>
|
||||
#include <86box/hdc_ide.h>
|
||||
#include <86box/fdd.h>
|
||||
#include <86box/fdd_audio.h>
|
||||
#include <86box/fdc_ext.h>
|
||||
#include <86box/gameport.h>
|
||||
#include <86box/keyboard.h>
|
||||
@@ -1379,10 +1380,15 @@ load_floppy_and_cdrom_drives(void)
|
||||
int c;
|
||||
int d;
|
||||
int count = cdrom_get_type_count();
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
fdd_audio_load_profiles();
|
||||
#endif
|
||||
|
||||
memset(temp, 0x00, sizeof(temp));
|
||||
for (c = 0; c < FDD_NUM; c++) {
|
||||
sprintf(temp, "fdd_%02i_type", c + 1);
|
||||
|
||||
p = ini_section_get_string(cat, temp, (c < 2) ? "525_2dd" : "none");
|
||||
if (!strcmp(p, "525_2hd_ps2"))
|
||||
d = fdd_get_from_internal_name("525_2hd");
|
||||
@@ -1437,14 +1443,14 @@ load_floppy_and_cdrom_drives(void)
|
||||
sprintf(temp, "fdd_%02i_check_bpb", c + 1);
|
||||
ini_section_delete_var(cat, temp);
|
||||
}
|
||||
sprintf(temp, "fdd_%02i_audio", c + 1);
|
||||
int def_prof = FDD_AUDIO_PROFILE_NONE;
|
||||
int prof = ini_section_get_int(cat, temp, def_prof);
|
||||
if (prof < 0 || prof >= FDD_AUDIO_PROFILE_MAX)
|
||||
prof = def_prof;
|
||||
sprintf(temp, "fdd_%02i_audio", c + 1);
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
p = ini_section_get_string(cat, temp, "none");
|
||||
int prof = fdd_audio_get_profile_by_internal_name(p);
|
||||
fdd_set_audio_profile(c, prof);
|
||||
if (prof == def_prof)
|
||||
ini_section_delete_var(cat, temp);
|
||||
#else
|
||||
fdd_set_audio_profile(c, 0);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < MAX_PREV_IMAGES; i++) {
|
||||
fdd_image_history[c][i] = (char *) calloc((MAX_IMAGE_PATH_LEN + 1) << 1, sizeof(char));
|
||||
@@ -3433,12 +3439,17 @@ save_floppy_and_cdrom_drives(void)
|
||||
}
|
||||
|
||||
sprintf(temp, "fdd_%02i_audio", c + 1);
|
||||
int def_prof = FDD_AUDIO_PROFILE_NONE;
|
||||
int prof = fdd_get_audio_profile(c);
|
||||
if (prof == def_prof)
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
int prof = fdd_get_audio_profile(c);
|
||||
const char *internal_name = fdd_audio_get_profile_internal_name(prof);
|
||||
if (internal_name && strcmp(internal_name, "none") != 0) {
|
||||
ini_section_set_string(cat, temp, internal_name);
|
||||
} else {
|
||||
ini_section_delete_var(cat, temp);
|
||||
else
|
||||
ini_section_set_int(cat, temp, prof);
|
||||
}
|
||||
#else
|
||||
ini_section_delete_var(cat, temp);
|
||||
#endif
|
||||
}
|
||||
|
||||
for (c = 0; c < CDROM_NUM; c++) {
|
||||
|
||||
@@ -409,8 +409,10 @@ fdd_seek(int drive, int track_diff)
|
||||
timer_add(&(fdd_seek_timer[drive]), fdd_seek_complete_callback, &drives[drive], 0);
|
||||
}
|
||||
|
||||
double initial_seek_time = FDC_FLAG_PCJR & fdd_fdc->flags ? 40000.0 : 15000.0;
|
||||
double track_seek_time = FDC_FLAG_PCJR & fdd_fdc->flags ? 10000.0 : 6000.0;
|
||||
/* Get seek timings from audio profile configuration */
|
||||
double initial_seek_time = fdd_audio_get_seek_time(drive, 1, actual_track_diff);
|
||||
double track_seek_time = fdd_audio_get_seek_time(drive, 0, actual_track_diff);
|
||||
fdd_log("Seek timing for drive %d: initial %.2f ms, per track %.2f ms\n", drive, initial_seek_time, track_seek_time);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -24,23 +24,28 @@
|
||||
#include <86box/timer.h>
|
||||
#include <86box/fdd.h>
|
||||
#include <86box/fdd_audio.h>
|
||||
#include <86box/fdc.h>
|
||||
#include <86box/mem.h>
|
||||
#include <86box/rom.h>
|
||||
#include <86box/sound.h>
|
||||
#include <86box/plat.h>
|
||||
#include <86box/path.h>
|
||||
#include <86box/ini.h>
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
|
||||
/* Global audio profile configurations */
|
||||
static fdd_audio_profile_config_t audio_profiles[FDD_AUDIO_PROFILE_MAX];
|
||||
static int audio_profile_count = 0;
|
||||
|
||||
/* Audio sample structure */
|
||||
typedef struct {
|
||||
const char *filename;
|
||||
int16_t *buffer;
|
||||
int samples;
|
||||
float volume;
|
||||
char filename[512];
|
||||
int16_t *buffer;
|
||||
int samples;
|
||||
float volume;
|
||||
} audio_sample_t;
|
||||
|
||||
/* Single step audio state */
|
||||
typedef struct {
|
||||
int position;
|
||||
int active;
|
||||
@@ -64,77 +69,8 @@ typedef struct {
|
||||
audio_sample_t multi_track_seek;
|
||||
} drive_audio_samples_t;
|
||||
|
||||
/* 5.25" Teac FD-55GFR sample set */
|
||||
static drive_audio_samples_t samples_teac = {
|
||||
.spindlemotor_start = {
|
||||
.filename = "roms/floppy/samples/TeacFD-55GFR_5.25_1.2MB_motor_start_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 3.0f
|
||||
},
|
||||
.spindlemotor_loop = {
|
||||
.filename = "roms/floppy/samples/TeacFD-55GFR_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 3.0f
|
||||
},
|
||||
.spindlemotor_stop = {
|
||||
.filename = "roms/floppy/samples/TeacFD-55GFR_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 3.0f
|
||||
},
|
||||
.single_track_step = {
|
||||
.filename = "roms/floppy/samples/TeacFD-55GFR_5.25_1.2MB_track_step_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 2.0f
|
||||
},
|
||||
.multi_track_seek = {
|
||||
.filename = "roms/floppy/samples/TeacFD_55GFR_5.25_1.2MB_seekupdown_80_tracks1100ms_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 2.0f
|
||||
}
|
||||
};
|
||||
|
||||
/* 3.5" drive audio samples (Mitsumi) */
|
||||
static drive_audio_samples_t samples_35 = {
|
||||
.spindlemotor_start = {
|
||||
.filename = "roms/floppy/samples/mitsumi_spindle_motor_start_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 0.2f
|
||||
},
|
||||
.spindlemotor_loop = {
|
||||
.filename = "roms/floppy/samples/mitsumi_spindle_motor_loop_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 0.2f
|
||||
},
|
||||
.spindlemotor_stop = {
|
||||
.filename = "roms/floppy/samples/mitsumi_spindle_motor_stop_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 0.2f
|
||||
},
|
||||
.single_track_step = {
|
||||
.filename = "roms/floppy/samples/mitsumi_track_step_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 1.0f
|
||||
},
|
||||
.multi_track_seek = {
|
||||
.filename = "roms/floppy/samples/mitsumi_seek_80_tracks_495ms_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 1.0f
|
||||
}
|
||||
};
|
||||
|
||||
/* 5.25" drive audio samples (Panasonic) */
|
||||
static drive_audio_samples_t samples_525 = {
|
||||
.spindlemotor_start = {
|
||||
.filename = "roms/floppy/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_start_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 1.0f
|
||||
},
|
||||
.spindlemotor_loop = {
|
||||
.filename = "roms/floppy/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 1.0f
|
||||
},
|
||||
.spindlemotor_stop = {
|
||||
.filename = "roms/floppy/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 1.0f
|
||||
},
|
||||
.single_track_step = {
|
||||
.filename = "roms/floppy/samples/Panasonic_JU-475-5_5.25_1.2MB_track_step_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 2.0f
|
||||
},
|
||||
.multi_track_seek = {
|
||||
.filename = "roms/floppy/samples/Panasonic_JU-475-5_5.25_1.2MB_seekup_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav",
|
||||
.buffer = NULL, .samples = 0, .volume = 2.0f
|
||||
}
|
||||
};
|
||||
/* Dynamic sample storage for each profile */
|
||||
static drive_audio_samples_t profile_samples[FDD_AUDIO_PROFILE_MAX];
|
||||
|
||||
/* Audio state for each drive */
|
||||
static int spindlemotor_pos[FDD_NUM] = {};
|
||||
@@ -149,132 +85,376 @@ static single_step_state_t single_step_state[FDD_NUM] = {};
|
||||
static multi_seek_state_t multi_seek_state[FDD_NUM] = {};
|
||||
|
||||
extern uint64_t motoron[FDD_NUM];
|
||||
extern char exe_path[2048]; /* path (dir) of executable */
|
||||
extern char exe_path[2048];
|
||||
|
||||
extern int fdd_get_audio_profile(int drive); /* from fdd.h */
|
||||
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
|
||||
int fdc_do_log = ENABLE_FDD_LOG;
|
||||
|
||||
static void
|
||||
fdd_log(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if (fdc_do_log) {
|
||||
va_start(ap, fmt);
|
||||
pclog_ex(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
# else
|
||||
# define fdd_log(fmt, ...)
|
||||
# endif
|
||||
|
||||
/* Logging function for audio profile parameters */
|
||||
static void
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
fdd_log("FDD Audio Drive %d Profile Parameters:\n", drive);
|
||||
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(" Multi Seek: %s (volume: %.2f)\n",
|
||||
profile->multi_track_seek.filename, profile->multi_track_seek.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);
|
||||
}
|
||||
|
||||
/* Log audio profile parameters for a specific drive */
|
||||
void
|
||||
fdd_audio_log_drive_profile(int drive)
|
||||
{
|
||||
if (drive < 0 || drive >= FDD_NUM) {
|
||||
fdd_log("FDD Audio: Invalid drive number %d\n", drive);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* Log only the audio profiles that are actually used by configured drives */
|
||||
static void
|
||||
fdd_audio_log_active_profiles(void)
|
||||
{
|
||||
fdd_log("FDD Audio: Checking active drive configurations...\n");
|
||||
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);
|
||||
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];
|
||||
ini_t profiles_ini;
|
||||
|
||||
/* Validate exe_path to prevent directory traversal attacks */
|
||||
if (exe_path == NULL || strlen(exe_path) == 0) {
|
||||
fdd_log("FDD Audio: Invalid exe_path\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for directory traversal sequences */
|
||||
if (strstr(exe_path, "..") != NULL) {
|
||||
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 */
|
||||
if (strstr(config_path, "..") != NULL) {
|
||||
fdd_log("FDD Audio: Directory traversal detected in config path: %s\n", config_path);
|
||||
return;
|
||||
}
|
||||
|
||||
profiles_ini = ini_read(config_path);
|
||||
if (profiles_ini == NULL) {
|
||||
fdd_log("FDD Audio: Could not load profiles from %s\n", config_path);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const char *name = ini_section_get_string(section, "name", "Unknown");
|
||||
strncpy(profile->name, name, sizeof(profile->name) - 1);
|
||||
profile->name[sizeof(profile->name) - 1] = '\0';
|
||||
|
||||
const char *internal_name = ini_section_get_string(section, "internal_name", "unknown");
|
||||
strncpy(profile->internal_name, internal_name, sizeof(profile->internal_name) - 1);
|
||||
profile->internal_name[sizeof(profile->internal_name) - 1] = '\0';
|
||||
|
||||
/* Load sample configurations */
|
||||
const char *filename = ini_section_get_string(section, "spindlemotor_start_file", "");
|
||||
strncpy(profile->spindlemotor_start.filename, filename, sizeof(profile->spindlemotor_start.filename) - 1);
|
||||
profile->spindlemotor_start.filename[sizeof(profile->spindlemotor_start.filename) - 1] = '\0';
|
||||
profile->spindlemotor_start.volume = ini_section_get_double(section, "spindlemotor_start_volume", 1.0);
|
||||
|
||||
filename = ini_section_get_string(section, "spindlemotor_loop_file", "");
|
||||
strncpy(profile->spindlemotor_loop.filename, filename, sizeof(profile->spindlemotor_loop.filename) - 1);
|
||||
profile->spindlemotor_loop.filename[sizeof(profile->spindlemotor_loop.filename) - 1] = '\0';
|
||||
profile->spindlemotor_loop.volume = ini_section_get_double(section, "spindlemotor_loop_volume", 1.0);
|
||||
|
||||
filename = ini_section_get_string(section, "spindlemotor_stop_file", "");
|
||||
strncpy(profile->spindlemotor_stop.filename, filename, sizeof(profile->spindlemotor_stop.filename) - 1);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
} else {
|
||||
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);
|
||||
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);
|
||||
} else {
|
||||
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);
|
||||
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);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static drive_audio_samples_t *
|
||||
get_drive_samples(int drive)
|
||||
{
|
||||
switch (fdd_get_audio_profile(drive)) {
|
||||
case FDD_AUDIO_PROFILE_PANASONIC:
|
||||
return &samples_525;
|
||||
case FDD_AUDIO_PROFILE_TEAC:
|
||||
return &samples_teac;
|
||||
case FDD_AUDIO_PROFILE_MITSUMI:
|
||||
return &samples_35;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
int profile_id = fdd_get_audio_profile(drive);
|
||||
if (profile_id <= 0 || profile_id >= audio_profile_count)
|
||||
return NULL;
|
||||
|
||||
/* Samples are preloaded during fdd_audio_init */
|
||||
return &profile_samples[profile_id];
|
||||
}
|
||||
|
||||
static int16_t *
|
||||
load_wav(const char *filename, int *sample_count)
|
||||
/* Public API functions */
|
||||
int fdd_audio_get_profile_count(void) {
|
||||
return audio_profile_count;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (id < 0 || id >= audio_profile_count)
|
||||
return NULL;
|
||||
return audio_profiles[id].name;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int
|
||||
fdd_audio_get_profile_by_internal_name(const char *internal_name)
|
||||
{
|
||||
FILE *f = NULL;
|
||||
if (!internal_name || !*internal_name)
|
||||
return 0;
|
||||
|
||||
if ((filename == NULL) || (strlen(filename) == 0))
|
||||
return NULL;
|
||||
if (audio_profile_count == 0)
|
||||
fdd_audio_load_profiles();
|
||||
|
||||
if (strstr(filename, "..") != NULL)
|
||||
return NULL;
|
||||
|
||||
f = rom_fopen(filename, "rb");
|
||||
if (f == NULL)
|
||||
return NULL;
|
||||
|
||||
wav_header_t hdr;
|
||||
if (fread(&hdr, sizeof(hdr), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
for (int i = 0; i < audio_profile_count; i++) {
|
||||
if (!strcmp(audio_profiles[i].internal_name, internal_name))
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
double fdd_audio_get_seek_time(int drive, int is_initial, int track_count) {
|
||||
int profile_id = fdd_get_audio_profile(drive);
|
||||
if (profile_id <= 0 || profile_id >= audio_profile_count) {
|
||||
/* Return default values */
|
||||
return is_initial ? 15000.0 : 6000.0;
|
||||
}
|
||||
|
||||
/* Accept both mono and stereo, 16-bit PCM */
|
||||
if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || (hdr.num_channels != 1 && hdr.num_channels != 2)) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int input_samples = hdr.data_size / 2; /* 2 bytes per sample */
|
||||
int16_t *input_data = malloc(hdr.data_size);
|
||||
if (!input_data) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) {
|
||||
free(input_data);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
int16_t *output_data;
|
||||
int output_samples;
|
||||
|
||||
if (hdr.num_channels == 1) {
|
||||
/* Convert mono to stereo */
|
||||
output_samples = input_samples; /* Number of stereo sample pairs */
|
||||
output_data = malloc(input_samples * 2 * sizeof(int16_t)); /* Allocate for stereo */
|
||||
if (!output_data) {
|
||||
free(input_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert mono to stereo by duplicating each sample */
|
||||
for (int i = 0; i < input_samples; i++) {
|
||||
output_data[i * 2] = input_data[i]; /* Left channel */
|
||||
output_data[i * 2 + 1] = input_data[i]; /* Right channel */
|
||||
}
|
||||
|
||||
free(input_data);
|
||||
|
||||
fdd_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_initial) {
|
||||
return is_pcjr ? profile->initial_seek_time_pcjr : profile->initial_seek_time;
|
||||
} else {
|
||||
/* Already stereo */
|
||||
output_data = input_data;
|
||||
output_samples = input_samples / 2; /* Number of stereo sample pairs */
|
||||
return is_pcjr ? profile->track_seek_time_pcjr : profile->track_seek_time;
|
||||
}
|
||||
|
||||
if (sample_count)
|
||||
*sample_count = output_samples;
|
||||
|
||||
return output_data;
|
||||
}
|
||||
|
||||
void
|
||||
fdd_audio_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Load audio samples for both drive types */
|
||||
samples_35.spindlemotor_start.buffer = load_wav(samples_35.spindlemotor_start.filename, &samples_35.spindlemotor_start.samples);
|
||||
samples_35.spindlemotor_loop.buffer = load_wav(samples_35.spindlemotor_loop.filename, &samples_35.spindlemotor_loop.samples);
|
||||
samples_35.spindlemotor_stop.buffer = load_wav(samples_35.spindlemotor_stop.filename, &samples_35.spindlemotor_stop.samples);
|
||||
samples_35.single_track_step.buffer = load_wav(samples_35.single_track_step.filename, &samples_35.single_track_step.samples);
|
||||
samples_35.multi_track_seek.buffer = load_wav(samples_35.multi_track_seek.filename, &samples_35.multi_track_seek.samples);
|
||||
|
||||
samples_525.spindlemotor_start.buffer = load_wav(samples_525.spindlemotor_start.filename, &samples_525.spindlemotor_start.samples);
|
||||
samples_525.spindlemotor_loop.buffer = load_wav(samples_525.spindlemotor_loop.filename, &samples_525.spindlemotor_loop.samples);
|
||||
samples_525.spindlemotor_stop.buffer = load_wav(samples_525.spindlemotor_stop.filename, &samples_525.spindlemotor_stop.samples);
|
||||
samples_525.single_track_step.buffer = load_wav(samples_525.single_track_step.filename, &samples_525.single_track_step.samples);
|
||||
samples_525.multi_track_seek.buffer = load_wav(samples_525.multi_track_seek.filename, &samples_525.multi_track_seek.samples);
|
||||
|
||||
samples_teac.spindlemotor_start.buffer = load_wav(samples_teac.spindlemotor_start.filename, &samples_teac.spindlemotor_start.samples);
|
||||
samples_teac.spindlemotor_loop.buffer = load_wav(samples_teac.spindlemotor_loop.filename, &samples_teac.spindlemotor_loop.samples);
|
||||
samples_teac.spindlemotor_stop.buffer = load_wav(samples_teac.spindlemotor_stop.filename, &samples_teac.spindlemotor_stop.samples);
|
||||
samples_teac.single_track_step.buffer = load_wav(samples_teac.single_track_step.filename, &samples_teac.single_track_step.samples);
|
||||
samples_teac.multi_track_seek.buffer = load_wav(samples_teac.multi_track_seek.filename, &samples_teac.multi_track_seek.samples);
|
||||
/* Load audio profiles configuration */
|
||||
fdd_audio_load_profiles();
|
||||
|
||||
/* Initialize audio state for all drives */
|
||||
for (i = 0; i < FDD_NUM; i++) {
|
||||
for (int i = 0; i < FDD_NUM; i++) {
|
||||
spindlemotor_pos[i] = 0;
|
||||
spindlemotor_state[i] = MOTOR_STATE_STOPPED;
|
||||
spindlemotor_fade_volume[i] = 1.0f;
|
||||
@@ -292,95 +472,62 @@ fdd_audio_init(void)
|
||||
multi_seek_state[i].to_track = -1;
|
||||
}
|
||||
|
||||
/* Preload audio samples for each drive's selected profile */
|
||||
for (int drive = 0; drive < FDD_NUM; drive++) {
|
||||
int profile_id = fdd_get_audio_profile(drive);
|
||||
if (profile_id > 0 && profile_id < audio_profile_count) {
|
||||
load_profile_samples(profile_id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Log only the active profiles used by configured drives */
|
||||
fdd_audio_log_active_profiles();
|
||||
|
||||
/* Initialize sound thread */
|
||||
sound_fdd_thread_init();
|
||||
|
||||
fdd_log("FDD Audio: Initialization complete\n");
|
||||
}
|
||||
|
||||
void
|
||||
fdd_audio_close(void)
|
||||
{
|
||||
/* Free 3.5" samples */
|
||||
if (samples_35.spindlemotor_start.buffer) {
|
||||
free(samples_35.spindlemotor_start.buffer);
|
||||
samples_35.spindlemotor_start.buffer = NULL;
|
||||
samples_35.spindlemotor_start.samples = 0;
|
||||
}
|
||||
if (samples_35.spindlemotor_loop.buffer) {
|
||||
free(samples_35.spindlemotor_loop.buffer);
|
||||
samples_35.spindlemotor_loop.buffer = NULL;
|
||||
samples_35.spindlemotor_loop.samples = 0;
|
||||
}
|
||||
if (samples_35.spindlemotor_stop.buffer) {
|
||||
free(samples_35.spindlemotor_stop.buffer);
|
||||
samples_35.spindlemotor_stop.buffer = NULL;
|
||||
samples_35.spindlemotor_stop.samples = 0;
|
||||
}
|
||||
if (samples_35.single_track_step.buffer) {
|
||||
free(samples_35.single_track_step.buffer);
|
||||
samples_35.single_track_step.buffer = NULL;
|
||||
samples_35.single_track_step.samples = 0;
|
||||
}
|
||||
if (samples_35.multi_track_seek.buffer) {
|
||||
free(samples_35.multi_track_seek.buffer);
|
||||
samples_35.multi_track_seek.buffer = NULL;
|
||||
samples_35.multi_track_seek.samples = 0;
|
||||
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];
|
||||
|
||||
if (samples->spindlemotor_start.buffer) {
|
||||
free(samples->spindlemotor_start.buffer);
|
||||
samples->spindlemotor_start.buffer = NULL;
|
||||
samples->spindlemotor_start.samples = 0;
|
||||
}
|
||||
if (samples->spindlemotor_loop.buffer) {
|
||||
free(samples->spindlemotor_loop.buffer);
|
||||
samples->spindlemotor_loop.buffer = NULL;
|
||||
samples->spindlemotor_loop.samples = 0;
|
||||
}
|
||||
if (samples->spindlemotor_stop.buffer) {
|
||||
free(samples->spindlemotor_stop.buffer);
|
||||
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->multi_track_seek.buffer) {
|
||||
free(samples->multi_track_seek.buffer);
|
||||
samples->multi_track_seek.buffer = NULL;
|
||||
samples->multi_track_seek.samples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free 5.25" samples */
|
||||
if (samples_525.spindlemotor_start.buffer) {
|
||||
free(samples_525.spindlemotor_start.buffer);
|
||||
samples_525.spindlemotor_start.buffer = NULL;
|
||||
samples_525.spindlemotor_start.samples = 0;
|
||||
}
|
||||
if (samples_525.spindlemotor_loop.buffer) {
|
||||
free(samples_525.spindlemotor_loop.buffer);
|
||||
samples_525.spindlemotor_loop.buffer = NULL;
|
||||
samples_525.spindlemotor_loop.samples = 0;
|
||||
}
|
||||
if (samples_525.spindlemotor_stop.buffer) {
|
||||
free(samples_525.spindlemotor_stop.buffer);
|
||||
samples_525.spindlemotor_stop.buffer = NULL;
|
||||
samples_525.spindlemotor_stop.samples = 0;
|
||||
}
|
||||
if (samples_525.single_track_step.buffer) {
|
||||
free(samples_525.single_track_step.buffer);
|
||||
samples_525.single_track_step.buffer = NULL;
|
||||
samples_525.single_track_step.samples = 0;
|
||||
}
|
||||
if (samples_525.multi_track_seek.buffer) {
|
||||
free(samples_525.multi_track_seek.buffer);
|
||||
samples_525.multi_track_seek.buffer = NULL;
|
||||
samples_525.multi_track_seek.samples = 0;
|
||||
}
|
||||
|
||||
if (samples_teac.spindlemotor_start.buffer) {
|
||||
free(samples_teac.spindlemotor_start.buffer);
|
||||
samples_teac.spindlemotor_start.buffer = NULL;
|
||||
samples_teac.spindlemotor_start.samples = 0;
|
||||
}
|
||||
if (samples_teac.spindlemotor_loop.buffer) {
|
||||
free(samples_teac.spindlemotor_loop.buffer);
|
||||
samples_teac.spindlemotor_loop.buffer = NULL;
|
||||
samples_teac.spindlemotor_loop.samples = 0;
|
||||
}
|
||||
if (samples_teac.spindlemotor_stop.buffer) {
|
||||
free(samples_teac.spindlemotor_stop.buffer);
|
||||
samples_teac.spindlemotor_stop.buffer = NULL;
|
||||
samples_teac.spindlemotor_stop.samples = 0;
|
||||
}
|
||||
if (samples_teac.single_track_step.buffer) {
|
||||
free(samples_teac.single_track_step.buffer);
|
||||
samples_teac.single_track_step.buffer = NULL;
|
||||
samples_teac.single_track_step.samples = 0;
|
||||
}
|
||||
if (samples_teac.multi_track_seek.buffer) {
|
||||
free(samples_teac.multi_track_seek.buffer);
|
||||
samples_teac.multi_track_seek.buffer = NULL;
|
||||
samples_teac.multi_track_seek.samples = 0;
|
||||
}
|
||||
|
||||
/* End sound thread */
|
||||
sound_fdd_thread_end();
|
||||
|
||||
fdd_log("FDD Audio: Shutdown complete\n");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -393,16 +540,20 @@ fdd_audio_set_motor_enable(int drive, int motor_enable)
|
||||
if (!samples)
|
||||
return;
|
||||
|
||||
fdd_log("FDD Audio Drive %d: Motor %s\n", drive, motor_enable ? "ON" : "OFF");
|
||||
|
||||
if (motor_enable && !motoron[drive]) {
|
||||
/* Motor starting up */
|
||||
if (spindlemotor_state[drive] == MOTOR_STATE_STOPPING) {
|
||||
/* Interrupt stop sequence and transition back to loop */
|
||||
fdd_log("FDD Audio Drive %d: Interrupting stop sequence, returning to loop\n", drive);
|
||||
spindlemotor_state[drive] = MOTOR_STATE_RUNNING;
|
||||
spindlemotor_pos[drive] = 0;
|
||||
spindlemotor_fade_volume[drive] = 1.0f;
|
||||
spindlemotor_fade_samples_remaining[drive] = 0;
|
||||
} else {
|
||||
/* Normal startup */
|
||||
fdd_log("FDD Audio Drive %d: Starting motor (normal startup)\n", drive);
|
||||
spindlemotor_state[drive] = MOTOR_STATE_STARTING;
|
||||
spindlemotor_pos[drive] = 0;
|
||||
spindlemotor_fade_volume[drive] = 1.0f;
|
||||
@@ -410,6 +561,7 @@ fdd_audio_set_motor_enable(int drive, int motor_enable)
|
||||
}
|
||||
} else if (!motor_enable && motoron[drive]) {
|
||||
/* Motor stopping */
|
||||
fdd_log("FDD Audio Drive %d: Stopping motor\n", drive);
|
||||
spindlemotor_state[drive] = MOTOR_STATE_STOPPING;
|
||||
spindlemotor_pos[drive] = 0;
|
||||
spindlemotor_fade_volume[drive] = 1.0f;
|
||||
@@ -428,6 +580,8 @@ fdd_audio_play_single_track_step(int drive, int from_track, int to_track)
|
||||
if (abs(from_track - to_track) != 1)
|
||||
return; /* Only single track movements */
|
||||
|
||||
fdd_log("FDD Audio Drive %d: Single track step %d -> %d\n", drive, from_track, to_track);
|
||||
|
||||
single_step_state[drive].position = 0;
|
||||
single_step_state[drive].active = 1;
|
||||
}
|
||||
@@ -456,18 +610,20 @@ fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate duration based on drive type */
|
||||
int duration_samples;
|
||||
if (fdd_is_525(drive)) {
|
||||
/* 5.25": 285ms for 40 tracks = 7.125ms per track at 48kHz sample rate */
|
||||
/* 7.125ms = 0.007125s, at 48000 Hz = 342 samples per track */
|
||||
duration_samples = track_diff * 342;
|
||||
} else {
|
||||
/* 3.5": 495ms for 80 tracks = 6.1875ms per track at 48kHz sample rate */
|
||||
/* 6.1875ms = 0.0061875s, at 48000 Hz = 297 samples per track */
|
||||
duration_samples = track_diff * 297;
|
||||
}
|
||||
fdd_log("FDD Audio Drive %d: Multi-track seek %d -> %d (%d tracks)\n",
|
||||
drive, from_track, to_track, track_diff);
|
||||
|
||||
/* 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 > samples->multi_track_seek.samples)
|
||||
duration_samples = samples->multi_track_seek.samples;
|
||||
@@ -480,6 +636,92 @@ fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track)
|
||||
multi_seek_state[drive].to_track = to_track;
|
||||
}
|
||||
|
||||
static int16_t *
|
||||
load_wav(const char *filename, int *sample_count)
|
||||
{
|
||||
if ((filename == NULL) || (strlen(filename) == 0))
|
||||
return NULL;
|
||||
|
||||
if (strstr(filename, "..") != NULL)
|
||||
return NULL;
|
||||
|
||||
FILE *f = rom_fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
fdd_log("FDD Audio: Failed to open WAV file: %s\n", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
wav_header_t hdr;
|
||||
if (fread(&hdr, sizeof(hdr), 1, f) != 1) {
|
||||
fdd_log("FDD Audio: Failed to read WAV header from: %s\n", filename);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) {
|
||||
fdd_log("FDD Audio: Invalid WAV format in file: %s\n", filename);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Accept both mono and stereo, 16-bit PCM */
|
||||
if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || (hdr.num_channels != 1 && hdr.num_channels != 2)) {
|
||||
fdd_log("FDD Audio: Unsupported WAV format in %s (format: %d, bits: %d, channels: %d)\n",
|
||||
filename, hdr.audio_format, hdr.bits_per_sample, hdr.num_channels);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int input_samples = hdr.data_size / 2; /* 2 bytes per sample */
|
||||
int16_t *input_data = malloc(hdr.data_size);
|
||||
if (!input_data) {
|
||||
fdd_log("FDD Audio: Failed to allocate memory for WAV data: %s\n", filename);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) {
|
||||
fdd_log("FDD Audio: Failed to read WAV data from: %s\n", filename);
|
||||
free(input_data);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
int16_t *output_data;
|
||||
int output_samples;
|
||||
|
||||
if (hdr.num_channels == 1) {
|
||||
/* Convert mono to stereo */
|
||||
output_samples = input_samples; /* Number of stereo sample pairs */
|
||||
output_data = malloc(input_samples * 2 * sizeof(int16_t)); /* Allocate for stereo */
|
||||
if (!output_data) {
|
||||
fdd_log("FDD Audio: Failed to allocate stereo conversion buffer for: %s\n", filename);
|
||||
free(input_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert mono to stereo by duplicating each sample */
|
||||
for (int i = 0; i < input_samples; i++) {
|
||||
output_data[i * 2] = input_data[i]; /* Left channel */
|
||||
output_data[i * 2 + 1] = input_data[i]; /* Right channel */
|
||||
}
|
||||
|
||||
free(input_data);
|
||||
fdd_log("FDD Audio: Loaded %s (mono->stereo, %d samples)\n", filename, output_samples);
|
||||
} else {
|
||||
/* Already stereo */
|
||||
output_data = input_data;
|
||||
output_samples = input_samples / 2; /* Number of stereo sample pairs */
|
||||
fdd_log("FDD Audio: Loaded %s (stereo, %d samples)\n", filename, output_samples);
|
||||
}
|
||||
|
||||
if (sample_count)
|
||||
*sample_count = output_samples;
|
||||
|
||||
return output_data;
|
||||
}
|
||||
|
||||
void
|
||||
fdd_audio_callback(int16_t *buffer, int length)
|
||||
{
|
||||
@@ -776,11 +1018,24 @@ 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;
|
||||
}
|
||||
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) {
|
||||
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) drive; (void) motor_enable; }
|
||||
void fdd_audio_play_single_track_step(int drive, int from_track, int to_track) { (void) drive; (void) from_track; (void) to_track; }
|
||||
void fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track) { (void) drive; (void) from_track; (void) to_track; }
|
||||
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 */
|
||||
@@ -25,13 +25,6 @@
|
||||
#define FLOPPY_IMAGE_HISTORY 10
|
||||
#define SEEK_RECALIBRATE -999
|
||||
|
||||
/* Per-drive audio profiles */
|
||||
#define FDD_AUDIO_PROFILE_NONE 0
|
||||
#define FDD_AUDIO_PROFILE_MITSUMI 1
|
||||
#define FDD_AUDIO_PROFILE_PANASONIC 2
|
||||
#define FDD_AUDIO_PROFILE_TEAC 3
|
||||
#define FDD_AUDIO_PROFILE_MAX 4
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,32 @@ extern "C" {
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
|
||||
/* Audio sample configuration structure */
|
||||
typedef struct {
|
||||
char filename[512];
|
||||
float volume;
|
||||
} audio_sample_config_t;
|
||||
|
||||
/* Drive type specific audio configuration */
|
||||
typedef struct {
|
||||
int id;
|
||||
char name[128];
|
||||
char internal_name[64];
|
||||
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;
|
||||
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;
|
||||
} fdd_audio_profile_config_t;
|
||||
|
||||
#define FDD_AUDIO_PROFILE_MAX 64
|
||||
|
||||
/* Motor sound states */
|
||||
typedef enum {
|
||||
MOTOR_STATE_STOPPED = 0,
|
||||
@@ -52,12 +78,29 @@ typedef struct {
|
||||
#define FADE_DURATION_MS 75
|
||||
#define FADE_SAMPLES (48000 * FADE_DURATION_MS / 1000)
|
||||
|
||||
/* Functions for configuration management */
|
||||
extern void fdd_audio_load_profiles(void);
|
||||
extern int fdd_audio_get_profile_count(void);
|
||||
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);
|
||||
|
||||
#else
|
||||
|
||||
typedef enum {
|
||||
MOTOR_STATE_STOPPED = 0
|
||||
} motor_state_t;
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
char name[128];
|
||||
char internal_name[64];
|
||||
} fdd_audio_profile_config_t;
|
||||
|
||||
#define FDD_AUDIO_PROFILE_MAX 1
|
||||
|
||||
#endif /* DISABLE_FDD_AUDIO */
|
||||
|
||||
/* FDD audio initialization and cleanup */
|
||||
@@ -76,9 +119,6 @@ extern void fdd_audio_play_multi_track_seek(int drive, int from_track, int to_tr
|
||||
/* Audio callback function */
|
||||
extern void fdd_audio_callback(int16_t *buffer, int length);
|
||||
|
||||
/* State name helper function */
|
||||
extern const char *fdd_audio_motor_state_name(motor_state_t state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,7 @@ extern "C" {
|
||||
#include <86box/timer.h>
|
||||
#include <86box/fdd.h>
|
||||
#include <86box/cdrom.h>
|
||||
#include <86box/fdd_audio.h>
|
||||
}
|
||||
|
||||
#include "qt_models_common.hpp"
|
||||
@@ -136,7 +137,7 @@ SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent)
|
||||
model->setHeaderData(2, Qt::Horizontal, tr("Check BPB"));
|
||||
model->setHeaderData(3, Qt::Horizontal, tr("Audio"));
|
||||
|
||||
model->insertRows(0, FDD_NUM);
|
||||
model->insertRows(0, FDD_NUM);
|
||||
/* Floppy drives category */
|
||||
for (int i = 0; i < FDD_NUM; i++) {
|
||||
auto idx = model->index(i, 0);
|
||||
@@ -145,22 +146,21 @@ SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent)
|
||||
model->setData(idx.siblingAtColumn(1), fdd_get_turbo(i) > 0 ? tr("On") : tr("Off"));
|
||||
model->setData(idx.siblingAtColumn(2), fdd_get_check_bpb(i) > 0 ? tr("On") : tr("Off"));
|
||||
|
||||
int prof = fdd_get_audio_profile(i);
|
||||
int prof = fdd_get_audio_profile(i);
|
||||
QString profName;
|
||||
switch (prof) {
|
||||
case FDD_AUDIO_PROFILE_PANASONIC:
|
||||
profName = tr("Panasonic");
|
||||
break;
|
||||
case FDD_AUDIO_PROFILE_TEAC:
|
||||
profName = tr("Teac");
|
||||
break;
|
||||
case FDD_AUDIO_PROFILE_MITSUMI:
|
||||
profName = tr("Mitsumi");
|
||||
break;
|
||||
default:
|
||||
profName = tr("None");
|
||||
break;
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
// Get the profile name from the configuration system
|
||||
const char *name = fdd_audio_get_profile_internal_name(prof);
|
||||
if (name) {
|
||||
profName = QString(name);
|
||||
} else {
|
||||
profName = tr("None");
|
||||
}
|
||||
#else
|
||||
profName = tr("None");
|
||||
#endif
|
||||
|
||||
auto audioIdx = model->index(i, 3);
|
||||
model->setData(audioIdx, profName);
|
||||
model->setData(audioIdx, prof, Qt::UserRole);
|
||||
@@ -174,10 +174,13 @@ SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent)
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
ui->comboBoxFloppyAudio->setVisible(true);
|
||||
ui->comboBoxFloppyAudio->addItem(tr("None"), FDD_AUDIO_PROFILE_NONE);
|
||||
ui->comboBoxFloppyAudio->addItem(tr("Generic Mitsumi 3.5\" 1.44MB"), FDD_AUDIO_PROFILE_MITSUMI);
|
||||
ui->comboBoxFloppyAudio->addItem(tr("Panasonic JU-475-5 5.25\" 1.2MB"), FDD_AUDIO_PROFILE_PANASONIC);
|
||||
ui->comboBoxFloppyAudio->addItem(tr("Teac FD-55GFR 5.25\" 1.2MB"), FDD_AUDIO_PROFILE_TEAC);
|
||||
int profile_count = fdd_audio_get_profile_count();
|
||||
for (int i = 0; i < profile_count; i++) {
|
||||
const char *name = fdd_audio_get_profile_name(i);
|
||||
if (name) {
|
||||
ui->comboBoxFloppyAudio->addItem(name, i);
|
||||
}
|
||||
}
|
||||
ui->comboBoxFloppyAudio->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
#else
|
||||
ui->comboBoxFloppyAudio->setVisible(false);
|
||||
@@ -390,26 +393,22 @@ SettingsFloppyCDROM::on_comboBoxFloppyType_activated(int index)
|
||||
void
|
||||
SettingsFloppyCDROM::on_comboBoxFloppyAudio_activated(int)
|
||||
{
|
||||
auto idx = ui->tableViewFloppy->selectionModel()->currentIndex();
|
||||
int prof = ui->comboBoxFloppyAudio->currentData().toInt();
|
||||
auto idx = ui->tableViewFloppy->selectionModel()->currentIndex();
|
||||
int prof = ui->comboBoxFloppyAudio->currentData().toInt();
|
||||
QString profName;
|
||||
switch (prof) {
|
||||
case FDD_AUDIO_PROFILE_NONE:
|
||||
profName = tr("None");
|
||||
break;
|
||||
case FDD_AUDIO_PROFILE_PANASONIC:
|
||||
profName = tr("Panasonic");
|
||||
break;
|
||||
case FDD_AUDIO_PROFILE_TEAC:
|
||||
profName = tr("Teac");
|
||||
break;
|
||||
case FDD_AUDIO_PROFILE_MITSUMI:
|
||||
profName = tr("Mitsumi");
|
||||
break;
|
||||
default:
|
||||
profName = tr("None");
|
||||
break;
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
// Get the profile name from the configuration system
|
||||
const char *name = fdd_audio_get_profile_internal_name(prof);
|
||||
if (name) {
|
||||
profName = name;
|
||||
} else {
|
||||
profName = tr("None");
|
||||
}
|
||||
#else
|
||||
profName = tr("None");
|
||||
#endif
|
||||
|
||||
auto audioIdx = idx.siblingAtColumn(3);
|
||||
ui->tableViewFloppy->model()->setData(audioIdx, profName);
|
||||
ui->tableViewFloppy->model()->setData(audioIdx, prof, Qt::UserRole);
|
||||
|
||||
Reference in New Issue
Block a user