diff --git a/.gitignore b/.gitignore index 5935efd38..470a352ea 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ Makefile /src/*.exe /src/86Box /src/include/86box/version.h +/src/.vs +/src/out # Legacy Makefile /src/*.o diff --git a/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav b/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav new file mode 100644 index 000000000..90897531e Binary files /dev/null and b/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav differ diff --git a/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_start_48000_16_1_PCM.wav b/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_start_48000_16_1_PCM.wav new file mode 100644 index 000000000..16181d337 Binary files /dev/null and b/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_start_48000_16_1_PCM.wav differ diff --git a/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav b/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav new file mode 100644 index 000000000..f76e0281a Binary files /dev/null and b/samples/Panasonic_JU-475-5_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav differ diff --git a/samples/Panasonic_JU-475-5_5.25_1.2MB_seekdown_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav b/samples/Panasonic_JU-475-5_5.25_1.2MB_seekdown_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav new file mode 100644 index 000000000..3330fc7b8 Binary files /dev/null and b/samples/Panasonic_JU-475-5_5.25_1.2MB_seekdown_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav differ diff --git a/samples/Panasonic_JU-475-5_5.25_1.2MB_seekup_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav b/samples/Panasonic_JU-475-5_5.25_1.2MB_seekup_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav new file mode 100644 index 000000000..620a32d4d Binary files /dev/null and b/samples/Panasonic_JU-475-5_5.25_1.2MB_seekup_40_tracks_285ms_5ms_per_track_48000_16_1_PCM.wav differ diff --git a/samples/Panasonic_JU-475-5_5.25_1.2MB_track_step_48000_16_1_PCM.wav b/samples/Panasonic_JU-475-5_5.25_1.2MB_track_step_48000_16_1_PCM.wav new file mode 100644 index 000000000..fd1c60833 Binary files /dev/null and b/samples/Panasonic_JU-475-5_5.25_1.2MB_track_step_48000_16_1_PCM.wav differ diff --git a/samples/TeacFD-55GFR_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav b/samples/TeacFD-55GFR_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav new file mode 100644 index 000000000..bd8a4cce5 Binary files /dev/null and b/samples/TeacFD-55GFR_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav differ diff --git a/samples/TeacFD-55GFR_5.25_1.2MB_motor_start_48000_16_1_PCM.wav b/samples/TeacFD-55GFR_5.25_1.2MB_motor_start_48000_16_1_PCM.wav new file mode 100644 index 000000000..be3bddd70 Binary files /dev/null and b/samples/TeacFD-55GFR_5.25_1.2MB_motor_start_48000_16_1_PCM.wav differ diff --git a/samples/TeacFD-55GFR_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav b/samples/TeacFD-55GFR_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav new file mode 100644 index 000000000..6c6c4988c Binary files /dev/null and b/samples/TeacFD-55GFR_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav differ diff --git a/samples/TeacFD-55GFR_5.25_1.2MB_track_step_48000_16_1_PCM.wav b/samples/TeacFD-55GFR_5.25_1.2MB_track_step_48000_16_1_PCM.wav new file mode 100644 index 000000000..cc26c4809 Binary files /dev/null and b/samples/TeacFD-55GFR_5.25_1.2MB_track_step_48000_16_1_PCM.wav differ diff --git a/samples/TeacFD_55GFR_5.25_1.2MB_seekupdown_80_tracks1100ms_48000_16_1_PCM.wav b/samples/TeacFD_55GFR_5.25_1.2MB_seekupdown_80_tracks1100ms_48000_16_1_PCM.wav new file mode 100644 index 000000000..0b6be1124 Binary files /dev/null and b/samples/TeacFD_55GFR_5.25_1.2MB_seekupdown_80_tracks1100ms_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_offseek_80_tracks_1000ms_48000_16_1_PCM.wav b/samples/mitsumi_offseek_80_tracks_1000ms_48000_16_1_PCM.wav new file mode 100644 index 000000000..afd95b8d0 Binary files /dev/null and b/samples/mitsumi_offseek_80_tracks_1000ms_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_seek_80_tracks_380ms_48000_16_1_PCM.wav b/samples/mitsumi_seek_80_tracks_380ms_48000_16_1_PCM.wav new file mode 100644 index 000000000..a5516cae8 Binary files /dev/null and b/samples/mitsumi_seek_80_tracks_380ms_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_seek_80_tracks_495ms_48000_16_1_PCM.wav b/samples/mitsumi_seek_80_tracks_495ms_48000_16_1_PCM.wav new file mode 100644 index 000000000..da8b6f974 Binary files /dev/null and b/samples/mitsumi_seek_80_tracks_495ms_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_spindle_motor_loop_48000_16_1_PCM.wav b/samples/mitsumi_spindle_motor_loop_48000_16_1_PCM.wav new file mode 100644 index 000000000..190dfda3e Binary files /dev/null and b/samples/mitsumi_spindle_motor_loop_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_spindle_motor_start_48000_16_1_PCM.wav b/samples/mitsumi_spindle_motor_start_48000_16_1_PCM.wav new file mode 100644 index 000000000..2595ed089 Binary files /dev/null and b/samples/mitsumi_spindle_motor_start_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_spindle_motor_stop_48000_16_1_PCM.wav b/samples/mitsumi_spindle_motor_stop_48000_16_1_PCM.wav new file mode 100644 index 000000000..1411fd9e3 Binary files /dev/null and b/samples/mitsumi_spindle_motor_stop_48000_16_1_PCM.wav differ diff --git a/samples/mitsumi_track_step_48000_16_1_PCM.wav b/samples/mitsumi_track_step_48000_16_1_PCM.wav new file mode 100644 index 000000000..258793df5 Binary files /dev/null and b/samples/mitsumi_track_step_48000_16_1_PCM.wav differ diff --git a/samples/readme.txt b/samples/readme.txt new file mode 100644 index 000000000..5aca6b001 --- /dev/null +++ b/samples/readme.txt @@ -0,0 +1,13 @@ +These samples have been edited from Shiru's recordings. + +Originals can be found here: +http://shiru.untergrund.net/files/flopster.zip + +Original samples can edited and used separately from the Flopster VSTi under the +Creative Commons Attribution (CC-BY) license terms. + +Installation instructions +- Currently these samples need to be manually copied to the 86box executable path under '/samples' + or to the same directory with 86box executable. +- The samples are 48kHz 16bit 1 channel PCM WAVE samples. Immersive86box code does not make any resampling, + and assumes that the host audio system is using 48kHz 16bit output. \ No newline at end of file diff --git a/src/86box.c b/src/86box.c index 95f714747..591772458 100644 --- a/src/86box.c +++ b/src/86box.c @@ -239,6 +239,7 @@ char monitor_edid_path[1024] = { 0 }; /* (C) Path to double video_gl_input_scale = 1.0; /* (C) OpenGL 3.x input scale */ int video_gl_input_scale_mode = FULLSCR_SCALE_FULL; /* (C) OpenGL 3.x input stretch mode */ int color_scheme = 0; /* (C) Color scheme of UI (Windows-only) */ +int fdd_sounds_enabled = 1; /* (C) Floppy drive sounds enabled */ // Accelerator key array struct accelKey acc_keys[NUM_ACCELS]; diff --git a/src/config.c b/src/config.c index 42d4fe012..d4afe9d81 100644 --- a/src/config.c +++ b/src/config.c @@ -265,6 +265,7 @@ load_general(void) do_auto_pause = ini_section_get_int(cat, "do_auto_pause", 0); force_constant_mouse = ini_section_get_int(cat, "force_constant_mouse", 0); + fdd_sounds_enabled = ini_section_get_int(cat, "fdd_sounds_enabled", 1); p = ini_section_get_string(cat, "uuid", NULL); if (p != NULL) @@ -1436,6 +1437,15 @@ 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; + fdd_set_audio_profile(c, prof); + if (prof == def_prof) + ini_section_delete_var(cat, temp); + for (int i = 0; i < MAX_PREV_IMAGES; i++) { fdd_image_history[c][i] = (char *) calloc((MAX_IMAGE_PATH_LEN + 1) << 1, sizeof(char)); sprintf(temp, "fdd_%02i_image_history_%02i", c + 1, i + 1); @@ -2479,6 +2489,11 @@ save_general(void) else ini_section_delete_var(cat, "force_constant_mouse"); + if (fdd_sounds_enabled == 1) + ini_section_delete_var(cat, "fdd_sounds_enabled"); + else + ini_section_set_int(cat, "fdd_sounds_enabled", fdd_sounds_enabled); + char cpu_buf[128] = { 0 }; plat_get_cpu_string(cpu_buf, 128); ini_section_set_string(cat, "host_cpu", cpu_buf); @@ -3416,6 +3431,14 @@ save_floppy_and_cdrom_drives(void) else save_image_file(cat, temp, fdd_image_history[c][i]); } + + 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) + ini_section_delete_var(cat, temp); + else + ini_section_set_int(cat, temp, prof); } for (c = 0; c < CDROM_NUM; c++) { diff --git a/src/floppy/CMakeLists.txt b/src/floppy/CMakeLists.txt index c16ca06f4..c51d3717c 100644 --- a/src/floppy/CMakeLists.txt +++ b/src/floppy/CMakeLists.txt @@ -10,9 +10,12 @@ # # Authors: David Hrdlička, # Jasmine Iwanek, +# Toni Riikonen, # # Copyright 2020-2021 David Hrdlička. # Copyright 2024 Jasmine Iwanek. +# Copyright 2025 Toni Riikonen. +# All rights reserved. # add_library(fdd OBJECT @@ -31,6 +34,7 @@ add_library(fdd OBJECT fdd_pcjs.c fdd_mfm.c fdd_td0.c + fdd_audio.c ) add_subdirectory(lzw) diff --git a/src/floppy/fdc.c b/src/floppy/fdc.c index 75938e21a..bcf4a1299 100644 --- a/src/floppy/fdc.c +++ b/src/floppy/fdc.c @@ -11,9 +11,11 @@ * * Authors: Sarah Walker, * Miran Grca, + * Toni Riikonen, * * Copyright 2008-2020 Sarah Walker. * Copyright 2016-2020 Miran Grca. + * Copyright 2025 Toni Riikonen. */ #include #include @@ -662,6 +664,33 @@ real_drive(fdc_t *fdc, int drive) return drive; } +/* FDD notifies FDC when seek operation is complete */ +void +fdc_seek_complete_interrupt(fdc_t *fdc, int drive) +{ + if (!fdc) { + fdc_log("ERROR: fdc_seek_complete_interrupt called with NULL fdc!\n"); + return; + } + + if (FDC_FLAG_PCJR & fdc->flags) { + fdc->fintr = 1; + fdc->interrupt = -4; + fdc_callback(fdc); + return; + } + + fdc_log("FDD %c: Seek complete interrupt\n", 0x41 + drive); + + fdc->fintr = 1; + fdc->interrupt = -3; + fdc->st0 = 0x20 | (drive & 3); + if (fdd_get_head(drive)) + fdc->st0 |= 0x04; + + fdc_callback(fdc); +} + void fdc_seek(fdc_t *fdc, int drive, int params) { @@ -1229,7 +1258,7 @@ fdc_write(uint16_t addr, uint8_t val, void *priv) case 0x0f: /* Seek */ fdc->rw_drive = fdc->params[0] & 3; fdc->stat = (1 << fdc->drive); - if (!(fdc->flags & FDC_FLAG_PCJR)) + if (!(fdc->flags & FDC_FLAG_PCJR)) fdc->stat |= 0x80; fdc->head = 0; /* TODO: See if this is correct. */ fdc->st0 = fdc->params[0] & 0x03; @@ -1901,14 +1930,7 @@ fdc_callback(void *priv) case 0x0f: /*Seek*/ fdc->st0 = 0x20 | (fdc->params[0] & 3); fdc->stat = 0x80 | (1 << fdc->rw_drive); - if (fdc->flags & FDC_FLAG_PCJR) { - fdc->fintr = 1; - fdc->interrupt = -4; - timer_set_delay_u64(&fdc->timer, 1024 * TIMER_USEC); - } else { - fdc->interrupt = -3; - fdc_callback(fdc); - } + // Interrupts and callbacks in the fdd callback function return; case 0x10: /*Version*/ case 0x18: /*NSC*/ diff --git a/src/floppy/fdd.c b/src/floppy/fdd.c index 8e834c2fc..a0bef7ac6 100644 --- a/src/floppy/fdd.c +++ b/src/floppy/fdd.c @@ -11,16 +11,20 @@ * Authors: Sarah Walker, * Miran Grca, * Fred N. van Kempen, + * Toni Riikonen, * * Copyright 2008-2019 Sarah Walker. * Copyright 2016-2019 Miran Grca. * Copyright 2018-2019 Fred N. van Kempen. + * Copyright 2025 Toni Riikonen. */ #include #include #include #include #include +#include + #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/timer.h> @@ -37,6 +41,7 @@ #include <86box/fdd_mfm.h> #include <86box/fdd_td0.h> #include <86box/fdc.h> +#include <86box/fdd_audio.h> /* Flags: Bit 0: 300 rpm supported; @@ -78,15 +83,17 @@ char floppyfns[FDD_NUM][512]; char *fdd_image_history[FDD_NUM][FLOPPY_IMAGE_HISTORY]; pc_timer_t fdd_poll_time[FDD_NUM]; +pc_timer_t fdd_seek_timer[FDD_NUM]; static int fdd_notfound = 0; static int driveloaders[FDD_NUM]; +static int fdd_audio_profile[FDD_NUM] = { 0 }; int writeprot[FDD_NUM]; int fwriteprot[FDD_NUM]; int fdd_changed[FDD_NUM]; int ui_writeprot[FDD_NUM] = { 0, 0, 0, 0 }; -int drive_empty[FDD_NUM] = { 1, 1, 1, 1 }; +int drive_empty[FDD_NUM] = { 1, 1, 1, 1 }; DRIVE drives[FDD_NUM]; @@ -99,9 +106,9 @@ d86f_handler_t d86f_handler[FDD_NUM]; static const struct { const char *ext; - void (*load)(int drive, char *fn); - void (*close)(int drive); - int size; + void (*load)(int drive, char *fn); + void (*close)(int drive); + int size; } loaders[] = { { "001", img_load, img_close, -1 }, { "002", img_load, img_close, -1 }, @@ -145,35 +152,35 @@ static const struct { const char *internal_name; } drive_types[] = { /* None */ - { 0, 0, "None", "none" }, + { 0, 0, "None", "none" }, /* 5.25" 1DD */ - { 43, FLAG_RPM_300 | FLAG_525 | FLAG_HOLE0, "5.25\" 180k", "525_1dd" }, + { 43, FLAG_RPM_300 | FLAG_525 | FLAG_HOLE0, "5.25\" 180k", "525_1dd" }, /* 5.25" DD */ - { 43, FLAG_RPM_300 | FLAG_525 | FLAG_DS | FLAG_HOLE0, "5.25\" 360k", "525_2dd" }, + { 43, FLAG_RPM_300 | FLAG_525 | FLAG_DS | FLAG_HOLE0, "5.25\" 360k", "525_2dd" }, /* 5.25" QD */ - { 86, FLAG_RPM_300 | FLAG_525 | FLAG_DS | FLAG_HOLE0 | FLAG_DOUBLE_STEP, "5.25\" 720k", "525_2qd" }, + { 86, FLAG_RPM_300 | FLAG_525 | FLAG_DS | FLAG_HOLE0 | FLAG_DOUBLE_STEP, "5.25\" 720k", "525_2qd" }, /* 5.25" HD */ - { 86, FLAG_RPM_360 | FLAG_525 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP | FLAG_PS2, "5.25\" 1.2M", "525_2hd" }, + { 86, FLAG_RPM_360 | FLAG_525 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP | FLAG_PS2, "5.25\" 1.2M", "525_2hd" }, /* 5.25" HD Dual RPM */ - { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_525 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP, "5.25\" 1.2M 300/360 RPM", "525_2hd_dualrpm" }, + { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_525 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP, "5.25\" 1.2M 300/360 RPM", "525_2hd_dualrpm" }, /* 3.5" 1DD */ - { 86, FLAG_RPM_300 | FLAG_HOLE0 | FLAG_DOUBLE_STEP, "3.5\" 360k", "35_1dd" }, + { 86, FLAG_RPM_300 | FLAG_HOLE0 | FLAG_DOUBLE_STEP, "3.5\" 360k", "35_1dd" }, /* 3.5" DD, Equivalent to TEAC FD-235F */ - { 86, FLAG_RPM_300 | FLAG_DS | FLAG_HOLE0 | FLAG_DOUBLE_STEP, "3.5\" 720k", "35_2dd" }, + { 86, FLAG_RPM_300 | FLAG_DS | FLAG_HOLE0 | FLAG_DOUBLE_STEP, "3.5\" 720k", "35_2dd" }, /* 3.5" HD, Equivalent to TEAC FD-235HF */ - { 86, FLAG_RPM_300 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP | FLAG_PS2, "3.5\" 1.44M", "35_2hd" }, + { 86, FLAG_RPM_300 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP | FLAG_PS2, "3.5\" 1.44M", "35_2hd" }, /* TODO: 3.5" DD, Equivalent to TEAC FD-235GF */ -// { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP, "3.5\" 1.25M", "35_2hd_2mode" }, + // { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP, "3.5\" 1.25M", "35_2hd_2mode" }, /* 3.5" HD PC-98 */ - { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP | FLAG_INVERT_DENSEL, "3.5\" 1.25M PC-98", "35_2hd_nec" }, + { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP | FLAG_INVERT_DENSEL, "3.5\" 1.25M PC-98", "35_2hd_nec" }, /* 3.5" HD 3-Mode, Equivalent to TEAC FD-235HG */ - { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP, "3.5\" 1.44M 300/360 RPM", "35_2hd_3mode" }, + { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_DOUBLE_STEP, "3.5\" 1.44M 300/360 RPM", "35_2hd_3mode" }, /* 3.5" ED, Equivalent to TEAC FD-235J */ - { 86, FLAG_RPM_300 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_HOLE2 | FLAG_DOUBLE_STEP, "3.5\" 2.88M", "35_2ed" }, + { 86, FLAG_RPM_300 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_HOLE2 | FLAG_DOUBLE_STEP, "3.5\" 2.88M", "35_2ed" }, /* 3.5" ED Dual RPM, Equivalent to TEAC FD-335J */ - { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_HOLE2 | FLAG_DOUBLE_STEP, "3.5\" 2.88M 300/360 RPM", "35_2ed_dualrpm" }, + { 86, FLAG_RPM_300 | FLAG_RPM_360 | FLAG_DS | FLAG_HOLE0 | FLAG_HOLE1 | FLAG_HOLE2 | FLAG_DOUBLE_STEP, "3.5\" 2.88M 300/360 RPM", "35_2ed_dualrpm" }, /* End of list */ - { -1, -1, "", "" } + { -1, -1, "", "" } }; #ifdef ENABLE_FDD_LOG @@ -182,11 +189,32 @@ int fdd_do_log = ENABLE_FDD_LOG; static void fdd_log(const char *fmt, ...) { - va_list ap; + va_list ap, ap_copy; + char timebuf[32]; + char fullbuf[1056]; // 32 + 1024 bytes for timestamp + message if (fdd_do_log) { + uint32_t ticks = plat_get_ticks(); + uint32_t seconds = ticks / 1000; + uint32_t milliseconds = ticks % 1000; + + snprintf(timebuf, sizeof(timebuf), "[%07u.%03u] ", seconds, milliseconds); + va_start(ap, fmt); - pclog_ex(fmt, ap); + va_copy(ap_copy, ap); + + strcpy(fullbuf, timebuf); + vsnprintf(fullbuf + strlen(timebuf), sizeof(fullbuf) - strlen(timebuf), fmt, ap_copy); + + va_end(ap_copy); + va_end(ap); + + va_start(ap, fmt); + va_end(ap); + + char *msg = fullbuf; + va_start(ap, fmt); + pclog_ex("%s", (va_list) &msg); va_end(ap); } } @@ -194,6 +222,24 @@ fdd_log(const char *fmt, ...) # define fdd_log(fmt, ...) #endif +void +fdd_set_audio_profile(int drive, int profile) +{ + if (drive < 0 || drive >= FDD_NUM) + return; + if (profile < 0 || profile >= FDD_AUDIO_PROFILE_MAX) + profile = 0; + fdd_audio_profile[drive] = profile; +} + +int +fdd_get_audio_profile(int drive) +{ + if (drive < 0 || drive >= FDD_NUM) + return 0; + return fdd_audio_profile[drive]; +} + char * fdd_getname(int type) { @@ -242,12 +288,26 @@ fdd_forced_seek(int drive, int track_diff) fdd_do_seek(drive, fdd[drive].track); } +static void +fdd_seek_complete_callback(void *priv) +{ + DRIVE *drive = (DRIVE *) priv; + + fdd_log("fdd_seek_complete_callback(drive=%d) - TIMER FIRED! seek_in_progress=1\n", drive->id); + fdd_log("Notifying FDC of seek completion\n"); + fdd_do_seek(drive->id, fdd[drive->id].track); + fdc_seek_complete_interrupt(fdd_fdc, drive->id); +} + void fdd_seek(int drive, int track_diff) { + fdd_log("fdd_seek(drive=%d, track_diff=%d)\n", drive, track_diff); if (!track_diff) return; + int old_track = fdd[drive].track; + fdd[drive].track += track_diff; if (fdd[drive].track < 0) @@ -258,12 +318,36 @@ fdd_seek(int drive, int track_diff) fdd_changed[drive] = 0; - fdd_do_seek(drive, fdd[drive].track); + /* 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) { + /* Multi-track seek */ + fdd_audio_play_multi_track_seek(drive, old_track, fdd[drive].track); + } + + if (old_track + track_diff < 0) { + fdd_do_seek(drive, fdd[drive].track); + return; + } + + if (!fdd_seek_timer[drive].callback) { + 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; + 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); } int fdd_track0(int drive) { + fdd_log("fdd_track0(drive=%d)\n", drive); + /* If drive is disabled, TRK0 never gets set. */ if (!drive_types[fdd[drive].type].max_track) return 0; @@ -406,6 +490,7 @@ fdd_is_double_sided(int drive) void fdd_set_head(int drive, int head) { + fdd_log("fdd_set_head(%d, %d)\n", drive, head); if (head && !fdd_is_double_sided(drive)) fdd[drive].head = 0; else @@ -453,14 +538,13 @@ fdd_get_densel(int drive) void fdd_load(int drive, char *fn) { + fdd_log("fdd_load(%d, %s)\n", drive, fn); int c = 0; int size; const char *p; FILE *fp; int offs = 0; - fdd_log("FDD: loading drive %d with '%s'\n", drive, fn); - if (!fn) return; if (strstr(fn, "wp://") == fn) { @@ -493,7 +577,6 @@ fdd_load(int drive, char *fn) c++; } } - fdd_log("FDD: could not load '%s' %s\n", fn, p); drive_empty[drive] = 1; fdd_set_head(drive, 0); memset(floppyfns[drive], 0, sizeof(floppyfns[drive])); @@ -503,8 +586,6 @@ fdd_load(int drive, char *fn) void fdd_close(int drive) { - fdd_log("FDD: closing drive %d\n", drive); - d86f_stop(drive); /* Call this first of all to make sure the 86F poll is back to idle state. */ if (loaders[driveloaders[drive]].close) loaders[driveloaders[drive]].close(drive); @@ -546,11 +627,14 @@ fdd_byteperiod(int drive) void fdd_set_motor_enable(int drive, int motor_enable) { - /* I think here is where spin-up and spin-down should be implemented. */ - if (motor_enable && !motoron[drive]) + fdd_log("fdd_set_motor_enable(%d, %d)\n", drive, motor_enable); + fdd_audio_set_motor_enable(drive, motor_enable); + + if (motor_enable && !motoron[drive]) { timer_set_delay_u64(&fdd_poll_time[drive], fdd_byteperiod(drive)); - else if (!motor_enable) + } else if (!motor_enable && motoron[drive]) { timer_disable(&fdd_poll_time[drive]); + } motoron[drive] = motor_enable; } @@ -615,6 +699,7 @@ fdd_reset(void) void fdd_readsector(int drive, int sector, int track, int side, int density, int sector_size) { + fdd_log("fdd_readsector(%d, %d, %d, %d, %d, %d)\n", drive, sector, track, side, density, sector_size); if (drives[drive].readsector) drives[drive].readsector(drive, sector, track, side, density, sector_size); else @@ -624,6 +709,7 @@ fdd_readsector(int drive, int sector, int track, int side, int density, int sect void fdd_writesector(int drive, int sector, int track, int side, int density, int sector_size) { + fdd_log("fdd_writesector(%d, %d, %d, %d, %d, %d)\n", drive, sector, track, side, density, sector_size); if (drives[drive].writesector) drives[drive].writesector(drive, sector, track, side, density, sector_size); else @@ -688,10 +774,14 @@ fdd_init(void) for (i = 0; i < FDD_NUM; i++) { fdd_load(i, floppyfns[i]); } + + if (fdd_sounds_enabled) { + fdd_audio_init(); + } } void fdd_do_writeback(int drive) { d86f_handler[drive].writeback(drive); -} +} \ No newline at end of file diff --git a/src/floppy/fdd_audio.c b/src/floppy/fdd_audio.c new file mode 100644 index 000000000..9ef4bf991 --- /dev/null +++ b/src/floppy/fdd_audio.c @@ -0,0 +1,667 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Implementation of the floppy drive audio emulation. + * + * Authors: Toni Riikonen, + * + * Copyright 2025 Toni Riikonen. + */ +#include +#include +#include +#include +#include +#include + +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/timer.h> +#include <86box/fdd.h> +#include <86box/fdd_audio.h> +#include <86box/sound.h> +#include <86box/plat.h> +#include <86box/path.h> + +#ifndef DISABLE_FDD_AUDIO + +/* Audio sample structure */ +typedef struct { + const char *filename; + int16_t *buffer; + int samples; + float volume; +} audio_sample_t; + +/* Single step audio state */ +typedef struct { + int position; + int active; +} single_step_state_t; + +/* Multi-track seek audio state */ +typedef struct { + int position; + int active; + int duration_samples; + int from_track; + int to_track; +} multi_seek_state_t; + +/* Drive type specific audio samples */ +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 multi_track_seek; +} drive_audio_samples_t; + +/* 5.25" Teac FD-55GFR sample set */ +static drive_audio_samples_t samples_teac = { + .spindlemotor_start = { + .filename = "TeacFD-55GFR_5.25_1.2MB_motor_start_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 3.0f + }, + .spindlemotor_loop = { + .filename = "TeacFD-55GFR_5.25_1.2MB_motor_loop_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 3.0f + }, + .spindlemotor_stop = { + .filename = "TeacFD-55GFR_5.25_1.2MB_motor_stop_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 3.0f + }, + .single_track_step = { + .filename = "TeacFD-55GFR_5.25_1.2MB_track_step_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 2.0f + }, + .multi_track_seek = { + .filename = "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 = "mitsumi_spindle_motor_start_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 0.2f + }, + .spindlemotor_loop = { + .filename = "mitsumi_spindle_motor_loop_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 0.2f + }, + .spindlemotor_stop = { + .filename = "mitsumi_spindle_motor_stop_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 0.2f + }, + .single_track_step = { + .filename = "mitsumi_track_step_48000_16_1_PCM.wav", + .buffer = NULL, .samples = 0, .volume = 1.0f + }, + .multi_track_seek = { + .filename = "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 = "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 = "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 = "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 = "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 = "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 + } +}; + +/* Audio state for each drive */ +static int spindlemotor_pos[FDD_NUM] = {}; +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] = {}; + +extern uint64_t motoron[FDD_NUM]; +extern char exe_path[2048]; /* path (dir) of executable */ + +extern int fdd_get_audio_profile(int drive); /* from fdd.h */ + +/* Forward declaration */ +static int16_t *load_wav(const char *filename, int *sample_count); + + +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; + } +} + +static int16_t * +load_wav(const char *filename, int *sample_count) +{ + FILE *f = NULL; + char full_path[2048]; + + if (!filename || strlen(filename) == 0) { + return NULL; + } + + if (strstr(filename, "..") != NULL || strchr(filename, '/') != NULL || strchr(filename, '\\') != NULL) { + return NULL; + } + + for (const char *p = filename; *p; p++) { + if (!isalnum(*p) && *p != '.' && *p != '_' && *p != '-') { + return NULL; + } + } + + path_append_filename(full_path, exe_path, "roms"); + path_append_filename(full_path, full_path, "samples"); + path_append_filename(full_path, full_path, "fdd"); + path_append_filename(full_path, full_path, filename); + f = fopen(full_path, "rb"); + if (!f) { + path_append_filename(full_path, exe_path, "samples"); + path_append_filename(full_path, full_path, filename); + f = fopen(full_path, "rb"); + if (!f) + return NULL; + } + + wav_header_t hdr; + if (fread(&hdr, sizeof(hdr), 1, f) != 1) { + fclose(f); + return NULL; + } + + if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) { + fclose(f); + return NULL; + } + + /* Accept both mono and stereo, 16-bit PCM */ + if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || (hdr.num_channels != 1 && hdr.num_channels != 2)) { + fclose(f); + return NULL; + } + + int input_samples = hdr.data_size / 2; /* 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); + } else { + /* Already stereo */ + output_data = input_data; + output_samples = input_samples / 2; /* Number of stereo sample pairs */ + } + + 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); + + /* Initialize audio state for all drives */ + for (i = 0; i < FDD_NUM; i++) { + spindlemotor_pos[i] = 0; + spindlemotor_state[i] = MOTOR_STATE_STOPPED; + 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; + } + + /* Initialize sound thread */ + sound_fdd_thread_init(); +} + +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; + } + + /* 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(); +} + +void +fdd_audio_set_motor_enable(int drive, int motor_enable) +{ + if (!fdd_sounds_enabled) + return; + + drive_audio_samples_t *samples = get_drive_samples(drive); + if (!samples) + return; + + if (motor_enable && !motoron[drive]) { + /* Motor starting up */ + if (spindlemotor_state[drive] == MOTOR_STATE_STOPPING) { + /* Interrupt stop sequence and transition back to loop */ + 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 */ + spindlemotor_state[drive] = MOTOR_STATE_STARTING; + spindlemotor_pos[drive] = 0; + spindlemotor_fade_volume[drive] = 1.0f; + spindlemotor_fade_samples_remaining[drive] = 0; + } + } else if (!motor_enable && motoron[drive]) { + /* Motor stopping */ + spindlemotor_state[drive] = MOTOR_STATE_STOPPING; + spindlemotor_pos[drive] = 0; + spindlemotor_fade_volume[drive] = 1.0f; + spindlemotor_fade_samples_remaining[drive] = FADE_SAMPLES; + } +} + +void +fdd_audio_play_single_track_step(int drive, int from_track, int to_track) +{ + if (!fdd_sounds_enabled) + return; + + if (drive < 0 || drive >= FDD_NUM) + return; + if (abs(from_track - to_track) != 1) + return; /* Only single track movements */ + + 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) +{ + if (!fdd_sounds_enabled) + return; + + if (drive < 0 || drive >= FDD_NUM) + return; + + int track_diff = abs(from_track - to_track); + if (track_diff <= 1) + return; /* Use single step for 1 track movements */ + + drive_audio_samples_t *samples = get_drive_samples(drive); + if (!samples || !samples->multi_track_seek.buffer || samples->multi_track_seek.samples == 0) + return; /* No multi-track seek sample loaded */ + + /* 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) { + 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; + } + + /* Clamp to maximum available sample length */ + if (duration_samples > samples->multi_track_seek.samples) + duration_samples = samples->multi_track_seek.samples; + + /* 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; +} + +void +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) { + any_audio_active = 1; + break; + } + } + + if (!any_audio_active) + return; + + float *float_buffer = (float *) buffer; + int samples_in_buffer = length / 2; + + /* Process audio for all drives */ + for (int drive = 0; drive < FDD_NUM; drive++) { + 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; + + /* Process motor audio */ + if (spindlemotor_state[drive] != MOTOR_STATE_STOPPED) { + switch (spindlemotor_state[drive]) { + case MOTOR_STATE_STARTING: + if (samples->spindlemotor_start.buffer && spindlemotor_pos[drive] < samples->spindlemotor_start.samples) { + /* Play start sound with volume control */ + left_sample = (float) samples->spindlemotor_start.buffer[spindlemotor_pos[drive] * 2] / 32768.0f * samples->spindlemotor_start.volume; + right_sample = (float) samples->spindlemotor_start.buffer[spindlemotor_pos[drive] * 2 + 1] / 32768.0f * samples->spindlemotor_start.volume; + spindlemotor_pos[drive]++; + } else { + /* Start sound finished, transition to loop */ + spindlemotor_state[drive] = MOTOR_STATE_RUNNING; + spindlemotor_pos[drive] = 0; + } + break; + + case MOTOR_STATE_RUNNING: + if (samples->spindlemotor_loop.buffer && samples->spindlemotor_loop.samples > 0) { + /* Play loop sound with volume control */ + left_sample = (float) samples->spindlemotor_loop.buffer[spindlemotor_pos[drive] * 2] / 32768.0f * samples->spindlemotor_loop.volume; + right_sample = (float) samples->spindlemotor_loop.buffer[spindlemotor_pos[drive] * 2 + 1] / 32768.0f * samples->spindlemotor_loop.volume; + spindlemotor_pos[drive]++; + + /* Loop back to beginning */ + if (spindlemotor_pos[drive] >= samples->spindlemotor_loop.samples) { + spindlemotor_pos[drive] = 0; + } + } + break; + + case MOTOR_STATE_STOPPING: + if (spindlemotor_fade_samples_remaining[drive] > 0) { + /* Mix fading loop sound with rising stop sound */ + float loop_volume = spindlemotor_fade_volume[drive]; + float stop_volume = 1.0f - loop_volume; + + float loop_left = 0.0f, loop_right = 0.0f; + float stop_left = 0.0f, stop_right = 0.0f; + + /* Get loop sample (continue from current position) with volume control */ + if (samples->spindlemotor_loop.buffer && samples->spindlemotor_loop.samples > 0) { + int loop_pos = spindlemotor_pos[drive] % samples->spindlemotor_loop.samples; + loop_left = (float) samples->spindlemotor_loop.buffer[loop_pos * 2] / 32768.0f * samples->spindlemotor_loop.volume; + loop_right = (float) samples->spindlemotor_loop.buffer[loop_pos * 2 + 1] / 32768.0f * samples->spindlemotor_loop.volume; + } + + /* Get stop sample with volume control */ + if (samples->spindlemotor_stop.buffer && spindlemotor_pos[drive] < samples->spindlemotor_stop.samples) { + stop_left = (float) samples->spindlemotor_stop.buffer[spindlemotor_pos[drive] * 2] / 32768.0f * samples->spindlemotor_stop.volume; + stop_right = (float) samples->spindlemotor_stop.buffer[spindlemotor_pos[drive] * 2 + 1] / 32768.0f * samples->spindlemotor_stop.volume; + } + + /* Mix the sounds */ + left_sample = loop_left * loop_volume + stop_left * stop_volume; + right_sample = loop_right * loop_volume + stop_right * stop_volume; + + spindlemotor_pos[drive]++; + spindlemotor_fade_samples_remaining[drive]--; + + /* Update fade volume */ + spindlemotor_fade_volume[drive] = (float) spindlemotor_fade_samples_remaining[drive] / FADE_SAMPLES; + } else { + /* Fade completed, play remaining stop sound with volume control */ + if (samples->spindlemotor_stop.buffer && spindlemotor_pos[drive] < samples->spindlemotor_stop.samples) { + left_sample = (float) samples->spindlemotor_stop.buffer[spindlemotor_pos[drive] * 2] / 32768.0f * samples->spindlemotor_stop.volume; + right_sample = (float) samples->spindlemotor_stop.buffer[spindlemotor_pos[drive] * 2 + 1] / 32768.0f * samples->spindlemotor_stop.volume; + spindlemotor_pos[drive]++; + } else { + /* Stop sound finished */ + spindlemotor_state[drive] = MOTOR_STATE_STOPPED; + /* Note: Timer disabling is handled by fdd.c, not here */ + } + } + break; + + default: + break; + } + } + + /* Process single step audio */ + if (single_step_state[drive].active) { + if (samples->single_track_step.buffer && single_step_state[drive].position < samples->single_track_step.samples) { + /* Mix step sound with motor sound with volume control */ + float step_left = (float) samples->single_track_step.buffer[single_step_state[drive].position * 2] / 32768.0f * samples->single_track_step.volume; + float step_right = (float) samples->single_track_step.buffer[single_step_state[drive].position * 2 + 1] / 32768.0f * samples->single_track_step.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 */ + if (multi_seek_state[drive].active) { + if (samples->multi_track_seek.buffer && + multi_seek_state[drive].position < multi_seek_state[drive].duration_samples && + multi_seek_state[drive].position < samples->multi_track_seek.samples) { + /* Mix seek sound with motor sound with volume control */ + float seek_left = (float) samples->multi_track_seek.buffer[multi_seek_state[drive].position * 2] / 32768.0f * samples->multi_track_seek.volume; + float seek_right = (float) samples->multi_track_seek.buffer[multi_seek_state[drive].position * 2 + 1] / 32768.0f * samples->multi_track_seek.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; + } + } + + /* Mix this drive's audio into the buffer */ + float_buffer[i * 2] += left_sample; + float_buffer[i * 2 + 1] += right_sample; + } + } +} +#else + +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_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/86box.h b/src/include/86box/86box.h index 701e93e28..6c924e031 100644 --- a/src/include/86box/86box.h +++ b/src/include/86box/86box.h @@ -219,6 +219,7 @@ extern int monitor_edid; /* (C) Which EDID to use. 0=default, extern char monitor_edid_path[1024]; /* (C) Path to custom EDID */ extern int color_scheme; /* (C) Color scheme of UI (Windows-only) */ +extern int fdd_sounds_enabled; /* (C) Enable floppy drive sounds */ #ifndef USE_NEW_DYNAREC extern FILE *stdlog; /* file to log output to */ diff --git a/src/include/86box/fdc.h b/src/include/86box/fdc.h index cd1e58db7..06ccafa39 100644 --- a/src/include/86box/fdc.h +++ b/src/include/86box/fdc.h @@ -14,10 +14,12 @@ * Authors: Sarah Walker, * Miran Grca, * Fred N. van Kempen, + * Toni Riikonen, * * Copyright 2008-2020 Sarah Walker. * Copyright 2016-2020 Miran Grca. * Copyright 2018-2020 Fred N. van Kempen. + * Copyright 2025 Toni Riikonen. */ #ifndef EMU_FDC_H #define EMU_FDC_H @@ -251,6 +253,7 @@ extern uint8_t fdc_read(uint16_t addr, void *priv); extern void fdc_reset(void *priv); extern uint8_t fdc_get_current_drive(void); +extern void fdc_seek_complete_interrupt(fdc_t *fdc, int drive); #ifdef EMU_DEVICE_H extern const device_t fdc_xt_device; diff --git a/src/include/86box/fdd.h b/src/include/86box/fdd.h index d6ade3bc6..b73365de2 100644 --- a/src/include/86box/fdd.h +++ b/src/include/86box/fdd.h @@ -11,10 +11,12 @@ * Authors: Sarah Walker, * Miran Grca, * Fred N. van Kempen, + * Toni Riikonen, * * Copyright 2008-2025 Sarah Walker. * Copyright 2016-2025 Miran Grca. * Copyright 2018-2025 Fred N. van Kempen. + * Copyright 2025 Toni Riikonen. */ #ifndef EMU_FDD_H #define EMU_FDD_H @@ -23,6 +25,13 @@ #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 @@ -53,6 +62,10 @@ extern int fdd_get_check_bpb(int drive); extern void fdd_set_type(int drive, int type); extern int fdd_get_type(int drive); +/* New audio profile accessors */ +extern void fdd_set_audio_profile(int drive, int profile); +extern int fdd_get_audio_profile(int drive); + extern int fdd_get_flags(int drive); extern int fdd_get_densel(int drive); diff --git a/src/include/86box/fdd_audio.h b/src/include/86box/fdd_audio.h new file mode 100644 index 000000000..acd4e9350 --- /dev/null +++ b/src/include/86box/fdd_audio.h @@ -0,0 +1,86 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for the floppy drive audio emulation. + * + * Authors: Toni Riikonen, + * + * Copyright 2025 Toni Riikonen. + */ +#ifndef EMU_FDD_AUDIO_H +#define EMU_FDD_AUDIO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DISABLE_FDD_AUDIO + +/* Motor sound states */ +typedef enum { + MOTOR_STATE_STOPPED = 0, + MOTOR_STATE_STARTING, + MOTOR_STATE_RUNNING, + MOTOR_STATE_STOPPING +} motor_state_t; + +/* WAV header structure */ +typedef struct { + char riff[4]; + uint32_t size; + char wave[4]; + char fmt[4]; + uint32_t fmt_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char data[4]; + uint32_t data_size; +} wav_header_t; + +/* Fade duration: 75ms at 48kHz = 3600 samples */ +#define FADE_DURATION_MS 75 +#define FADE_SAMPLES (48000 * FADE_DURATION_MS / 1000) + +#else + +typedef enum { + MOTOR_STATE_STOPPED = 0 +} motor_state_t; + +#endif /* DISABLE_FDD_AUDIO */ + +/* FDD audio initialization and cleanup */ +extern void fdd_audio_init(void); +extern void fdd_audio_close(void); + +/* Motor control for audio */ +extern void fdd_audio_set_motor_enable(int drive, int motor_enable); + +/* Single sector movement audio */ +extern void fdd_audio_play_single_track_step(int drive, int from_track, int to_track); + +/* Multi-track seek audio */ +extern void fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track); + +/* 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 + +#endif /*EMU_FDD_AUDIO_H*/ \ No newline at end of file diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index bbd2ff820..5815d3f66 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -103,12 +103,16 @@ extern void sound_card_reset(void); extern void sound_cd_thread_end(void); extern void sound_cd_thread_reset(void); +extern void sound_fdd_thread_init(void); +extern void sound_fdd_thread_end(void); + extern void closeal(void); extern void inital(void); extern void givealbuffer(const void *buf); extern void givealbuffer_music(const void *buf); extern void givealbuffer_wt(const void *buf); extern void givealbuffer_cd(const void *buf); +extern void givealbuffer_fdd(const void *buf, const uint32_t size); #define sb_vibra16c_onboard_relocate_base sb_vibra16s_onboard_relocate_base #define sb_vibra16cl_onboard_relocate_base sb_vibra16s_onboard_relocate_base diff --git a/src/qt/qt_settingsfloppycdrom.cpp b/src/qt/qt_settingsfloppycdrom.cpp index 588b1aff9..6ec130044 100644 --- a/src/qt/qt_settingsfloppycdrom.cpp +++ b/src/qt/qt_settingsfloppycdrom.cpp @@ -129,11 +129,12 @@ SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent) ++i; } - model = new QStandardItemModel(0, 3, this); + model = new QStandardItemModel(0, 4, this); ui->tableViewFloppy->setModel(model); model->setHeaderData(0, Qt::Horizontal, tr("Type")); model->setHeaderData(1, Qt::Horizontal, tr("Turbo")); model->setHeaderData(2, Qt::Horizontal, tr("Check BPB")); + model->setHeaderData(3, Qt::Horizontal, tr("Audio")); model->insertRows(0, FDD_NUM); /* Floppy drives category */ @@ -143,6 +144,26 @@ SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent) setFloppyType(model, idx, type); 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); + 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; + } + auto audioIdx = model->index(i, 3); + model->setData(audioIdx, profName); + model->setData(audioIdx, prof, Qt::UserRole); } ui->tableViewFloppy->resizeColumnsToContents(); @@ -150,7 +171,22 @@ SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent) connect(ui->tableViewFloppy->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsFloppyCDROM::onFloppyRowChanged); + +#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); + ui->comboBoxFloppyAudio->setSizeAdjustPolicy(QComboBox::AdjustToContents); +#else + ui->comboBoxFloppyAudio->setVisible(false); +#endif + + // Set initial selection and trigger the row changed event to update controls ui->tableViewFloppy->setCurrentIndex(model->index(0, 0)); + // Manually trigger the row changed event to ensure audio selection is updated + onFloppyRowChanged(model->index(0, 0)); cdrom_disabled_icon = QIcon(":/settings/qt/icons/cdrom_disabled.ico"); cdrom_icon = QIcon(":/settings/qt/icons/cdrom.ico"); @@ -233,6 +269,9 @@ SettingsFloppyCDROM::save() fdd_set_type(i, model->index(i, 0).data(Qt::UserRole).toInt()); fdd_set_turbo(i, model->index(i, 1).data() == tr("On") ? 1 : 0); fdd_set_check_bpb(i, model->index(i, 2).data() == tr("On") ? 1 : 0); +#ifndef DISABLE_FDD_AUDIO + fdd_set_audio_profile(i, model->index(i, 3).data(Qt::UserRole).toInt()); +#endif } /* Removable devices category */ @@ -250,6 +289,12 @@ SettingsFloppyCDROM::save() cdrom[i].speed = model->index(i, 1).data(Qt::UserRole).toUInt(); cdrom_set_type(i, model->index(i, 2).data(Qt::UserRole).toInt()); } + +#ifdef DISABLE_FDD_AUDIO + fdd_sounds_enabled = 0; +#else + fdd_sounds_enabled = 1; +#endif } void @@ -259,6 +304,10 @@ SettingsFloppyCDROM::onFloppyRowChanged(const QModelIndex ¤t) ui->comboBoxFloppyType->setCurrentIndex(type); ui->checkBoxTurboTimings->setChecked(current.siblingAtColumn(1).data() == tr("On")); ui->checkBoxCheckBPB->setChecked(current.siblingAtColumn(2).data() == tr("On")); + + int prof = current.siblingAtColumn(3).data(Qt::UserRole).toInt(); + int comboIndex = ui->comboBoxFloppyAudio->findData(prof); + ui->comboBoxFloppyAudio->setCurrentIndex(comboIndex); } void @@ -318,7 +367,7 @@ void SettingsFloppyCDROM::on_checkBoxTurboTimings_stateChanged(int arg1) { auto idx = ui->tableViewFloppy->selectionModel()->currentIndex(); - ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(1), arg1 == Qt::Checked ? + ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(1), arg1 == Qt::Checked ? tr("On") : tr("Off")); } @@ -326,10 +375,11 @@ void SettingsFloppyCDROM::on_checkBoxCheckBPB_stateChanged(int arg1) { auto idx = ui->tableViewFloppy->selectionModel()->currentIndex(); - ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(2), arg1 == Qt::Checked ? + ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(2), arg1 == Qt::Checked ? tr("On") : tr("Off")); } + void SettingsFloppyCDROM::on_comboBoxFloppyType_activated(int index) { @@ -337,6 +387,34 @@ SettingsFloppyCDROM::on_comboBoxFloppyType_activated(int index) ui->tableViewFloppy->selectionModel()->currentIndex(), index); } +void +SettingsFloppyCDROM::on_comboBoxFloppyAudio_activated(int) +{ + 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; + } + auto audioIdx = idx.siblingAtColumn(3); + ui->tableViewFloppy->model()->setData(audioIdx, profName); + ui->tableViewFloppy->model()->setData(audioIdx, prof, Qt::UserRole); +} + void SettingsFloppyCDROM::reloadBusChannels() { auto selected = ui->comboBoxChannel->currentIndex(); Harddrives::populateBusChannels(ui->comboBoxChannel->model(), ui->comboBoxBus->currentData().toInt(), Harddrives::busTrackClass); diff --git a/src/qt/qt_settingsfloppycdrom.hpp b/src/qt/qt_settingsfloppycdrom.hpp index ba7b396b7..9a53dd88f 100644 --- a/src/qt/qt_settingsfloppycdrom.hpp +++ b/src/qt/qt_settingsfloppycdrom.hpp @@ -26,6 +26,7 @@ private slots: void on_comboBoxFloppyType_activated(int index); void on_checkBoxTurboTimings_stateChanged(int arg1); void on_checkBoxCheckBPB_stateChanged(int arg1); + void on_comboBoxFloppyAudio_activated(int index); void onCDROMRowChanged(const QModelIndex ¤t); void on_comboBoxBus_activated(int index); diff --git a/src/qt/qt_settingsfloppycdrom.ui b/src/qt/qt_settingsfloppycdrom.ui index 6b1036b5c..ff54a5020 100644 --- a/src/qt/qt_settingsfloppycdrom.ui +++ b/src/qt/qt_settingsfloppycdrom.ui @@ -66,34 +66,69 @@ - + - - - Type: - - + + + + + Type: + + + + + + + 30 + + + + + + + Turbo timings + + + + + + + Check BPB + + + + - - - 30 - - - - - - - Turbo timings - - - - - - - Check BPB - - + + + + + Audio: + + + + + + + 10 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -205,6 +240,7 @@ comboBoxFloppyType checkBoxTurboTimings checkBoxCheckBPB + comboBoxFloppyAudio tableViewCDROM comboBoxBus comboBoxChannel diff --git a/src/sound/audio4.c b/src/sound/audio4.c index db43fa5d9..060e574e6 100644 --- a/src/sound/audio4.c +++ b/src/sound/audio4.c @@ -33,18 +33,20 @@ #endif #define I_NORMAL 0 -#define I_MUSIC 1 -#define I_WT 2 -#define I_CD 3 -#define I_MIDI 4 +#define I_MUSIC 1 +#define I_WT 2 +#define I_CD 3 +#define I_FDD 4 +#define I_MIDI 5 + +static int audio[6] = {-1, -1, -1, -1, -1, -1}; -static int audio[5] = { -1, -1, -1, -1, -1 }; #ifdef USE_NEW_API static struct audio_swpar info[5]; #else -static audio_info_t info[5]; +static audio_info_t info[6]; #endif -static int freqs[5] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, 0 }; +static int freqs[6] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0}; void closeal(void) @@ -165,12 +167,18 @@ givealbuffer_cd(const void *buf) givealbuffer_common(buf, I_CD, CD_BUFLEN << 1); } +void +givealbuffer_fdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_FDD, (int) size); +} + void givealbuffer_midi(const void *buf, const uint32_t size) { givealbuffer_common(buf, I_MIDI, (int) size); } - + void al_set_midi(const int freq, UNUSED(const int buf_size)) { diff --git a/src/sound/openal.c b/src/sound/openal.c index 9734470b0..d163150af 100644 --- a/src/sound/openal.c +++ b/src/sound/openal.c @@ -39,14 +39,16 @@ #define I_MUSIC 1 #define I_WT 2 #define I_CD 3 -#define I_MIDI 4 +#define I_FDD 4 +#define I_MIDI 5 ALuint buffers[4]; /* front and back buffers */ ALuint buffers_music[4]; /* front and back buffers */ ALuint buffers_wt[4]; /* front and back buffers */ ALuint buffers_cd[4]; /* front and back buffers */ +ALuint buffers_fdd[4]; /* front and back buffers */ ALuint buffers_midi[4]; /* front and back buffers */ -static ALuint source[5]; /* audio source */ +static ALuint source[6]; /* audio source - CHANGED FROM 5 TO 6 */ static int midi_freq = 44100; static int midi_buf_size = 4410; @@ -103,8 +105,9 @@ closeal(void) alSourceStopv(sources, source); alDeleteSources(sources, source); - if (sources == 4) + if (sources >= 6) alDeleteBuffers(4, buffers_midi); + alDeleteBuffers(4, buffers_fdd); alDeleteBuffers(4, buffers_cd); alDeleteBuffers(4, buffers_music); alDeleteBuffers(4, buffers); @@ -122,11 +125,13 @@ inital(void) float *wt_buf = NULL; float *cd_buf = NULL; float *midi_buf = NULL; + float *fdd_buf = NULL; int16_t *buf_int16 = NULL; int16_t *music_buf_int16 = NULL; int16_t *wt_buf_int16 = NULL; int16_t *cd_buf_int16 = NULL; int16_t *midi_buf_int16 = NULL; + int16_t *fdd_buf_int16 = NULL; int init_midi = 0; @@ -140,13 +145,14 @@ inital(void) if ((strcmp(mdn, "none") != 0) && (strcmp(mdn, SYSTEM_MIDI_INTERNAL_NAME) != 0)) init_midi = 1; /* If the device is neither none, nor system MIDI, initialize the MIDI buffer and source, otherwise, do not. */ - sources = 4 + !!init_midi; + sources = 5 + !!init_midi; if (sound_is_float) { buf = (float *) calloc((BUFLEN << 1), sizeof(float)); music_buf = (float *) calloc((MUSICBUFLEN << 1), sizeof(float)); wt_buf = (float *) calloc((WTBUFLEN << 1), sizeof(float)); cd_buf = (float *) calloc((CD_BUFLEN << 1), sizeof(float)); + fdd_buf = (float *) calloc((BUFLEN << 1), sizeof(float)); if (init_midi) midi_buf = (float *) calloc(midi_buf_size, sizeof(float)); } else { @@ -154,17 +160,22 @@ inital(void) music_buf_int16 = (int16_t *) calloc((MUSICBUFLEN << 1), sizeof(int16_t)); wt_buf_int16 = (int16_t *) calloc((WTBUFLEN << 1), sizeof(int16_t)); cd_buf_int16 = (int16_t *) calloc((CD_BUFLEN << 1), sizeof(int16_t)); + fdd_buf_int16 = (int16_t *) calloc((BUFLEN << 1), sizeof(int16_t)); if (init_midi) midi_buf_int16 = (int16_t *) calloc(midi_buf_size, sizeof(int16_t)); } alGenBuffers(4, buffers); alGenBuffers(4, buffers_cd); + alGenBuffers(4, buffers_fdd); alGenBuffers(4, buffers_music); alGenBuffers(4, buffers_wt); if (init_midi) alGenBuffers(4, buffers_midi); + // Create sources: 0=main, 1=music, 2=wt, 3=cd, 4=fdd, 5=midi(optional) + alGenSources(sources, source); + if (init_midi) alGenSources(5, source); else @@ -194,6 +205,12 @@ inital(void) alSourcef(source[I_CD], AL_ROLLOFF_FACTOR, 0.0f); alSourcei(source[I_CD], AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source[I_FDD], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(source[I_FDD], AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(source[I_FDD], AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSourcef(source[I_FDD], AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source[I_FDD], AL_SOURCE_RELATIVE, AL_TRUE); + if (init_midi) { alSource3f(source[I_MIDI], AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(source[I_MIDI], AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -207,6 +224,7 @@ inital(void) memset(cd_buf, 0, CD_BUFLEN * 2 * sizeof(float)); memset(music_buf, 0, MUSICBUFLEN * 2 * sizeof(float)); memset(wt_buf, 0, WTBUFLEN * 2 * sizeof(float)); + memset(fdd_buf, 0, BUFLEN * 2 * sizeof(float)); if (init_midi) memset(midi_buf, 0, midi_buf_size * sizeof(float)); } else { @@ -214,6 +232,7 @@ inital(void) memset(cd_buf_int16, 0, CD_BUFLEN * 2 * sizeof(int16_t)); memset(music_buf_int16, 0, MUSICBUFLEN * 2 * sizeof(int16_t)); memset(wt_buf_int16, 0, WTBUFLEN * 2 * sizeof(int16_t)); + memset(fdd_buf_int16, 0, BUFLEN * 2 * sizeof(int16_t)); if (init_midi) memset(midi_buf_int16, 0, midi_buf_size * sizeof(int16_t)); } @@ -224,6 +243,7 @@ inital(void) alBufferData(buffers_music[c], AL_FORMAT_STEREO_FLOAT32, music_buf, MUSICBUFLEN * 2 * sizeof(float), MUSIC_FREQ); alBufferData(buffers_wt[c], AL_FORMAT_STEREO_FLOAT32, wt_buf, WTBUFLEN * 2 * sizeof(float), WT_FREQ); alBufferData(buffers_cd[c], AL_FORMAT_STEREO_FLOAT32, cd_buf, CD_BUFLEN * 2 * sizeof(float), CD_FREQ); + alBufferData(buffers_fdd[c], AL_FORMAT_STEREO_FLOAT32, fdd_buf, BUFLEN * 2 * sizeof(float), FREQ); if (init_midi) alBufferData(buffers_midi[c], AL_FORMAT_STEREO_FLOAT32, midi_buf, midi_buf_size * (int) sizeof(float), midi_freq); } else { @@ -231,6 +251,7 @@ inital(void) alBufferData(buffers_music[c], AL_FORMAT_STEREO16, music_buf_int16, MUSICBUFLEN * 2 * sizeof(int16_t), MUSIC_FREQ); alBufferData(buffers_wt[c], AL_FORMAT_STEREO16, wt_buf_int16, WTBUFLEN * 2 * sizeof(int16_t), WT_FREQ); alBufferData(buffers_cd[c], AL_FORMAT_STEREO16, cd_buf_int16, CD_BUFLEN * 2 * sizeof(int16_t), CD_FREQ); + alBufferData(buffers_fdd[c], AL_FORMAT_STEREO16, fdd_buf_int16, BUFLEN * 2 * sizeof(int16_t), FREQ); if (init_midi) alBufferData(buffers_midi[c], AL_FORMAT_STEREO16, midi_buf_int16, midi_buf_size * (int) sizeof(int16_t), midi_freq); } @@ -240,12 +261,14 @@ inital(void) alSourceQueueBuffers(source[I_MUSIC], 4, buffers_music); alSourceQueueBuffers(source[I_WT], 4, buffers_wt); alSourceQueueBuffers(source[I_CD], 4, buffers_cd); + alSourceQueueBuffers(source[I_FDD], 4, buffers_fdd); if (init_midi) alSourceQueueBuffers(source[I_MIDI], 4, buffers_midi); alSourcePlay(source[I_NORMAL]); alSourcePlay(source[I_MUSIC]); alSourcePlay(source[I_WT]); alSourcePlay(source[I_CD]); + alSourcePlay(source[I_FDD]); if (init_midi) alSourcePlay(source[I_MIDI]); @@ -256,6 +279,7 @@ inital(void) free(wt_buf); free(music_buf); free(buf); + free(fdd_buf); } else { if (init_midi) free(midi_buf_int16); @@ -263,6 +287,7 @@ inital(void) free(wt_buf_int16); free(music_buf_int16); free(buf_int16); + free(fdd_buf_int16); } initialized = 1; @@ -327,5 +352,11 @@ givealbuffer_cd(const void *buf) void givealbuffer_midi(const void *buf, const uint32_t size) { - givealbuffer_common(buf, 4, (int) size, midi_freq); + givealbuffer_common(buf, 5, (int) size, midi_freq); } + +void +givealbuffer_fdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, 4, (int) size, FREQ); +} \ No newline at end of file diff --git a/src/sound/sndio.c b/src/sound/sndio.c index b5a54e420..6363163a2 100644 --- a/src/sound/sndio.c +++ b/src/sound/sndio.c @@ -18,7 +18,6 @@ #include #include #include - #include #include <86box/86box.h> @@ -30,10 +29,11 @@ #define I_WT 2 #define I_CD 3 #define I_MIDI 4 +#define I_FDD 5 -static struct sio_hdl* audio[5] = { NULL, NULL, NULL, NULL, NULL }; -static struct sio_par info[5]; -static int freqs[5] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, 0 }; +static struct sio_hdl* audio[6] = {NULL, NULL, NULL, NULL, NULL, NULL}; +static struct sio_par info[6]; +static int freqs[6] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0 }; void closeal(void) @@ -147,7 +147,13 @@ givealbuffer_midi(const void *buf, const uint32_t size) { givealbuffer_common(buf, I_MIDI, (int) size); } - + +void +givealbuffer_fdd(const void *buf, const uint32_t size) +{ + givealbuffer_common(buf, I_FDD, (int) size); +} + void al_set_midi(const int freq, UNUSED(const int buf_size)) { diff --git a/src/sound/sound.c b/src/sound/sound.c index 1e406cc8e..7503c55da 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -36,6 +36,7 @@ #include <86box/timer.h> #include <86box/snd_mpu401.h> #include <86box/sound.h> +#include <86box/fdd_audio.h> typedef struct { const device_t *device; @@ -89,6 +90,13 @@ static int cd_buf_update = CD_BUFLEN / SOUNDBUFLEN; static volatile int cdaudioon = 0; static int cd_thread_enable = 0; +static thread_t *sound_fdd_thread_h; +static event_t *sound_fdd_event; +static event_t *sound_fdd_start_event; +static int16_t fdd_out_buffer[SOUNDBUFLEN * 2]; +static volatile int fddaudioon = 0; +static int fdd_thread_enable = 0; + static void (*filter_cd_audio)(int channel, double *buffer, void *priv) = NULL; static void *filter_cd_audio_p = NULL; @@ -597,6 +605,9 @@ sound_poll(UNUSED(void *priv)) } } + if (fdd_thread_enable) { + thread_set_event(sound_fdd_event); + } sound_pos_global = 0; } } @@ -698,17 +709,14 @@ sound_reset(void) inital(); timer_add(&sound_poll_timer, sound_poll, NULL, 1); - sound_handlers_num = 0; memset(sound_handlers, 0x00, 8 * sizeof(sound_handler_t)); timer_add(&music_poll_timer, music_poll, NULL, 1); - music_handlers_num = 0; memset(music_handlers, 0x00, 8 * sizeof(sound_handler_t)); timer_add(&wavetable_poll_timer, wavetable_poll, NULL, 1); - wavetable_handlers_num = 0; memset(wavetable_handlers, 0x00, 8 * sizeof(sound_handler_t)); @@ -785,3 +793,61 @@ sound_cd_thread_reset(void) cd_thread_enable = available_cdrom_drives ? 1 : 0; } + +static void +sound_fdd_thread(UNUSED(void *param)) +{ + thread_set_event(sound_fdd_start_event); + while (fddaudioon) { + thread_wait_event(sound_fdd_event, -1); + thread_reset_event(sound_fdd_event); + + if (!fddaudioon) + break; + + static float fdd_float_buffer[SOUNDBUFLEN * 2]; + memset(fdd_float_buffer, 0, sizeof(fdd_float_buffer)); + fdd_audio_callback((int16_t*)fdd_float_buffer, SOUNDBUFLEN * 2); + givealbuffer_fdd(fdd_float_buffer, SOUNDBUFLEN * 2); + } +} + +void +sound_fdd_thread_init(void) +{ + if (!fddaudioon) { + fddaudioon = 1; + fdd_thread_enable = 1; + + sound_fdd_start_event = thread_create_event(); + sound_fdd_event = thread_create_event(); + sound_fdd_thread_h = thread_create(sound_fdd_thread, NULL); + + thread_wait_event(sound_fdd_start_event, -1); + thread_reset_event(sound_fdd_start_event); + } +} + +void +sound_fdd_thread_end(void) +{ + if (fddaudioon) { + fddaudioon = 0; + fdd_thread_enable = 0; + + thread_set_event(sound_fdd_event); + thread_wait(sound_fdd_thread_h); + + if (sound_fdd_event) { + thread_destroy_event(sound_fdd_event); + sound_fdd_event = NULL; + } + + sound_fdd_thread_h = NULL; + + if (sound_fdd_start_event) { + thread_destroy_event(sound_fdd_start_event); + sound_fdd_start_event = NULL; + } + } +} \ No newline at end of file diff --git a/src/sound/xaudio2.c b/src/sound/xaudio2.c index 148175f33..8596c2a49 100644 --- a/src/sound/xaudio2.c +++ b/src/sound/xaudio2.c @@ -53,6 +53,7 @@ static IXAudio2SourceVoice *srcvoicemusic = NULL; static IXAudio2SourceVoice *srcvoicewt = NULL; static IXAudio2SourceVoice *srcvoicemidi = NULL; static IXAudio2SourceVoice *srcvoicecd = NULL; +static IXAudio2SourceVoice *srcvoicefdd = NULL; #define FREQ SOUND_FREQ #define BUFLEN SOUNDBUFLEN @@ -178,11 +179,18 @@ inital(void) (void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicecd, &fmt, 0, 2.0f, &callbacks, NULL, NULL); + fmt.nSamplesPerSec = FREQ; + fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; + fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; + + (void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicefdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL); + (void) IXAudio2SourceVoice_SetVolume(srcvoice, 1, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoice, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicecd, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicemusic, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_Start(srcvoicewt, 0, XAUDIO2_COMMIT_NOW); + (void) IXAudio2SourceVoice_Start(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW); const char *mdn = midi_out_device_get_internal_name(midi_output_device_current); @@ -213,6 +221,8 @@ closeal(void) (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicewt); (void) IXAudio2SourceVoice_Stop(srcvoicecd, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicecd); + (void) IXAudio2SourceVoice_Stop(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW); + (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicefdd); if (srcvoicemidi) { (void) IXAudio2SourceVoice_Stop(srcvoicemidi, 0, XAUDIO2_COMMIT_NOW); (void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicemidi); @@ -220,6 +230,7 @@ closeal(void) } IXAudio2SourceVoice_DestroyVoice(srcvoicewt); IXAudio2SourceVoice_DestroyVoice(srcvoicecd); + IXAudio2SourceVoice_DestroyVoice(srcvoicefdd); IXAudio2SourceVoice_DestroyVoice(srcvoicemusic); IXAudio2SourceVoice_DestroyVoice(srcvoice); IXAudio2MasteringVoice_DestroyVoice(mastervoice); @@ -227,6 +238,7 @@ closeal(void) srcvoice = NULL; srcvoicecd = NULL; srcvoicemidi = NULL; + srcvoicefdd = NULL; mastervoice = NULL; xaudio2 = NULL; @@ -288,6 +300,18 @@ givealbuffer_cd(const void *buf) givealbuffer_common(buf, srcvoicecd, CD_BUFLEN << 1); } +void +givealbuffer_fdd(const void *buf, const uint32_t size) +{ + if (!initialized) + return; + + if (!srcvoicefdd) + return; + + givealbuffer_common(buf, srcvoicefdd, size); +} + void al_set_midi(const int freq, const int buf_size) { @@ -321,4 +345,4 @@ void givealbuffer_midi(const void *buf, const uint32_t size) { givealbuffer_common(buf, srcvoicemidi, size); -} +} \ No newline at end of file