mirror of
https://github.com/86Box/86Box.git
synced 2026-02-22 01:25:33 -07:00
HDD audio profile for settings, ui and using the selected profile
This commit is contained in:
@@ -83,6 +83,7 @@
|
||||
#include <86box/fdc.h>
|
||||
#include <86box/fdc_ext.h>
|
||||
#include <86box/hdd.h>
|
||||
#include <86box/hdd_audio.h>
|
||||
#include <86box/hdc.h>
|
||||
#include <86box/hdc_ide.h>
|
||||
#include <86box/scsi.h>
|
||||
@@ -1476,6 +1477,8 @@ pc_init_modules(void)
|
||||
fdd_audio_load_profiles();
|
||||
fdd_audio_init();
|
||||
}
|
||||
|
||||
hdd_audio_init();
|
||||
|
||||
sound_init();
|
||||
|
||||
@@ -1719,6 +1722,9 @@ pc_reset_hard_init(void)
|
||||
|
||||
fdd_reset();
|
||||
|
||||
/* Reset HDD audio to pick up any profile changes */
|
||||
hdd_audio_reset();
|
||||
|
||||
/* Reset and reconfigure the SCSI layer. */
|
||||
scsi_card_init();
|
||||
|
||||
|
||||
19
src/config.c
19
src/config.c
@@ -51,6 +51,7 @@
|
||||
#include <86box/lpt.h>
|
||||
#include <86box/serial.h>
|
||||
#include <86box/hdd.h>
|
||||
#include <86box/hdd_audio.h>
|
||||
#include <86box/hdc.h>
|
||||
#include <86box/hdc_ide.h>
|
||||
#include <86box/fdd.h>
|
||||
@@ -1247,6 +1248,8 @@ load_hard_disks(void)
|
||||
uint32_t board = 0;
|
||||
uint32_t dev = 0;
|
||||
|
||||
hdd_audio_load_profiles();
|
||||
|
||||
memset(temp, '\0', sizeof(temp));
|
||||
for (uint8_t c = 0; c < HDD_NUM; c++) {
|
||||
sprintf(temp, "hdd_%02i_parameters", c + 1);
|
||||
@@ -1315,6 +1318,11 @@ load_hard_disks(void)
|
||||
p = ini_section_get_string(cat, temp, tmp2);
|
||||
hdd[c].speed_preset = hdd_preset_get_from_internal_name(p);
|
||||
|
||||
/* Audio Profile */
|
||||
sprintf(temp, "hdd_%02i_audio", c + 1);
|
||||
p = ini_section_get_string(cat, temp, "none");
|
||||
hdd[c].audio_profile = hdd_audio_get_profile_by_internal_name(p);
|
||||
|
||||
/* MFM/RLL */
|
||||
sprintf(temp, "hdd_%02i_mfm_channel", c + 1);
|
||||
if (hdd[c].bus_type == HDD_BUS_MFM)
|
||||
@@ -3458,6 +3466,17 @@ save_hard_disks(void)
|
||||
ini_section_delete_var(cat, temp);
|
||||
else
|
||||
ini_section_set_string(cat, temp, hdd_preset_get_internal_name(hdd[c].speed_preset));
|
||||
|
||||
sprintf(temp, "hdd_%02i_audio", c + 1);
|
||||
if (!hdd_is_valid(c) || hdd[c].audio_profile == 0) {
|
||||
ini_section_delete_var(cat, temp);
|
||||
} else {
|
||||
const char *internal_name = hdd_audio_get_profile_internal_name(hdd[c].audio_profile);
|
||||
if (internal_name && strcmp(internal_name, "none") != 0)
|
||||
ini_section_set_string(cat, temp, internal_name);
|
||||
else
|
||||
ini_section_delete_var(cat, temp);
|
||||
}
|
||||
}
|
||||
|
||||
ini_delete_section_if_empty(config, cat);
|
||||
|
||||
@@ -39,7 +39,6 @@ hdd_init(void)
|
||||
{
|
||||
/* Clear all global data. */
|
||||
memset(hdd, 0x00, sizeof(hdd));
|
||||
hdd_audio_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
#include <stdio.h>
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Hard disk audio emulation.
|
||||
*
|
||||
* Authors: Toni Riikonen, <riikonen.toni@gmail.com>
|
||||
*
|
||||
* Copyright 2026 Toni Riikonen.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <86box/86box.h>
|
||||
#include <86box/hdd.h>
|
||||
#include <86box/hdd_audio.h>
|
||||
#include <86box/sound.h>
|
||||
#include <86box/sound_util.h>
|
||||
#include <86box/thread.h>
|
||||
#include <86box/plat.h>
|
||||
#include <86box/path.h>
|
||||
#include <86box/ini.h>
|
||||
#include <86box/mem.h>
|
||||
#include <86box/rom.h>
|
||||
|
||||
/* Maximum number of simultaneous seek sounds */
|
||||
#define HDD_MAX_SEEK_VOICES 8
|
||||
@@ -16,36 +38,277 @@ typedef struct {
|
||||
float volume;
|
||||
} hdd_seek_voice_t;
|
||||
|
||||
static int16_t *hdd_spindle_sound_buffer = NULL;
|
||||
static int16_t *hdd_seek_sound_buffer = NULL;
|
||||
static int hdd_samples;
|
||||
static int hdd_seek_samples;
|
||||
/* Audio samples structure for a profile */
|
||||
typedef struct {
|
||||
int16_t *spindle_start_buffer;
|
||||
int spindle_start_samples;
|
||||
int16_t *spindle_loop_buffer;
|
||||
int spindle_loop_samples;
|
||||
int16_t *spindle_stop_buffer;
|
||||
int spindle_stop_samples;
|
||||
int16_t *seek_buffer;
|
||||
int seek_samples;
|
||||
float spindle_volume;
|
||||
float seek_volume;
|
||||
int loaded;
|
||||
} hdd_audio_samples_t;
|
||||
|
||||
/* Global audio profile configurations */
|
||||
static hdd_audio_profile_config_t audio_profiles[HDD_AUDIO_PROFILE_MAX];
|
||||
static int audio_profile_count = 0;
|
||||
|
||||
/* Per-profile loaded samples */
|
||||
static hdd_audio_samples_t profile_samples[HDD_AUDIO_PROFILE_MAX];
|
||||
|
||||
/* Active profile for audio playback (first HDD with valid profile) */
|
||||
static int active_audio_profile = 0;
|
||||
|
||||
static hdd_seek_voice_t hdd_seek_voices[HDD_MAX_SEEK_VOICES];
|
||||
static mutex_t *hdd_audio_mutex = NULL;
|
||||
|
||||
/* Load audio profiles from configuration file */
|
||||
void
|
||||
hdd_audio_load_profiles(void)
|
||||
{
|
||||
ini_t profiles_ini;
|
||||
char cfg_fn[1024] = { 0 };
|
||||
|
||||
int ret = asset_getfile("assets/sounds/hdd/hdd_audio_profiles.cfg", cfg_fn, 1024);
|
||||
if (!ret) {
|
||||
pclog("HDD Audio: Could not find hdd_audio_profiles.cfg\n");
|
||||
return;
|
||||
}
|
||||
|
||||
profiles_ini = ini_read_ex(cfg_fn, 1);
|
||||
if (profiles_ini == NULL) {
|
||||
pclog("HDD Audio: Failed to load hdd_audio_profiles.cfg\n");
|
||||
return;
|
||||
}
|
||||
|
||||
audio_profile_count = 0;
|
||||
|
||||
/* Load profiles by trying known profile section names */
|
||||
for (int i = 0; i < HDD_AUDIO_PROFILE_MAX && audio_profile_count < HDD_AUDIO_PROFILE_MAX; i++) {
|
||||
char section_name[64];
|
||||
sprintf(section_name, "Profile \"%d\"", i);
|
||||
|
||||
ini_section_t cat = ini_find_section(profiles_ini, section_name);
|
||||
if (cat == NULL)
|
||||
continue;
|
||||
|
||||
hdd_audio_profile_config_t *config = &audio_profiles[audio_profile_count];
|
||||
memset(config, 0, sizeof(hdd_audio_profile_config_t));
|
||||
|
||||
config->id = ini_section_get_int(cat, "id", i);
|
||||
|
||||
const char *name = ini_section_get_string(cat, "name", "Unknown");
|
||||
strncpy(config->name, name, sizeof(config->name) - 1);
|
||||
|
||||
const char *internal_name = ini_section_get_string(cat, "internal_name", "unknown");
|
||||
strncpy(config->internal_name, internal_name, sizeof(config->internal_name) - 1);
|
||||
|
||||
/* Load spindle motor sample files */
|
||||
const char *file = ini_section_get_string(cat, "spindlemotor_start_file", "");
|
||||
strncpy(config->spindlemotor_start.filename, file, sizeof(config->spindlemotor_start.filename) - 1);
|
||||
config->spindlemotor_start.volume = (float) ini_section_get_double(cat, "spindlemotor_start_volume", 1.0);
|
||||
|
||||
file = ini_section_get_string(cat, "spindlemotor_loop_file", "");
|
||||
strncpy(config->spindlemotor_loop.filename, file, sizeof(config->spindlemotor_loop.filename) - 1);
|
||||
config->spindlemotor_loop.volume = (float) ini_section_get_double(cat, "spindlemotor_loop_volume", 1.0);
|
||||
|
||||
file = ini_section_get_string(cat, "spindlemotor_stop_file", "");
|
||||
strncpy(config->spindlemotor_stop.filename, file, sizeof(config->spindlemotor_stop.filename) - 1);
|
||||
config->spindlemotor_stop.volume = (float) ini_section_get_double(cat, "spindlemotor_stop_volume", 1.0);
|
||||
|
||||
/* Load seek sample file */
|
||||
file = ini_section_get_string(cat, "seek_track_file", "");
|
||||
strncpy(config->seek_track.filename, file, sizeof(config->seek_track.filename) - 1);
|
||||
config->seek_track.volume = (float) ini_section_get_double(cat, "seek_track_volume", 1.0);
|
||||
|
||||
pclog("HDD Audio: Loaded profile %d: %s (%s)\n",
|
||||
audio_profile_count, config->name, config->internal_name);
|
||||
|
||||
audio_profile_count++;
|
||||
}
|
||||
|
||||
ini_close(profiles_ini);
|
||||
|
||||
pclog("HDD Audio: Loaded %d audio profiles\n", audio_profile_count);
|
||||
}
|
||||
|
||||
/* Public API functions */
|
||||
int
|
||||
hdd_audio_get_profile_count(void)
|
||||
{
|
||||
return audio_profile_count;
|
||||
}
|
||||
|
||||
const hdd_audio_profile_config_t *
|
||||
hdd_audio_get_profile(int id)
|
||||
{
|
||||
if (id < 0 || id >= audio_profile_count)
|
||||
return NULL;
|
||||
return &audio_profiles[id];
|
||||
}
|
||||
|
||||
const char *
|
||||
hdd_audio_get_profile_name(int id)
|
||||
{
|
||||
if (id < 0 || id >= audio_profile_count)
|
||||
return NULL;
|
||||
return audio_profiles[id].name;
|
||||
}
|
||||
|
||||
const char *
|
||||
hdd_audio_get_profile_internal_name(int id)
|
||||
{
|
||||
if (id < 0 || id >= audio_profile_count)
|
||||
return NULL;
|
||||
return audio_profiles[id].internal_name;
|
||||
}
|
||||
|
||||
int
|
||||
hdd_audio_get_profile_by_internal_name(const char *internal_name)
|
||||
{
|
||||
if (!internal_name)
|
||||
return 0;
|
||||
|
||||
for (int i = 0; i < audio_profile_count; i++) {
|
||||
if (strcmp(audio_profiles[i].internal_name, internal_name) == 0)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_close(void)
|
||||
{
|
||||
/* Free all loaded profile samples */
|
||||
for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) {
|
||||
if (profile_samples[i].spindle_start_buffer) {
|
||||
free(profile_samples[i].spindle_start_buffer);
|
||||
profile_samples[i].spindle_start_buffer = NULL;
|
||||
}
|
||||
if (profile_samples[i].spindle_loop_buffer) {
|
||||
free(profile_samples[i].spindle_loop_buffer);
|
||||
profile_samples[i].spindle_loop_buffer = NULL;
|
||||
}
|
||||
if (profile_samples[i].spindle_stop_buffer) {
|
||||
free(profile_samples[i].spindle_stop_buffer);
|
||||
profile_samples[i].spindle_stop_buffer = NULL;
|
||||
}
|
||||
if (profile_samples[i].seek_buffer) {
|
||||
free(profile_samples[i].seek_buffer);
|
||||
profile_samples[i].seek_buffer = NULL;
|
||||
}
|
||||
profile_samples[i].loaded = 0;
|
||||
}
|
||||
|
||||
if (hdd_audio_mutex) {
|
||||
thread_close_mutex(hdd_audio_mutex);
|
||||
hdd_audio_mutex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Load samples for a specific profile */
|
||||
static void
|
||||
hdd_audio_load_profile_samples(int profile_id)
|
||||
{
|
||||
if (profile_id < 0 || profile_id >= audio_profile_count)
|
||||
return;
|
||||
|
||||
hdd_audio_profile_config_t *config = &audio_profiles[profile_id];
|
||||
hdd_audio_samples_t *samples = &profile_samples[profile_id];
|
||||
|
||||
/* Already loaded? */
|
||||
if (samples->loaded)
|
||||
return;
|
||||
|
||||
/* Profile 0 is "None" - no audio */
|
||||
if (profile_id == 0 || strcmp(config->internal_name, "none") == 0) {
|
||||
samples->loaded = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
pclog("HDD Audio: Loading samples for profile %d (%s)\n", profile_id, config->name);
|
||||
|
||||
/* Load spindle loop (main running sound) */
|
||||
if (config->spindlemotor_loop.filename[0]) {
|
||||
samples->spindle_loop_buffer = sound_load_wav(
|
||||
config->spindlemotor_loop.filename,
|
||||
&samples->spindle_loop_samples);
|
||||
if (samples->spindle_loop_buffer) {
|
||||
samples->spindle_volume = config->spindlemotor_loop.volume;
|
||||
pclog("HDD Audio: Loaded spindle loop, %d frames\n", samples->spindle_loop_samples);
|
||||
} else {
|
||||
pclog("HDD Audio: Failed to load spindle loop: %s\n", config->spindlemotor_loop.filename);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load spindle start */
|
||||
if (config->spindlemotor_start.filename[0]) {
|
||||
samples->spindle_start_buffer = sound_load_wav(
|
||||
config->spindlemotor_start.filename,
|
||||
&samples->spindle_start_samples);
|
||||
if (samples->spindle_start_buffer) {
|
||||
pclog("HDD Audio: Loaded spindle start, %d frames\n", samples->spindle_start_samples);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load spindle stop */
|
||||
if (config->spindlemotor_stop.filename[0]) {
|
||||
samples->spindle_stop_buffer = sound_load_wav(
|
||||
config->spindlemotor_stop.filename,
|
||||
&samples->spindle_stop_samples);
|
||||
if (samples->spindle_stop_buffer) {
|
||||
pclog("HDD Audio: Loaded spindle stop, %d frames\n", samples->spindle_stop_samples);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load seek sound */
|
||||
if (config->seek_track.filename[0]) {
|
||||
samples->seek_buffer = sound_load_wav(
|
||||
config->seek_track.filename,
|
||||
&samples->seek_samples);
|
||||
if (samples->seek_buffer) {
|
||||
samples->seek_volume = config->seek_track.volume;
|
||||
pclog("HDD Audio: Loaded seek sound, %d frames (%.1f ms)\n",
|
||||
samples->seek_samples, (float)samples->seek_samples / 48.0f);
|
||||
} else {
|
||||
pclog("HDD Audio: Failed to load seek sound: %s\n", config->seek_track.filename);
|
||||
}
|
||||
}
|
||||
|
||||
samples->loaded = 1;
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_init(void)
|
||||
{
|
||||
hdd_spindle_sound_buffer = sound_load_wav(
|
||||
"assets/sounds/hdd/1993 IBM H3171/1993_IBM_H3171_3.5_SPINDLE_RUNNING.wav",
|
||||
&hdd_samples);
|
||||
|
||||
if (hdd_spindle_sound_buffer) {
|
||||
pclog("HDD Audio: Loaded spindle sound, %d frames\n", hdd_samples);
|
||||
} else {
|
||||
pclog("HDD Audio: Failed to load spindle sound!\n");
|
||||
/* Initialize profile samples */
|
||||
memset(profile_samples, 0, sizeof(profile_samples));
|
||||
|
||||
pclog("HDD Audio Init: audio_profile_count=%d\n", audio_profile_count);
|
||||
|
||||
/* Find first HDD with a valid audio profile and load its samples */
|
||||
active_audio_profile = 0;
|
||||
for (int i = 0; i < HDD_NUM; i++) {
|
||||
if (hdd[i].bus_type != HDD_BUS_DISABLED) {
|
||||
pclog("HDD Audio Init: HDD %d bus_type=%d audio_profile=%d\n",
|
||||
i, hdd[i].bus_type, hdd[i].audio_profile);
|
||||
if (hdd[i].audio_profile > 0) {
|
||||
active_audio_profile = hdd[i].audio_profile;
|
||||
pclog("HDD Audio: Using profile %d from HDD %d\n", active_audio_profile, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hdd_seek_sound_buffer = sound_load_wav(
|
||||
"assets/sounds/hdd/1993 IBM H3171/1993_IBM_H3171_3.5_SEEK_1TRACK.wav",
|
||||
&hdd_seek_samples);
|
||||
|
||||
if (hdd_seek_sound_buffer) {
|
||||
pclog("HDD Audio: Loaded seek sound, %d frames (%.1f ms)\n",
|
||||
hdd_seek_samples, (float)hdd_seek_samples / 48.0f);
|
||||
} else {
|
||||
pclog("HDD Audio: Failed to load seek sound!\n");
|
||||
|
||||
pclog("HDD Audio Init: active_audio_profile=%d\n", active_audio_profile);
|
||||
|
||||
/* Load samples for the active profile */
|
||||
if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) {
|
||||
hdd_audio_load_profile_samples(active_audio_profile);
|
||||
}
|
||||
|
||||
for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) {
|
||||
@@ -60,28 +323,87 @@ hdd_audio_init(void)
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder)
|
||||
hdd_audio_reset(void)
|
||||
{
|
||||
uint32_t cylinder_diff = abs((int) hdd->cur_cylinder - (int) new_cylinder);
|
||||
pclog("HDD Audio: Reset\n");
|
||||
|
||||
/* Free previously loaded samples (but keep profiles) */
|
||||
for (int i = 0; i < HDD_AUDIO_PROFILE_MAX; i++) {
|
||||
if (profile_samples[i].spindle_start_buffer) {
|
||||
free(profile_samples[i].spindle_start_buffer);
|
||||
profile_samples[i].spindle_start_buffer = NULL;
|
||||
}
|
||||
if (profile_samples[i].spindle_loop_buffer) {
|
||||
free(profile_samples[i].spindle_loop_buffer);
|
||||
profile_samples[i].spindle_loop_buffer = NULL;
|
||||
}
|
||||
if (profile_samples[i].spindle_stop_buffer) {
|
||||
free(profile_samples[i].spindle_stop_buffer);
|
||||
profile_samples[i].spindle_stop_buffer = NULL;
|
||||
}
|
||||
if (profile_samples[i].seek_buffer) {
|
||||
free(profile_samples[i].seek_buffer);
|
||||
profile_samples[i].seek_buffer = NULL;
|
||||
}
|
||||
profile_samples[i].loaded = 0;
|
||||
}
|
||||
|
||||
/* Reset seek voices */
|
||||
for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) {
|
||||
hdd_seek_voices[i].active = 0;
|
||||
hdd_seek_voices[i].position = 0;
|
||||
hdd_seek_voices[i].volume = 1.0f;
|
||||
}
|
||||
|
||||
/* Find new active profile from current HDD configuration */
|
||||
active_audio_profile = 0;
|
||||
for (int i = 0; i < HDD_NUM; i++) {
|
||||
if (hdd[i].bus_type != HDD_BUS_DISABLED) {
|
||||
pclog("HDD Audio Reset: HDD %d audio_profile=%d\n", i, hdd[i].audio_profile);
|
||||
if (hdd[i].audio_profile > 0) {
|
||||
active_audio_profile = hdd[i].audio_profile;
|
||||
pclog("HDD Audio: Reset with profile %d from HDD %d\n", active_audio_profile, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Load samples for the active profile */
|
||||
if (active_audio_profile > 0 && active_audio_profile < audio_profile_count) {
|
||||
hdd_audio_load_profile_samples(active_audio_profile);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_seek(hard_disk_t *hdd_drive, uint32_t new_cylinder)
|
||||
{
|
||||
uint32_t cylinder_diff = abs((int) hdd_drive->cur_cylinder - (int) new_cylinder);
|
||||
|
||||
if (cylinder_diff == 0)
|
||||
return;
|
||||
|
||||
pclog("HDD Audio Seek: cur_cyl=%u -> new_cyl=%u, diff=%u, speed_preset=%d, cyl_switch_usec=%.1f\n",
|
||||
hdd->cur_cylinder, new_cylinder, cylinder_diff, hdd->speed_preset,
|
||||
hdd->cyl_switch_usec);
|
||||
|
||||
if (hdd->speed_preset == 0)
|
||||
/* Use the drive's audio profile, fallback to active profile */
|
||||
int profile_id = hdd_drive->audio_profile;
|
||||
if (profile_id == 0)
|
||||
profile_id = active_audio_profile;
|
||||
|
||||
/* No audio profile selected */
|
||||
if (profile_id == 0 || profile_id >= audio_profile_count)
|
||||
return;
|
||||
|
||||
if (!hdd_seek_sound_buffer || hdd_seek_samples == 0) {
|
||||
pclog("HDD Audio Seek: No seek sound buffer loaded!\n");
|
||||
|
||||
/* Load samples if not already loaded */
|
||||
if (!profile_samples[profile_id].loaded)
|
||||
hdd_audio_load_profile_samples(profile_id);
|
||||
|
||||
hdd_audio_samples_t *samples = &profile_samples[profile_id];
|
||||
|
||||
if (!samples->seek_buffer || samples->seek_samples == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int min_seek_spacing = 0;
|
||||
if (hdd->cyl_switch_usec > 0)
|
||||
min_seek_spacing = (int)(hdd->cyl_switch_usec * 48000.0 / 1000000.0);
|
||||
if (hdd_drive->cyl_switch_usec > 0)
|
||||
min_seek_spacing = (int)(hdd_drive->cyl_switch_usec * 48000.0 / 1000000.0);
|
||||
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
|
||||
@@ -95,22 +417,16 @@ hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder)
|
||||
}
|
||||
}
|
||||
|
||||
int active_count = 0;
|
||||
|
||||
for (int i = 0; i < HDD_MAX_SEEK_VOICES; i++) {
|
||||
if (!hdd_seek_voices[i].active) {
|
||||
hdd_seek_voices[i].active = 1;
|
||||
hdd_seek_voices[i].position = 0;
|
||||
hdd_seek_voices[i].volume = 0.5f;
|
||||
pclog("HDD Audio Seek: Using free voice %d\n", i);
|
||||
hdd_seek_voices[i].volume = samples->seek_volume;
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
return;
|
||||
}
|
||||
active_count++;
|
||||
}
|
||||
|
||||
pclog("HDD Audio Seek: All %d voices active, skipping seek\n", active_count);
|
||||
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
|
||||
@@ -118,23 +434,28 @@ void
|
||||
hdd_audio_callback(int16_t *buffer, int length)
|
||||
{
|
||||
static int spindle_pos = 0;
|
||||
static int debug_counter = 0;
|
||||
const float spindle_volume = 0.2f;
|
||||
const float seek_volume = 0.5f;
|
||||
int frames_in_buffer = length / 2;
|
||||
|
||||
/* Get active profile samples */
|
||||
hdd_audio_samples_t *samples = NULL;
|
||||
if (active_audio_profile > 0 && active_audio_profile < HDD_AUDIO_PROFILE_MAX) {
|
||||
samples = &profile_samples[active_audio_profile];
|
||||
}
|
||||
|
||||
if (sound_is_float) {
|
||||
float *float_buffer = (float *) buffer;
|
||||
|
||||
if (hdd_spindle_sound_buffer && hdd_samples > 0) {
|
||||
/* Spindle sound from profile */
|
||||
if (samples && samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) {
|
||||
float spindle_volume = samples->spindle_volume;
|
||||
for (int i = 0; i < frames_in_buffer; i++) {
|
||||
float left_sample = (float) hdd_spindle_sound_buffer[spindle_pos * 2] / 131072.0f * spindle_volume;
|
||||
float right_sample = (float) hdd_spindle_sound_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume;
|
||||
float left_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2] / 131072.0f * spindle_volume;
|
||||
float right_sample = (float) samples->spindle_loop_buffer[spindle_pos * 2 + 1] / 131072.0f * spindle_volume;
|
||||
float_buffer[i * 2] = left_sample;
|
||||
float_buffer[i * 2 + 1] = right_sample;
|
||||
|
||||
spindle_pos++;
|
||||
if (spindle_pos >= hdd_samples) {
|
||||
if (spindle_pos >= samples->spindle_loop_samples) {
|
||||
spindle_pos = 0;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +465,8 @@ hdd_audio_callback(int16_t *buffer, int length)
|
||||
}
|
||||
}
|
||||
|
||||
if (hdd_seek_sound_buffer && hdd_seek_samples > 0 && hdd_audio_mutex) {
|
||||
/* Seek sounds from profile */
|
||||
if (samples && samples->seek_buffer && samples->seek_samples > 0 && hdd_audio_mutex) {
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) {
|
||||
@@ -155,15 +477,15 @@ hdd_audio_callback(int16_t *buffer, int length)
|
||||
int pos = hdd_seek_voices[v].position;
|
||||
if (pos < 0) pos = 0;
|
||||
|
||||
for (int i = 0; i < frames_in_buffer && pos < hdd_seek_samples; i++, pos++) {
|
||||
float seek_left = (float) hdd_seek_sound_buffer[pos * 2] / 131072.0f * seek_volume * voice_vol;
|
||||
float seek_right = (float) hdd_seek_sound_buffer[pos * 2 + 1] / 131072.0f * seek_volume * voice_vol;
|
||||
for (int i = 0; i < frames_in_buffer && pos < samples->seek_samples; i++, pos++) {
|
||||
float seek_left = (float) samples->seek_buffer[pos * 2] / 131072.0f * voice_vol;
|
||||
float seek_right = (float) samples->seek_buffer[pos * 2 + 1] / 131072.0f * voice_vol;
|
||||
|
||||
float_buffer[i * 2] += seek_left;
|
||||
float_buffer[i * 2 + 1] += seek_right;
|
||||
}
|
||||
|
||||
if (pos >= hdd_seek_samples) {
|
||||
if (pos >= samples->seek_samples) {
|
||||
hdd_seek_voices[v].active = 0;
|
||||
hdd_seek_voices[v].position = 0;
|
||||
} else {
|
||||
@@ -173,19 +495,16 @@ hdd_audio_callback(int16_t *buffer, int length)
|
||||
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
|
||||
if (debug_counter++ % 100 == 0) {
|
||||
pclog("HDD Audio: float_buffer[0]=%.6f, float_buffer[1]=%.6f, spindle_pos=%d, frames=%d\n",
|
||||
float_buffer[0], float_buffer[1], spindle_pos, frames_in_buffer);
|
||||
}
|
||||
} else {
|
||||
if (hdd_spindle_sound_buffer && hdd_samples > 0) {
|
||||
/* Spindle sound from profile */
|
||||
if (samples && samples->spindle_loop_buffer && samples->spindle_loop_samples > 0) {
|
||||
float spindle_volume = samples->spindle_volume;
|
||||
for (int i = 0; i < frames_in_buffer; i++) {
|
||||
buffer[i * 2] = hdd_spindle_sound_buffer[spindle_pos * 2];
|
||||
buffer[i * 2 + 1] = hdd_spindle_sound_buffer[spindle_pos * 2 + 1];
|
||||
buffer[i * 2] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2] * spindle_volume);
|
||||
buffer[i * 2 + 1] = (int16_t)(samples->spindle_loop_buffer[spindle_pos * 2 + 1] * spindle_volume);
|
||||
|
||||
spindle_pos++;
|
||||
if (spindle_pos >= hdd_samples) {
|
||||
if (spindle_pos >= samples->spindle_loop_samples) {
|
||||
spindle_pos = 0;
|
||||
}
|
||||
}
|
||||
@@ -195,19 +514,21 @@ hdd_audio_callback(int16_t *buffer, int length)
|
||||
}
|
||||
}
|
||||
|
||||
if (hdd_seek_sound_buffer && hdd_seek_samples > 0 && hdd_audio_mutex) {
|
||||
/* Seek sounds from profile */
|
||||
if (samples && samples->seek_buffer && samples->seek_samples > 0 && hdd_audio_mutex) {
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES; v++) {
|
||||
if (!hdd_seek_voices[v].active)
|
||||
continue;
|
||||
|
||||
float voice_vol = hdd_seek_voices[v].volume;
|
||||
int pos = hdd_seek_voices[v].position;
|
||||
if (pos < 0) pos = 0;
|
||||
|
||||
for (int i = 0; i < frames_in_buffer && pos < hdd_seek_samples; i++, pos++) {
|
||||
int32_t left = buffer[i * 2] + hdd_seek_sound_buffer[pos * 2] / 2;
|
||||
int32_t right = buffer[i * 2 + 1] + hdd_seek_sound_buffer[pos * 2 + 1] / 2;
|
||||
for (int i = 0; i < frames_in_buffer && pos < samples->seek_samples; i++, pos++) {
|
||||
int32_t left = buffer[i * 2] + (int32_t)(samples->seek_buffer[pos * 2] * voice_vol);
|
||||
int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->seek_buffer[pos * 2 + 1] * voice_vol);
|
||||
|
||||
if (left > 32767) left = 32767;
|
||||
if (left < -32768) left = -32768;
|
||||
@@ -218,7 +539,7 @@ hdd_audio_callback(int16_t *buffer, int length)
|
||||
buffer[i * 2 + 1] = (int16_t) right;
|
||||
}
|
||||
|
||||
if (pos >= hdd_seek_samples) {
|
||||
if (pos >= samples->seek_samples) {
|
||||
hdd_seek_voices[v].active = 0;
|
||||
hdd_seek_voices[v].position = 0;
|
||||
} else {
|
||||
|
||||
@@ -172,6 +172,7 @@ typedef struct hard_disk_t {
|
||||
uint32_t hpc;
|
||||
uint32_t tracks;
|
||||
uint32_t speed_preset;
|
||||
uint32_t audio_profile;
|
||||
|
||||
uint32_t num_zones;
|
||||
uint32_t phy_cyl;
|
||||
|
||||
@@ -1,6 +1,63 @@
|
||||
/*
|
||||
* 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 hard disk audio emulation.
|
||||
*
|
||||
* Authors: Toni Riikonen, <riikonen.toni@gmail.com>
|
||||
*
|
||||
* Copyright 2026 Toni Riikonen.
|
||||
*/
|
||||
#ifndef EMU_HDD_AUDIO_H
|
||||
#define EMU_HDD_AUDIO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <86box/hdd.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HDD_AUDIO_PROFILE_MAX 64
|
||||
|
||||
/* Audio sample configuration structure */
|
||||
typedef struct {
|
||||
char filename[512];
|
||||
float volume;
|
||||
} hdd_audio_sample_config_t;
|
||||
|
||||
/* HDD audio profile configuration */
|
||||
typedef struct {
|
||||
int id;
|
||||
char name[128];
|
||||
char internal_name[64];
|
||||
hdd_audio_sample_config_t spindlemotor_start;
|
||||
hdd_audio_sample_config_t spindlemotor_loop;
|
||||
hdd_audio_sample_config_t spindlemotor_stop;
|
||||
hdd_audio_sample_config_t seek_track;
|
||||
} hdd_audio_profile_config_t;
|
||||
|
||||
/* Functions for profile management */
|
||||
extern void hdd_audio_load_profiles(void);
|
||||
extern int hdd_audio_get_profile_count(void);
|
||||
extern const hdd_audio_profile_config_t *hdd_audio_get_profile(int id);
|
||||
extern const char *hdd_audio_get_profile_name(int id);
|
||||
extern const char *hdd_audio_get_profile_internal_name(int id);
|
||||
extern int hdd_audio_get_profile_by_internal_name(const char *internal_name);
|
||||
|
||||
/* HDD audio initialization and cleanup */
|
||||
extern void hdd_audio_init(void);
|
||||
extern void hdd_audio_reset(void);
|
||||
extern void hdd_audio_close(void);
|
||||
extern void hdd_audio_callback(int16_t *buffer, int length);
|
||||
extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder);
|
||||
extern void hdd_audio_seek(hard_disk_t *hdd, uint32_t new_cylinder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* EMU_HDD_AUDIO_H */
|
||||
@@ -20,6 +20,7 @@
|
||||
extern "C" {
|
||||
#include <86box/86box.h>
|
||||
#include <86box/hdd.h>
|
||||
#include <86box/hdd_audio.h>
|
||||
}
|
||||
|
||||
#include <QStandardItemModel>
|
||||
@@ -36,6 +37,7 @@ const int ColumnHeads = 3;
|
||||
const int ColumnSectors = 4;
|
||||
const int ColumnSize = 5;
|
||||
const int ColumnSpeed = 6;
|
||||
const int ColumnAudio = 7;
|
||||
|
||||
const int DataBus = Qt::UserRole;
|
||||
const int DataBusChannel = Qt::UserRole + 1;
|
||||
@@ -103,6 +105,11 @@ addRow(QAbstractItemModel *model, hard_disk_t *hd)
|
||||
auto speedIndex = model->index(row, ColumnSpeed);
|
||||
model->setData(speedIndex, QObject::tr(hdd_preset_getname(hd->speed_preset)));
|
||||
model->setData(speedIndex, hd->speed_preset, Qt::UserRole);
|
||||
|
||||
auto audioIndex = model->index(row, ColumnAudio);
|
||||
const char *audioName = hdd_audio_get_profile_name(hd->audio_profile);
|
||||
model->setData(audioIndex, audioName ? QObject::tr(audioName) : QObject::tr("None"));
|
||||
model->setData(audioIndex, hd->audio_profile, Qt::UserRole);
|
||||
}
|
||||
|
||||
SettingsHarddisks::SettingsHarddisks(QWidget *parent)
|
||||
@@ -113,7 +120,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent)
|
||||
|
||||
hard_disk_icon = QIcon(":/settings/qt/icons/hard_disk.ico");
|
||||
|
||||
QAbstractItemModel *model = new QStandardItemModel(0, 7, this);
|
||||
QAbstractItemModel *model = new QStandardItemModel(0, 8, this);
|
||||
model->setHeaderData(ColumnBus, Qt::Horizontal, tr("Bus"));
|
||||
model->setHeaderData(ColumnFilename, Qt::Horizontal, tr("File"));
|
||||
model->setHeaderData(ColumnCylinders, Qt::Horizontal, tr("C"));
|
||||
@@ -121,6 +128,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent)
|
||||
model->setHeaderData(ColumnSectors, Qt::Horizontal, tr("S"));
|
||||
model->setHeaderData(ColumnSize, Qt::Horizontal, tr("MiB"));
|
||||
model->setHeaderData(ColumnSpeed, Qt::Horizontal, tr("Model"));
|
||||
model->setHeaderData(ColumnAudio, Qt::Horizontal, tr("Audio"));
|
||||
ui->tableView->setModel(model);
|
||||
|
||||
for (int i = 0; i < HDD_NUM; i++) {
|
||||
@@ -139,6 +147,16 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent)
|
||||
onTableRowChanged(QModelIndex());
|
||||
|
||||
Harddrives::populateBuses(ui->comboBoxBus->model());
|
||||
|
||||
/* Populate audio profile combobox */
|
||||
int profile_count = hdd_audio_get_profile_count();
|
||||
for (int i = 0; i < profile_count; i++) {
|
||||
const char *name = hdd_audio_get_profile_name(i);
|
||||
if (name) {
|
||||
ui->comboBoxAudio->addItem(name, i);
|
||||
}
|
||||
}
|
||||
|
||||
on_comboBoxBus_currentIndexChanged(0);
|
||||
}
|
||||
|
||||
@@ -162,6 +180,7 @@ SettingsHarddisks::save()
|
||||
hdd[i].hpc = idx.siblingAtColumn(ColumnHeads).data().toUInt();
|
||||
hdd[i].spt = idx.siblingAtColumn(ColumnSectors).data().toUInt();
|
||||
hdd[i].speed_preset = idx.siblingAtColumn(ColumnSpeed).data(Qt::UserRole).toUInt();
|
||||
hdd[i].audio_profile = idx.siblingAtColumn(ColumnAudio).data(Qt::UserRole).toUInt();
|
||||
|
||||
QByteArray fileName = idx.siblingAtColumn(ColumnFilename).data(Qt::UserRole).toString().toUtf8();
|
||||
strncpy(hdd[i].fn, fileName.data(), sizeof(hdd[i].fn) - 1);
|
||||
@@ -272,6 +291,24 @@ SettingsHarddisks::on_comboBoxSpeed_currentIndexChanged(int index)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SettingsHarddisks::on_comboBoxAudio_currentIndexChanged(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
auto idx = ui->tableView->selectionModel()->currentIndex();
|
||||
if (idx.isValid()) {
|
||||
auto *model = ui->tableView->model();
|
||||
auto col = idx.siblingAtColumn(ColumnAudio);
|
||||
int prof = ui->comboBoxAudio->currentData(Qt::UserRole).toInt();
|
||||
model->setData(col, prof, Qt::UserRole);
|
||||
|
||||
const char *audioName = hdd_audio_get_profile_name(prof);
|
||||
model->setData(col, audioName ? QObject::tr(audioName) : QObject::tr("None"));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t)
|
||||
{
|
||||
@@ -279,13 +316,16 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t)
|
||||
ui->labelBus->setHidden(hidden);
|
||||
ui->labelChannel->setHidden(hidden);
|
||||
ui->labelSpeed->setHidden(hidden);
|
||||
ui->labelAudio->setHidden(hidden);
|
||||
ui->comboBoxBus->setHidden(hidden);
|
||||
ui->comboBoxChannel->setHidden(hidden);
|
||||
ui->comboBoxSpeed->setHidden(hidden);
|
||||
ui->comboBoxAudio->setHidden(hidden);
|
||||
|
||||
uint32_t bus = current.siblingAtColumn(ColumnBus).data(DataBus).toUInt();
|
||||
uint32_t busChannel = current.siblingAtColumn(ColumnBus).data(DataBusChannel).toUInt();
|
||||
uint32_t speed = current.siblingAtColumn(ColumnSpeed).data(Qt::UserRole).toUInt();
|
||||
uint32_t audio = current.siblingAtColumn(ColumnAudio).data(Qt::UserRole).toUInt();
|
||||
|
||||
auto *model = ui->comboBoxBus->model();
|
||||
auto match = model->match(model->index(0, 0), Qt::UserRole, bus);
|
||||
@@ -302,6 +342,11 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t)
|
||||
if (!match.isEmpty())
|
||||
ui->comboBoxSpeed->setCurrentIndex(match.first().row());
|
||||
|
||||
model = ui->comboBoxAudio->model();
|
||||
match = model->match(model->index(0, 0), Qt::UserRole, audio);
|
||||
if (!match.isEmpty())
|
||||
ui->comboBoxAudio->setCurrentIndex(match.first().row());
|
||||
|
||||
reloadBusChannels();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ private slots:
|
||||
void on_comboBoxBus_currentIndexChanged(int index);
|
||||
void on_comboBoxChannel_currentIndexChanged(int index);
|
||||
void on_comboBoxSpeed_currentIndexChanged(int index);
|
||||
void on_comboBoxAudio_currentIndexChanged(int index);
|
||||
|
||||
void on_pushButtonNew_clicked();
|
||||
void on_pushButtonExisting_clicked();
|
||||
|
||||
@@ -90,6 +90,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="labelAudio">
|
||||
<property name="text">
|
||||
<string>Audio:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QComboBox" name="comboBoxAudio">
|
||||
<property name="maxVisibleItems">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Reference in New Issue
Block a user