HDD audio profile for settings, ui and using the selected profile

This commit is contained in:
Domppari
2026-01-04 08:59:43 +02:00
parent ebe651761b
commit b13e4c44b4
9 changed files with 534 additions and 71 deletions

View File

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

View File

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

View File

@@ -39,7 +39,6 @@ hdd_init(void)
{
/* Clear all global data. */
memset(hdd, 0x00, sizeof(hdd));
hdd_audio_init();
return 0;
}

View File

@@ -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 {

View File

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

View File

@@ -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 */

View File

@@ -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 &current)
{
@@ -279,13 +316,16 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex &current)
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 &current)
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();
}

View File

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

View File

@@ -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>