mirror of
https://github.com/86Box/86Box.git
synced 2026-02-22 09:35:32 -07:00
Merge pull request #6672 from Domppari/hdd_audio_prototype
Support for HDD sound emulation
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>
|
||||
@@ -1252,6 +1253,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);
|
||||
@@ -1320,6 +1323,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)
|
||||
@@ -3463,6 +3471,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);
|
||||
|
||||
@@ -36,6 +36,7 @@ add_library(hdd OBJECT
|
||||
hdc_ide_sff8038i.c
|
||||
hdc_ide_um8673f.c
|
||||
hdc_ide_w83769f.c
|
||||
hdd_audio.c
|
||||
)
|
||||
|
||||
add_library(rdisk OBJECT rdisk.c)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <86box/hdd.h>
|
||||
#include <86box/cdrom.h>
|
||||
#include <86box/video.h>
|
||||
#include <86box/hdd_audio.h>
|
||||
#include "cpu.h"
|
||||
|
||||
#define HDD_OVERHEAD_TIME 50.0
|
||||
@@ -38,7 +39,6 @@ hdd_init(void)
|
||||
{
|
||||
/* Clear all global data. */
|
||||
memset(hdd, 0x00, sizeof(hdd));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -196,6 +196,9 @@ hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_t operation, uint8_
|
||||
}
|
||||
|
||||
if (!max_seek_time || seek_time <= max_seek_time) {
|
||||
if (new_cylinder != hdd->cur_cylinder)
|
||||
hdd_audio_seek(hdd, new_cylinder);
|
||||
|
||||
hdd->cur_addr = dst_addr;
|
||||
hdd->cur_track = new_track;
|
||||
hdd->cur_cylinder = new_cylinder;
|
||||
@@ -586,6 +589,14 @@ hdd_preset_get_internal_name(int preset)
|
||||
return hdd_speed_presets[preset].internal_name;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
hdd_preset_get_rpm(int preset)
|
||||
{
|
||||
if (preset < 0 || preset >= hdd_preset_get_num())
|
||||
return 0;
|
||||
return hdd_speed_presets[preset].rpm;
|
||||
}
|
||||
|
||||
int
|
||||
hdd_preset_get_from_internal_name(char *s)
|
||||
{
|
||||
|
||||
972
src/disk/hdd_audio.c
Normal file
972
src/disk/hdd_audio.c
Normal file
@@ -0,0 +1,972 @@
|
||||
/*
|
||||
* 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 per HDD */
|
||||
#define HDD_MAX_SEEK_VOICES_PER_HDD 8
|
||||
|
||||
/* Maximum number of HDDs with audio emulation */
|
||||
#define HDD_AUDIO_MAX_DRIVES 8
|
||||
|
||||
typedef struct {
|
||||
int active;
|
||||
int position;
|
||||
float volume;
|
||||
int profile_id; /* Which profile's seek sound to use */
|
||||
} hdd_seek_voice_t;
|
||||
|
||||
/* Per-HDD audio state */
|
||||
typedef struct {
|
||||
int hdd_index; /* Index into hdd[] array */
|
||||
int profile_id; /* Audio profile ID */
|
||||
hdd_spindle_state_t spindle_state;
|
||||
int spindle_pos;
|
||||
int spindle_transition_pos;
|
||||
hdd_seek_voice_t seek_voices[HDD_MAX_SEEK_VOICES_PER_HDD];
|
||||
} hdd_audio_drive_state_t;
|
||||
|
||||
/* Audio samples structure for a profile */
|
||||
typedef struct {
|
||||
int16_t *spindle_start_buffer;
|
||||
int spindle_start_samples;
|
||||
float spindle_start_volume;
|
||||
int16_t *spindle_loop_buffer;
|
||||
int spindle_loop_samples;
|
||||
float spindle_loop_volume;
|
||||
int16_t *spindle_stop_buffer;
|
||||
int spindle_stop_samples;
|
||||
float spindle_stop_volume;
|
||||
int16_t *seek_buffer;
|
||||
int seek_samples;
|
||||
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];
|
||||
|
||||
/* Per-HDD audio states */
|
||||
static hdd_audio_drive_state_t drive_states[HDD_AUDIO_MAX_DRIVES];
|
||||
static int active_drive_count = 0;
|
||||
|
||||
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 };
|
||||
|
||||
/*
|
||||
* asset_getfile returns a path from the trusted asset search paths.
|
||||
* The filename is hardcoded and validated against existing files.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/* Validate that the path does not contain path traversal sequences */
|
||||
if (strstr(cfg_fn, "..") != NULL) {
|
||||
pclog("HDD Audio: Invalid path detected\n");
|
||||
return;
|
||||
}
|
||||
|
||||
profiles_ini = ini_read_ex(cfg_fn, 1); /* lgtm[cpp/path-injection] */
|
||||
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);
|
||||
|
||||
config->rpm = ini_section_get_int(cat, "rpm", 0);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
hdd_audio_get_profile_rpm(int id)
|
||||
{
|
||||
if (id < 0 || id >= audio_profile_count)
|
||||
return 0;
|
||||
return audio_profiles[id].rpm;
|
||||
}
|
||||
|
||||
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_loop_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) {
|
||||
samples->spindle_start_volume = config->spindlemotor_start.volume;
|
||||
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) {
|
||||
samples->spindle_stop_volume = config->spindlemotor_stop.volume;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Find drive state for a given HDD index, or NULL if not tracked */
|
||||
static hdd_audio_drive_state_t *
|
||||
hdd_audio_find_drive_state(int hdd_index)
|
||||
{
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
if (drive_states[i].hdd_index == hdd_index)
|
||||
return &drive_states[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_init(void)
|
||||
{
|
||||
/* Initialize profile samples */
|
||||
memset(profile_samples, 0, sizeof(profile_samples));
|
||||
memset(drive_states, 0, sizeof(drive_states));
|
||||
active_drive_count = 0;
|
||||
|
||||
pclog("HDD Audio Init: audio_profile_count=%d\n", audio_profile_count);
|
||||
|
||||
/* Create mutex BEFORE loading samples or calling spinup */
|
||||
if (!hdd_audio_mutex)
|
||||
hdd_audio_mutex = thread_create_mutex();
|
||||
|
||||
/* Find all HDDs with valid audio profiles and initialize their states */
|
||||
for (int i = 0; i < HDD_NUM && active_drive_count < HDD_AUDIO_MAX_DRIVES; i++) {
|
||||
if (hdd[i].bus_type != HDD_BUS_DISABLED && hdd[i].audio_profile > 0) {
|
||||
pclog("HDD Audio Init: HDD %d bus_type=%d audio_profile=%d\n",
|
||||
i, hdd[i].bus_type, hdd[i].audio_profile);
|
||||
|
||||
hdd_audio_drive_state_t *state = &drive_states[active_drive_count];
|
||||
state->hdd_index = i;
|
||||
state->profile_id = hdd[i].audio_profile;
|
||||
state->spindle_state = HDD_SPINDLE_STOPPED;
|
||||
state->spindle_pos = 0;
|
||||
state->spindle_transition_pos = 0;
|
||||
|
||||
/* Initialize seek voices for this drive */
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
state->seek_voices[v].active = 0;
|
||||
state->seek_voices[v].position = 0;
|
||||
state->seek_voices[v].volume = 1.0f;
|
||||
state->seek_voices[v].profile_id = state->profile_id;
|
||||
}
|
||||
|
||||
/* Load samples for this profile if not already loaded */
|
||||
hdd_audio_load_profile_samples(state->profile_id);
|
||||
|
||||
pclog("HDD Audio: Initialized drive %d with profile %d (%s)\n",
|
||||
i, state->profile_id,
|
||||
hdd_audio_get_profile_name(state->profile_id));
|
||||
|
||||
active_drive_count++;
|
||||
}
|
||||
}
|
||||
|
||||
pclog("HDD Audio Init: %d active drives with audio\n", active_drive_count);
|
||||
|
||||
/* Start spindle motors for all active drives */
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
hdd_audio_spinup_drive(drive_states[i].hdd_index);
|
||||
}
|
||||
|
||||
sound_hdd_thread_init();
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_reset(void)
|
||||
{
|
||||
pclog("HDD Audio: Reset\n");
|
||||
|
||||
/* Lock mutex to prevent audio callback from accessing buffers during reset */
|
||||
if (hdd_audio_mutex)
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
|
||||
/* Reset all drive states */
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
drive_states[i].spindle_state = HDD_SPINDLE_STOPPED;
|
||||
drive_states[i].spindle_pos = 0;
|
||||
drive_states[i].spindle_transition_pos = 0;
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
drive_states[i].seek_voices[v].active = 0;
|
||||
drive_states[i].seek_voices[v].position = 0;
|
||||
drive_states[i].seek_voices[v].volume = 1.0f;
|
||||
}
|
||||
}
|
||||
active_drive_count = 0;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (hdd_audio_mutex)
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
|
||||
/* Find all HDDs with valid audio profiles and initialize their states */
|
||||
for (int i = 0; i < HDD_NUM && active_drive_count < HDD_AUDIO_MAX_DRIVES; i++) {
|
||||
if (hdd[i].bus_type != HDD_BUS_DISABLED && hdd[i].audio_profile > 0) {
|
||||
pclog("HDD Audio Reset: HDD %d audio_profile=%d\n", i, hdd[i].audio_profile);
|
||||
|
||||
hdd_audio_drive_state_t *state = &drive_states[active_drive_count];
|
||||
state->hdd_index = i;
|
||||
state->profile_id = hdd[i].audio_profile;
|
||||
state->spindle_state = HDD_SPINDLE_STOPPED;
|
||||
state->spindle_pos = 0;
|
||||
state->spindle_transition_pos = 0;
|
||||
|
||||
/* Initialize seek voices for this drive */
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
state->seek_voices[v].active = 0;
|
||||
state->seek_voices[v].position = 0;
|
||||
state->seek_voices[v].volume = 1.0f;
|
||||
state->seek_voices[v].profile_id = state->profile_id;
|
||||
}
|
||||
|
||||
/* Load samples for this profile if not already loaded */
|
||||
hdd_audio_load_profile_samples(state->profile_id);
|
||||
|
||||
pclog("HDD Audio: Reset drive %d with profile %d (%s)\n",
|
||||
i, state->profile_id,
|
||||
hdd_audio_get_profile_name(state->profile_id));
|
||||
|
||||
active_drive_count++;
|
||||
}
|
||||
}
|
||||
|
||||
pclog("HDD Audio Reset: %d active drives with audio\n", active_drive_count);
|
||||
|
||||
/* Start spindle motors for all active drives */
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
hdd_audio_spinup_drive(drive_states[i].hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* Find the drive state for this HDD */
|
||||
hdd_audio_drive_state_t *drive_state = NULL;
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
if (&hdd[drive_states[i].hdd_index] == hdd_drive) {
|
||||
drive_state = &drive_states[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If no drive state found, drive has no audio profile */
|
||||
if (!drive_state)
|
||||
return;
|
||||
|
||||
int profile_id = drive_state->profile_id;
|
||||
|
||||
/* No audio profile selected */
|
||||
if (profile_id == 0 || profile_id >= audio_profile_count)
|
||||
return;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Mutex must exist */
|
||||
if (!hdd_audio_mutex)
|
||||
return;
|
||||
|
||||
int min_seek_spacing = 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);
|
||||
|
||||
/* Check if we should skip due to minimum spacing (per-drive) */
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
if (drive_state->seek_voices[v].active) {
|
||||
int pos = drive_state->seek_voices[v].position;
|
||||
if (pos >= 0 && pos < min_seek_spacing) {
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find a free seek voice for this drive */
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
if (!drive_state->seek_voices[v].active) {
|
||||
drive_state->seek_voices[v].active = 1;
|
||||
drive_state->seek_voices[v].position = 0;
|
||||
drive_state->seek_voices[v].volume = samples->seek_volume;
|
||||
drive_state->seek_voices[v].profile_id = profile_id;
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
|
||||
/* Spinup a specific drive by HDD index */
|
||||
void
|
||||
hdd_audio_spinup_drive(int hdd_index)
|
||||
{
|
||||
hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index);
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
if (state->spindle_state == HDD_SPINDLE_RUNNING || state->spindle_state == HDD_SPINDLE_STARTING)
|
||||
return;
|
||||
|
||||
pclog("HDD Audio: Spinup requested for drive %d (current state: %d)\n", hdd_index, state->spindle_state);
|
||||
|
||||
if (hdd_audio_mutex)
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
state->spindle_state = HDD_SPINDLE_STARTING;
|
||||
state->spindle_transition_pos = 0;
|
||||
if (hdd_audio_mutex)
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
|
||||
/* Spindown a specific drive by HDD index */
|
||||
void
|
||||
hdd_audio_spindown_drive(int hdd_index)
|
||||
{
|
||||
hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index);
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
if (state->spindle_state == HDD_SPINDLE_STOPPED || state->spindle_state == HDD_SPINDLE_STOPPING)
|
||||
return;
|
||||
|
||||
pclog("HDD Audio: Spindown requested for drive %d (current state: %d)\n", hdd_index, state->spindle_state);
|
||||
|
||||
if (hdd_audio_mutex)
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
state->spindle_state = HDD_SPINDLE_STOPPING;
|
||||
state->spindle_transition_pos = 0;
|
||||
if (hdd_audio_mutex)
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
|
||||
/* Legacy functions for backward compatibility - operate on all drives */
|
||||
void
|
||||
hdd_audio_spinup(void)
|
||||
{
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
hdd_audio_spinup_drive(drive_states[i].hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_spindown(void)
|
||||
{
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
hdd_audio_spindown_drive(drive_states[i].hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
hdd_spindle_state_t
|
||||
hdd_audio_get_spindle_state(void)
|
||||
{
|
||||
/* Return running if any drive is running */
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
if (drive_states[i].spindle_state == HDD_SPINDLE_RUNNING)
|
||||
return HDD_SPINDLE_RUNNING;
|
||||
}
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
if (drive_states[i].spindle_state == HDD_SPINDLE_STARTING)
|
||||
return HDD_SPINDLE_STARTING;
|
||||
}
|
||||
for (int i = 0; i < active_drive_count; i++) {
|
||||
if (drive_states[i].spindle_state == HDD_SPINDLE_STOPPING)
|
||||
return HDD_SPINDLE_STOPPING;
|
||||
}
|
||||
return HDD_SPINDLE_STOPPED;
|
||||
}
|
||||
|
||||
hdd_spindle_state_t
|
||||
hdd_audio_get_drive_spindle_state(int hdd_index)
|
||||
{
|
||||
hdd_audio_drive_state_t *state = hdd_audio_find_drive_state(hdd_index);
|
||||
if (!state)
|
||||
return HDD_SPINDLE_STOPPED;
|
||||
return state->spindle_state;
|
||||
}
|
||||
|
||||
/* Helper: Mix spindle start sound into float buffer */
|
||||
static void
|
||||
hdd_audio_mix_spindle_start_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples,
|
||||
float *float_buffer, int frames_in_buffer)
|
||||
{
|
||||
if (!samples->spindle_start_buffer || samples->spindle_start_samples <= 0) {
|
||||
state->spindle_state = HDD_SPINDLE_RUNNING;
|
||||
state->spindle_pos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float start_volume = samples->spindle_start_volume;
|
||||
for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_start_samples; i++) {
|
||||
float left_sample = (float) samples->spindle_start_buffer[state->spindle_transition_pos * 2] / 131072.0f * start_volume;
|
||||
float right_sample = (float) samples->spindle_start_buffer[state->spindle_transition_pos * 2 + 1] / 131072.0f * start_volume;
|
||||
float_buffer[i * 2] += left_sample;
|
||||
float_buffer[i * 2 + 1] += right_sample;
|
||||
state->spindle_transition_pos++;
|
||||
}
|
||||
|
||||
if (state->spindle_transition_pos >= samples->spindle_start_samples) {
|
||||
state->spindle_state = HDD_SPINDLE_RUNNING;
|
||||
state->spindle_pos = 0;
|
||||
pclog("HDD Audio: Drive %d spinup complete, now running\n", state->hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix spindle loop sound into float buffer */
|
||||
static void
|
||||
hdd_audio_mix_spindle_loop_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples,
|
||||
float *float_buffer, int frames_in_buffer)
|
||||
{
|
||||
if (!samples->spindle_loop_buffer || samples->spindle_loop_samples <= 0)
|
||||
return;
|
||||
|
||||
float spindle_volume = samples->spindle_loop_volume;
|
||||
for (int i = 0; i < frames_in_buffer; i++) {
|
||||
float left_sample = (float) samples->spindle_loop_buffer[state->spindle_pos * 2] / 131072.0f * spindle_volume;
|
||||
float right_sample = (float) samples->spindle_loop_buffer[state->spindle_pos * 2 + 1] / 131072.0f * spindle_volume;
|
||||
float_buffer[i * 2] += left_sample;
|
||||
float_buffer[i * 2 + 1] += right_sample;
|
||||
|
||||
state->spindle_pos++;
|
||||
if (state->spindle_pos >= samples->spindle_loop_samples) {
|
||||
state->spindle_pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix spindle stop sound into float buffer */
|
||||
static void
|
||||
hdd_audio_mix_spindle_stop_float(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples,
|
||||
float *float_buffer, int frames_in_buffer)
|
||||
{
|
||||
if (!samples->spindle_stop_buffer || samples->spindle_stop_samples <= 0) {
|
||||
state->spindle_state = HDD_SPINDLE_STOPPED;
|
||||
return;
|
||||
}
|
||||
|
||||
float stop_volume = samples->spindle_stop_volume;
|
||||
for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_stop_samples; i++) {
|
||||
float left_sample = (float) samples->spindle_stop_buffer[state->spindle_transition_pos * 2] / 131072.0f * stop_volume;
|
||||
float right_sample = (float) samples->spindle_stop_buffer[state->spindle_transition_pos * 2 + 1] / 131072.0f * stop_volume;
|
||||
float_buffer[i * 2] += left_sample;
|
||||
float_buffer[i * 2 + 1] += right_sample;
|
||||
state->spindle_transition_pos++;
|
||||
}
|
||||
|
||||
if (state->spindle_transition_pos >= samples->spindle_stop_samples) {
|
||||
state->spindle_state = HDD_SPINDLE_STOPPED;
|
||||
pclog("HDD Audio: Drive %d spindown complete, now stopped\n", state->hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix seek sounds into float buffer */
|
||||
static void
|
||||
hdd_audio_mix_seek_float(hdd_audio_drive_state_t *state, float *float_buffer, int frames_in_buffer)
|
||||
{
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
if (!state->seek_voices[v].active)
|
||||
continue;
|
||||
|
||||
int seek_profile_id = state->seek_voices[v].profile_id;
|
||||
hdd_audio_samples_t *seek_samples = &profile_samples[seek_profile_id];
|
||||
if (!seek_samples->seek_buffer || seek_samples->seek_samples == 0)
|
||||
continue;
|
||||
|
||||
float voice_vol = state->seek_voices[v].volume;
|
||||
int pos = state->seek_voices[v].position;
|
||||
if (pos < 0) pos = 0;
|
||||
|
||||
for (int i = 0; i < frames_in_buffer && pos < seek_samples->seek_samples; i++, pos++) {
|
||||
float seek_left = (float) seek_samples->seek_buffer[pos * 2] / 131072.0f * voice_vol;
|
||||
float seek_right = (float) seek_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 >= seek_samples->seek_samples) {
|
||||
state->seek_voices[v].active = 0;
|
||||
state->seek_voices[v].position = 0;
|
||||
} else {
|
||||
state->seek_voices[v].position = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix spindle start sound into int16 buffer */
|
||||
static void
|
||||
hdd_audio_mix_spindle_start_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples,
|
||||
int16_t *buffer, int frames_in_buffer)
|
||||
{
|
||||
if (!samples->spindle_start_buffer || samples->spindle_start_samples <= 0) {
|
||||
state->spindle_state = HDD_SPINDLE_RUNNING;
|
||||
state->spindle_pos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
float start_volume = samples->spindle_start_volume;
|
||||
for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_start_samples; i++) {
|
||||
int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_start_buffer[state->spindle_transition_pos * 2] * start_volume);
|
||||
int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_start_buffer[state->spindle_transition_pos * 2 + 1] * start_volume);
|
||||
if (left > 32767) left = 32767;
|
||||
if (left < -32768) left = -32768;
|
||||
if (right > 32767) right = 32767;
|
||||
if (right < -32768) right = -32768;
|
||||
buffer[i * 2] = (int16_t) left;
|
||||
buffer[i * 2 + 1] = (int16_t) right;
|
||||
state->spindle_transition_pos++;
|
||||
}
|
||||
|
||||
if (state->spindle_transition_pos >= samples->spindle_start_samples) {
|
||||
state->spindle_state = HDD_SPINDLE_RUNNING;
|
||||
state->spindle_pos = 0;
|
||||
pclog("HDD Audio: Drive %d spinup complete, now running\n", state->hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix spindle loop sound into int16 buffer */
|
||||
static void
|
||||
hdd_audio_mix_spindle_loop_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples,
|
||||
int16_t *buffer, int frames_in_buffer)
|
||||
{
|
||||
if (!samples->spindle_loop_buffer || samples->spindle_loop_samples <= 0)
|
||||
return;
|
||||
|
||||
float spindle_volume = samples->spindle_loop_volume;
|
||||
for (int i = 0; i < frames_in_buffer; i++) {
|
||||
int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_loop_buffer[state->spindle_pos * 2] * spindle_volume);
|
||||
int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_loop_buffer[state->spindle_pos * 2 + 1] * spindle_volume);
|
||||
if (left > 32767) left = 32767;
|
||||
if (left < -32768) left = -32768;
|
||||
if (right > 32767) right = 32767;
|
||||
if (right < -32768) right = -32768;
|
||||
buffer[i * 2] = (int16_t) left;
|
||||
buffer[i * 2 + 1] = (int16_t) right;
|
||||
|
||||
state->spindle_pos++;
|
||||
if (state->spindle_pos >= samples->spindle_loop_samples) {
|
||||
state->spindle_pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix spindle stop sound into int16 buffer */
|
||||
static void
|
||||
hdd_audio_mix_spindle_stop_int16(hdd_audio_drive_state_t *state, hdd_audio_samples_t *samples,
|
||||
int16_t *buffer, int frames_in_buffer)
|
||||
{
|
||||
if (!samples->spindle_stop_buffer || samples->spindle_stop_samples <= 0) {
|
||||
state->spindle_state = HDD_SPINDLE_STOPPED;
|
||||
return;
|
||||
}
|
||||
|
||||
float stop_volume = samples->spindle_stop_volume;
|
||||
for (int i = 0; i < frames_in_buffer && state->spindle_transition_pos < samples->spindle_stop_samples; i++) {
|
||||
int32_t left = buffer[i * 2] + (int32_t)(samples->spindle_stop_buffer[state->spindle_transition_pos * 2] * stop_volume);
|
||||
int32_t right = buffer[i * 2 + 1] + (int32_t)(samples->spindle_stop_buffer[state->spindle_transition_pos * 2 + 1] * stop_volume);
|
||||
if (left > 32767) left = 32767;
|
||||
if (left < -32768) left = -32768;
|
||||
if (right > 32767) right = 32767;
|
||||
if (right < -32768) right = -32768;
|
||||
buffer[i * 2] = (int16_t) left;
|
||||
buffer[i * 2 + 1] = (int16_t) right;
|
||||
state->spindle_transition_pos++;
|
||||
}
|
||||
|
||||
if (state->spindle_transition_pos >= samples->spindle_stop_samples) {
|
||||
state->spindle_state = HDD_SPINDLE_STOPPED;
|
||||
pclog("HDD Audio: Drive %d spindown complete, now stopped\n", state->hdd_index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Mix seek sounds into int16 buffer */
|
||||
static void
|
||||
hdd_audio_mix_seek_int16(hdd_audio_drive_state_t *state, int16_t *buffer, int frames_in_buffer)
|
||||
{
|
||||
for (int v = 0; v < HDD_MAX_SEEK_VOICES_PER_HDD; v++) {
|
||||
if (!state->seek_voices[v].active)
|
||||
continue;
|
||||
|
||||
int seek_profile_id = state->seek_voices[v].profile_id;
|
||||
hdd_audio_samples_t *seek_samples = &profile_samples[seek_profile_id];
|
||||
if (!seek_samples->seek_buffer || seek_samples->seek_samples == 0)
|
||||
continue;
|
||||
|
||||
float voice_vol = state->seek_voices[v].volume;
|
||||
int pos = state->seek_voices[v].position;
|
||||
if (pos < 0) pos = 0;
|
||||
|
||||
for (int i = 0; i < frames_in_buffer && pos < seek_samples->seek_samples; i++, pos++) {
|
||||
int32_t left = buffer[i * 2] + (int32_t)(seek_samples->seek_buffer[pos * 2] * voice_vol);
|
||||
int32_t right = buffer[i * 2 + 1] + (int32_t)(seek_samples->seek_buffer[pos * 2 + 1] * voice_vol);
|
||||
|
||||
if (left > 32767) left = 32767;
|
||||
if (left < -32768) left = -32768;
|
||||
if (right > 32767) right = 32767;
|
||||
if (right < -32768) right = -32768;
|
||||
|
||||
buffer[i * 2] = (int16_t) left;
|
||||
buffer[i * 2 + 1] = (int16_t) right;
|
||||
}
|
||||
|
||||
if (pos >= seek_samples->seek_samples) {
|
||||
state->seek_voices[v].active = 0;
|
||||
state->seek_voices[v].position = 0;
|
||||
} else {
|
||||
state->seek_voices[v].position = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Process a single drive's audio in float mode */
|
||||
static void
|
||||
hdd_audio_process_drive_float(hdd_audio_drive_state_t *state, float *float_buffer, int frames_in_buffer)
|
||||
{
|
||||
int profile_id = state->profile_id;
|
||||
|
||||
if (profile_id <= 0 || profile_id >= HDD_AUDIO_PROFILE_MAX)
|
||||
return;
|
||||
|
||||
hdd_audio_samples_t *samples = &profile_samples[profile_id];
|
||||
if (!samples->loaded)
|
||||
return;
|
||||
|
||||
/* Handle spindle states for this drive */
|
||||
switch (state->spindle_state) {
|
||||
case HDD_SPINDLE_STARTING:
|
||||
hdd_audio_mix_spindle_start_float(state, samples, float_buffer, frames_in_buffer);
|
||||
break;
|
||||
case HDD_SPINDLE_RUNNING:
|
||||
hdd_audio_mix_spindle_loop_float(state, samples, float_buffer, frames_in_buffer);
|
||||
break;
|
||||
case HDD_SPINDLE_STOPPING:
|
||||
hdd_audio_mix_spindle_stop_float(state, samples, float_buffer, frames_in_buffer);
|
||||
break;
|
||||
case HDD_SPINDLE_STOPPED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Seek sounds - only play when spindle is running */
|
||||
if (samples->seek_buffer && samples->seek_samples > 0 &&
|
||||
hdd_audio_mutex && state->spindle_state == HDD_SPINDLE_RUNNING) {
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
hdd_audio_mix_seek_float(state, float_buffer, frames_in_buffer);
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/* Process a single drive's audio in int16 mode */
|
||||
static void
|
||||
hdd_audio_process_drive_int16(hdd_audio_drive_state_t *state, int16_t *buffer, int frames_in_buffer)
|
||||
{
|
||||
int profile_id = state->profile_id;
|
||||
|
||||
if (profile_id <= 0 || profile_id >= HDD_AUDIO_PROFILE_MAX)
|
||||
return;
|
||||
|
||||
hdd_audio_samples_t *samples = &profile_samples[profile_id];
|
||||
if (!samples->loaded)
|
||||
return;
|
||||
|
||||
/* Handle spindle states for this drive */
|
||||
switch (state->spindle_state) {
|
||||
case HDD_SPINDLE_STARTING:
|
||||
hdd_audio_mix_spindle_start_int16(state, samples, buffer, frames_in_buffer);
|
||||
break;
|
||||
case HDD_SPINDLE_RUNNING:
|
||||
hdd_audio_mix_spindle_loop_int16(state, samples, buffer, frames_in_buffer);
|
||||
break;
|
||||
case HDD_SPINDLE_STOPPING:
|
||||
hdd_audio_mix_spindle_stop_int16(state, samples, buffer, frames_in_buffer);
|
||||
break;
|
||||
case HDD_SPINDLE_STOPPED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Seek sounds - only play when spindle is running */
|
||||
if (samples->seek_buffer && samples->seek_samples > 0 &&
|
||||
hdd_audio_mutex && state->spindle_state == HDD_SPINDLE_RUNNING) {
|
||||
thread_wait_mutex(hdd_audio_mutex);
|
||||
hdd_audio_mix_seek_int16(state, buffer, frames_in_buffer);
|
||||
thread_release_mutex(hdd_audio_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
hdd_audio_callback(int16_t *buffer, int length)
|
||||
{
|
||||
int frames_in_buffer = length / 2;
|
||||
|
||||
if (sound_is_float) {
|
||||
float *float_buffer = (float *) buffer;
|
||||
|
||||
/* Initialize buffer to silence */
|
||||
for (int i = 0; i < length; i++) {
|
||||
float_buffer[i] = 0.0f;
|
||||
}
|
||||
|
||||
/* Process each active drive */
|
||||
for (int d = 0; d < active_drive_count; d++) {
|
||||
hdd_audio_process_drive_float(&drive_states[d], float_buffer, frames_in_buffer);
|
||||
}
|
||||
} else {
|
||||
/* Initialize buffer to silence */
|
||||
for (int i = 0; i < length; i++) {
|
||||
buffer[i] = 0;
|
||||
}
|
||||
|
||||
/* Process each active drive */
|
||||
for (int d = 0; d < active_drive_count; d++) {
|
||||
hdd_audio_process_drive_int16(&drive_states[d], buffer, frames_in_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <86box/plat.h>
|
||||
#include <86box/path.h>
|
||||
#include <86box/ini.h>
|
||||
#include <86box/sound_util.h>
|
||||
|
||||
#ifndef DISABLE_FDD_AUDIO
|
||||
|
||||
@@ -53,9 +54,6 @@ static multi_seek_state_t seek_state[FDD_NUM][MAX_CONCURRENT_SEEKS] = {};
|
||||
extern uint64_t motoron[FDD_NUM];
|
||||
extern char exe_path[2048];
|
||||
|
||||
/* Forward declaration */
|
||||
static int16_t *load_wav(const char *filename, int *sample_count);
|
||||
|
||||
extern uint8_t *rom;
|
||||
extern uint32_t biosmask;
|
||||
extern uint32_t biosaddr;
|
||||
@@ -412,7 +410,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->spindlemotor_start.buffer == NULL && config->spindlemotor_start.filename[0]) {
|
||||
strcpy(samples->spindlemotor_start.filename, config->spindlemotor_start.filename);
|
||||
samples->spindlemotor_start.volume = config->spindlemotor_start.volume;
|
||||
samples->spindlemotor_start.buffer = load_wav(config->spindlemotor_start.filename,
|
||||
samples->spindlemotor_start.buffer = sound_load_wav(config->spindlemotor_start.filename,
|
||||
&samples->spindlemotor_start.samples);
|
||||
if (samples->spindlemotor_start.buffer) {
|
||||
fdd_log(" Loaded spindlemotor_start: %s (%d samples, volume %.2f)\n",
|
||||
@@ -428,7 +426,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->spindlemotor_loop.buffer == NULL && config->spindlemotor_loop.filename[0]) {
|
||||
strcpy(samples->spindlemotor_loop.filename, config->spindlemotor_loop.filename);
|
||||
samples->spindlemotor_loop.volume = config->spindlemotor_loop.volume;
|
||||
samples->spindlemotor_loop.buffer = load_wav(config->spindlemotor_loop.filename,
|
||||
samples->spindlemotor_loop.buffer = sound_load_wav(config->spindlemotor_loop.filename,
|
||||
&samples->spindlemotor_loop.samples);
|
||||
if (samples->spindlemotor_loop.buffer) {
|
||||
fdd_log(" Loaded spindlemotor_loop: %s (%d samples, volume %.2f)\n",
|
||||
@@ -444,7 +442,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->spindlemotor_stop.buffer == NULL && config->spindlemotor_stop.filename[0]) {
|
||||
strcpy(samples->spindlemotor_stop.filename, config->spindlemotor_stop.filename);
|
||||
samples->spindlemotor_stop.volume = config->spindlemotor_stop.volume;
|
||||
samples->spindlemotor_stop.buffer = load_wav(config->spindlemotor_stop.filename,
|
||||
samples->spindlemotor_stop.buffer = sound_load_wav(config->spindlemotor_stop.filename,
|
||||
&samples->spindlemotor_stop.samples);
|
||||
if (samples->spindlemotor_stop.buffer) {
|
||||
fdd_log(" Loaded spindlemotor_stop: %s (%d samples, volume %.2f)\n",
|
||||
@@ -466,7 +464,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->seek_up[idx].buffer == NULL && config->seek_up[idx].filename[0]) {
|
||||
strcpy(samples->seek_up[idx].filename, config->seek_up[idx].filename);
|
||||
samples->seek_up[idx].volume = config->seek_up[idx].volume;
|
||||
samples->seek_up[idx].buffer = load_wav(config->seek_up[idx].filename,
|
||||
samples->seek_up[idx].buffer = sound_load_wav(config->seek_up[idx].filename,
|
||||
&samples->seek_up[idx].samples);
|
||||
if (samples->seek_up[idx].buffer) {
|
||||
fdd_log(" Loaded seek_up[%d]: %s (%d samples, volume %.2f)\n",
|
||||
@@ -479,7 +477,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->seek_down[idx].buffer == NULL && config->seek_down[idx].filename[0]) {
|
||||
strcpy(samples->seek_down[idx].filename, config->seek_down[idx].filename);
|
||||
samples->seek_down[idx].volume = config->seek_down[idx].volume;
|
||||
samples->seek_down[idx].buffer = load_wav(config->seek_down[idx].filename,
|
||||
samples->seek_down[idx].buffer = sound_load_wav(config->seek_down[idx].filename,
|
||||
&samples->seek_down[idx].samples);
|
||||
if (samples->seek_down[idx].buffer) {
|
||||
fdd_log(" Loaded seek_down[%d]: %s (%d samples, volume %.2f)\n",
|
||||
@@ -493,7 +491,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->post_seek_up[idx].buffer == NULL) {
|
||||
strcpy(samples->post_seek_up[idx].filename, config->post_seek_up[idx].filename);
|
||||
samples->post_seek_up[idx].volume = config->post_seek_up[idx].volume;
|
||||
samples->post_seek_up[idx].buffer = load_wav(config->post_seek_up[idx].filename,
|
||||
samples->post_seek_up[idx].buffer = sound_load_wav(config->post_seek_up[idx].filename,
|
||||
&samples->post_seek_up[idx].samples);
|
||||
if (samples->post_seek_up[idx].buffer) {
|
||||
fdd_log(" Loaded POST seek_up[%d] (%d-track): %s (%d samples, volume %.2f)\n",
|
||||
@@ -507,7 +505,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->post_seek_down[idx].buffer == NULL) {
|
||||
strcpy(samples->post_seek_down[idx].filename, config->post_seek_down[idx].filename);
|
||||
samples->post_seek_down[idx].volume = config->post_seek_down[idx].volume;
|
||||
samples->post_seek_down[idx].buffer = load_wav(config->post_seek_down[idx].filename,
|
||||
samples->post_seek_down[idx].buffer = sound_load_wav(config->post_seek_down[idx].filename,
|
||||
&samples->post_seek_down[idx].samples);
|
||||
if (samples->post_seek_down[idx].buffer) {
|
||||
fdd_log(" Loaded POST seek_down[%d] (%d-track): %s (%d samples, volume %.2f)\n",
|
||||
@@ -529,7 +527,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->bios_post_seek_up[vendor][idx].buffer == NULL) {
|
||||
strcpy(samples->bios_post_seek_up[vendor][idx].filename, config->bios_post_seek_up[vendor][idx].filename);
|
||||
samples->bios_post_seek_up[vendor][idx].volume = config->bios_post_seek_up[vendor][idx].volume;
|
||||
samples->bios_post_seek_up[vendor][idx].buffer = load_wav(config->bios_post_seek_up[vendor][idx].filename,
|
||||
samples->bios_post_seek_up[vendor][idx].buffer = sound_load_wav(config->bios_post_seek_up[vendor][idx].filename,
|
||||
&samples->bios_post_seek_up[vendor][idx].samples);
|
||||
if (samples->bios_post_seek_up[vendor][idx].buffer) {
|
||||
fdd_log(" Loaded %s POST seek_up[%d] (%d-track): %s (%d samples, volume %.2f)\n",
|
||||
@@ -543,7 +541,7 @@ load_profile_samples(int profile_id)
|
||||
if (samples->bios_post_seek_down[vendor][idx].buffer == NULL) {
|
||||
strcpy(samples->bios_post_seek_down[vendor][idx].filename, config->bios_post_seek_down[vendor][idx].filename);
|
||||
samples->bios_post_seek_down[vendor][idx].volume = config->bios_post_seek_down[vendor][idx].volume;
|
||||
samples->bios_post_seek_down[vendor][idx].buffer = load_wav(config->bios_post_seek_down[vendor][idx].filename,
|
||||
samples->bios_post_seek_down[vendor][idx].buffer = sound_load_wav(config->bios_post_seek_down[vendor][idx].filename,
|
||||
&samples->bios_post_seek_down[vendor][idx].samples);
|
||||
if (samples->bios_post_seek_down[vendor][idx].buffer) {
|
||||
fdd_log(" Loaded %s POST seek_down[%d] (%d-track): %s (%d samples, volume %.2f)\n",
|
||||
@@ -947,92 +945,6 @@ fdd_audio_play_multi_track_seek(int drive, int from_track, int to_track)
|
||||
drive, slot, sample_to_use->samples);
|
||||
}
|
||||
|
||||
static int16_t *
|
||||
load_wav(const char *filename, int *sample_count)
|
||||
{
|
||||
if ((filename == NULL) || (strlen(filename) == 0))
|
||||
return NULL;
|
||||
|
||||
if (strstr(filename, "..") != NULL)
|
||||
return NULL;
|
||||
|
||||
FILE *f = asset_fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
fdd_log("FDD Audio: Failed to open WAV file: %s\n", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
wav_header_t hdr;
|
||||
if (fread(&hdr, sizeof(hdr), 1, f) != 1) {
|
||||
fdd_log("FDD Audio: Failed to read WAV header from: %s\n", filename);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (memcmp(hdr.riff, "RIFF", 4) || memcmp(hdr.wave, "WAVE", 4) || memcmp(hdr.fmt, "fmt ", 4) || memcmp(hdr.data, "data", 4)) {
|
||||
fdd_log("FDD Audio: Invalid WAV format in file: %s\n", filename);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Accept both mono and stereo, 16-bit PCM */
|
||||
if (hdr.audio_format != 1 || hdr.bits_per_sample != 16 || (hdr.num_channels != 1 && hdr.num_channels != 2)) {
|
||||
fdd_log("FDD Audio: Unsupported WAV format in %s (format: %d, bits: %d, channels: %d)\n",
|
||||
filename, hdr.audio_format, hdr.bits_per_sample, hdr.num_channels);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int input_samples = hdr.data_size / 2; /* 2 bytes per sample */
|
||||
int16_t *input_data = malloc(hdr.data_size);
|
||||
if (!input_data) {
|
||||
fdd_log("FDD Audio: Failed to allocate memory for WAV data: %s\n", filename);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(input_data, 1, hdr.data_size, f) != hdr.data_size) {
|
||||
fdd_log("FDD Audio: Failed to read WAV data from: %s\n", filename);
|
||||
free(input_data);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
int16_t *output_data;
|
||||
int output_samples;
|
||||
|
||||
if (hdr.num_channels == 1) {
|
||||
/* Convert mono to stereo */
|
||||
output_samples = input_samples; /* Number of stereo sample pairs */
|
||||
output_data = malloc(input_samples * 2 * sizeof(int16_t)); /* Allocate for stereo */
|
||||
if (!output_data) {
|
||||
fdd_log("FDD Audio: Failed to allocate stereo conversion buffer for: %s\n", filename);
|
||||
free(input_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert mono to stereo by duplicating each sample */
|
||||
for (int i = 0; i < input_samples; i++) {
|
||||
output_data[i * 2] = input_data[i]; /* Left channel */
|
||||
output_data[i * 2 + 1] = input_data[i]; /* Right channel */
|
||||
}
|
||||
|
||||
free(input_data);
|
||||
fdd_log("FDD Audio: Loaded %s (mono->stereo, %d samples)\n", filename, output_samples);
|
||||
} else {
|
||||
/* Already stereo */
|
||||
output_data = input_data;
|
||||
output_samples = input_samples / 2; /* Number of stereo sample pairs */
|
||||
fdd_log("FDD Audio: Loaded %s (stereo, %d samples)\n", filename, output_samples);
|
||||
}
|
||||
|
||||
if (sample_count)
|
||||
*sample_count = output_samples;
|
||||
|
||||
return output_data;
|
||||
}
|
||||
|
||||
void
|
||||
fdd_audio_callback(int16_t *buffer, int length)
|
||||
{
|
||||
|
||||
@@ -71,23 +71,6 @@ typedef enum {
|
||||
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;
|
||||
|
||||
/* Audio sample structure */
|
||||
typedef struct {
|
||||
char filename[512];
|
||||
|
||||
@@ -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;
|
||||
@@ -233,6 +234,7 @@ extern double hdd_seek_get_time(hard_disk_t *hdd, uint32_t dst_addr, uint8_
|
||||
int hdd_preset_get_num(void);
|
||||
const char *hdd_preset_getname(int preset);
|
||||
extern const char *hdd_preset_get_internal_name(int preset);
|
||||
extern uint32_t hdd_preset_get_rpm(int preset);
|
||||
extern int hdd_preset_get_from_internal_name(char *s);
|
||||
extern void hdd_preset_apply(int hdd_id);
|
||||
|
||||
|
||||
83
src/include/86box/hdd_audio.h
Normal file
83
src/include/86box/hdd_audio.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/* Spindle motor states */
|
||||
typedef enum {
|
||||
HDD_SPINDLE_STOPPED = 0,
|
||||
HDD_SPINDLE_STARTING,
|
||||
HDD_SPINDLE_RUNNING,
|
||||
HDD_SPINDLE_STOPPING
|
||||
} hdd_spindle_state_t;
|
||||
|
||||
/* 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];
|
||||
uint32_t rpm;
|
||||
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 uint32_t hdd_audio_get_profile_rpm(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);
|
||||
|
||||
/* Per-drive spindle control */
|
||||
extern void hdd_audio_spinup_drive(int hdd_index);
|
||||
extern void hdd_audio_spindown_drive(int hdd_index);
|
||||
extern hdd_spindle_state_t hdd_audio_get_drive_spindle_state(int hdd_index);
|
||||
|
||||
/* Legacy functions for backward compatibility - operate on all drives */
|
||||
extern void hdd_audio_spinup(void);
|
||||
extern void hdd_audio_spindown(void);
|
||||
extern hdd_spindle_state_t hdd_audio_get_spindle_state(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* EMU_HDD_AUDIO_H */
|
||||
@@ -103,6 +103,9 @@ extern void sound_cd_thread_reset(void);
|
||||
extern void sound_fdd_thread_init(void);
|
||||
extern void sound_fdd_thread_end(void);
|
||||
|
||||
extern void sound_hdd_thread_init(void);
|
||||
extern void sound_hdd_thread_end(void);
|
||||
|
||||
extern void closeal(void);
|
||||
extern void inital(void);
|
||||
extern void givealbuffer(const void *buf);
|
||||
@@ -110,6 +113,7 @@ 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);
|
||||
extern void givealbuffer_hdd(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
|
||||
|
||||
28
src/include/86box/sound_util.h
Normal file
28
src/include/86box/sound_util.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef SOUND_UTIL_H
|
||||
#define SOUND_UTIL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* WAV file header structure */
|
||||
typedef struct wav_header_t {
|
||||
char riff[4];
|
||||
uint32_t file_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;
|
||||
|
||||
/* Load a WAV file and return stereo 16-bit samples
|
||||
* Returns allocated buffer (caller must free) or NULL on error
|
||||
* sample_count receives the number of stereo sample pairs */
|
||||
int16_t *sound_load_wav(const char *filename, int *sample_count);
|
||||
|
||||
#endif /* SOUND_UTIL_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,7 @@ SettingsHarddisks::SettingsHarddisks(QWidget *parent)
|
||||
onTableRowChanged(QModelIndex());
|
||||
|
||||
Harddrives::populateBuses(ui->comboBoxBus->model());
|
||||
|
||||
on_comboBoxBus_currentIndexChanged(0);
|
||||
}
|
||||
|
||||
@@ -162,6 +171,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);
|
||||
@@ -269,6 +279,53 @@ SettingsHarddisks::on_comboBoxSpeed_currentIndexChanged(int index)
|
||||
auto col = idx.siblingAtColumn(ColumnSpeed);
|
||||
model->setData(col, ui->comboBoxSpeed->currentData(Qt::UserRole), Qt::UserRole);
|
||||
model->setData(col, QObject::tr(hdd_preset_getname(ui->comboBoxSpeed->currentData(Qt::UserRole).toUInt())));
|
||||
|
||||
/* Reset audio profile to None when speed/model changes */
|
||||
auto audioCol = idx.siblingAtColumn(ColumnAudio);
|
||||
model->setData(audioCol, 0, Qt::UserRole);
|
||||
model->setData(audioCol, QObject::tr("None"));
|
||||
}
|
||||
|
||||
/* Repopulate audio profiles based on the selected speed preset's RPM */
|
||||
populateAudioProfiles();
|
||||
}
|
||||
|
||||
void
|
||||
SettingsHarddisks::populateAudioProfiles()
|
||||
{
|
||||
ui->comboBoxAudio->clear();
|
||||
|
||||
/* Get RPM from currently selected speed preset */
|
||||
uint32_t target_rpm = hdd_preset_get_rpm(ui->comboBoxSpeed->currentData(Qt::UserRole).toUInt());
|
||||
|
||||
/* Populate audio profile combobox with matching RPM profiles */
|
||||
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);
|
||||
uint32_t profile_rpm = hdd_audio_get_profile_rpm(i);
|
||||
|
||||
/* Include profile if it has no RPM set (0) or matches target RPM */
|
||||
if (name && (profile_rpm == 0 || profile_rpm == target_rpm)) {
|
||||
ui->comboBoxAudio->addItem(name, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +336,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 +362,14 @@ SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t)
|
||||
if (!match.isEmpty())
|
||||
ui->comboBoxSpeed->setCurrentIndex(match.first().row());
|
||||
|
||||
/* Populate audio profiles based on selected speed preset's RPM */
|
||||
populateAudioProfiles();
|
||||
|
||||
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();
|
||||
@@ -34,6 +35,7 @@ private slots:
|
||||
private:
|
||||
Ui::SettingsHarddisks *ui;
|
||||
void enableCurrentlySelectedChannel();
|
||||
void populateAudioProfiles();
|
||||
bool buschangeinprogress = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -54,6 +54,7 @@ add_library(snd OBJECT
|
||||
snd_opl_esfm.c
|
||||
snd_ymf701.c
|
||||
snd_ymf71x.c
|
||||
sound_util.c
|
||||
)
|
||||
|
||||
# TODO: Should platform-specific audio driver be here?
|
||||
|
||||
@@ -37,17 +37,17 @@
|
||||
#define I_WT 2
|
||||
#define I_CD 3
|
||||
#define I_FDD 4
|
||||
#define I_MIDI 5
|
||||
#define I_HDD 5
|
||||
#define I_MIDI 6
|
||||
|
||||
static int audio[6] = {-1, -1, -1, -1, -1, -1};
|
||||
static int audio[7] = {-1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
#ifdef USE_NEW_API
|
||||
static struct audio_swpar info[5];
|
||||
static struct audio_swpar info[7];
|
||||
#else
|
||||
static audio_info_t info[6];
|
||||
static audio_info_t info[7];
|
||||
#endif
|
||||
static int freqs[6] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, 0};
|
||||
|
||||
static int freqs[7] = {SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, SOUND_FREQ, 0};
|
||||
void
|
||||
closeal(void)
|
||||
{
|
||||
@@ -173,6 +173,12 @@ givealbuffer_fdd(const void *buf, const uint32_t size)
|
||||
givealbuffer_common(buf, I_FDD, (int) size);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_hdd(const void *buf, const uint32_t size)
|
||||
{
|
||||
givealbuffer_common(buf, I_HDD, (int) size);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_midi(const void *buf, const uint32_t size)
|
||||
{
|
||||
|
||||
@@ -40,15 +40,17 @@
|
||||
#define I_WT 2
|
||||
#define I_CD 3
|
||||
#define I_FDD 4
|
||||
#define I_MIDI 5
|
||||
#define I_HDD 5
|
||||
#define I_MIDI 6
|
||||
|
||||
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_hdd[4]; /* front and back buffers */
|
||||
ALuint buffers_midi[4]; /* front and back buffers */
|
||||
static ALuint source[6]; /* audio source - CHANGED FROM 5 TO 6 */
|
||||
static ALuint source[7]; /* audio sources */
|
||||
|
||||
static int midi_freq = 44100;
|
||||
static int midi_buf_size = 4410;
|
||||
@@ -105,9 +107,10 @@ closeal(void)
|
||||
alSourceStopv(sources, source);
|
||||
alDeleteSources(sources, source);
|
||||
|
||||
if (sources >= 6)
|
||||
if (sources >= 7)
|
||||
alDeleteBuffers(4, buffers_midi);
|
||||
alDeleteBuffers(4, buffers_fdd);
|
||||
alDeleteBuffers(4, buffers_hdd);
|
||||
alDeleteBuffers(4, buffers_cd);
|
||||
alDeleteBuffers(4, buffers_music);
|
||||
alDeleteBuffers(4, buffers);
|
||||
@@ -126,12 +129,14 @@ inital(void)
|
||||
float *cd_buf = NULL;
|
||||
float *midi_buf = NULL;
|
||||
float *fdd_buf = NULL;
|
||||
float *hdd_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;
|
||||
int16_t *hdd_buf_int16 = NULL;
|
||||
|
||||
int init_midi = 0;
|
||||
|
||||
@@ -146,13 +151,14 @@ inital(void)
|
||||
init_midi = 1; /* If the device is neither none, nor system MIDI, initialize the
|
||||
MIDI buffer and source, otherwise, do not. */
|
||||
|
||||
sources = 5 + !!init_midi;
|
||||
sources = 6 + !!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));
|
||||
hdd_buf = (float *) calloc((BUFLEN << 1), sizeof(float));
|
||||
if (init_midi)
|
||||
midi_buf = (float *) calloc(midi_buf_size, sizeof(float));
|
||||
} else {
|
||||
@@ -161,6 +167,7 @@ inital(void)
|
||||
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));
|
||||
hdd_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));
|
||||
}
|
||||
@@ -168,18 +175,17 @@ inital(void)
|
||||
alGenBuffers(4, buffers);
|
||||
alGenBuffers(4, buffers_cd);
|
||||
alGenBuffers(4, buffers_fdd);
|
||||
alGenBuffers(4, buffers_hdd);
|
||||
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);
|
||||
|
||||
// Create sources: 0=main, 1=music, 2=wt, 3=cd, 4=fdd, 5=hdd, 6=midi(optional)
|
||||
if (init_midi)
|
||||
alGenSources(5, source);
|
||||
alGenSources(7, source);
|
||||
else
|
||||
alGenSources(4, source);
|
||||
alGenSources(6, source);
|
||||
|
||||
alSource3f(source[I_NORMAL], AL_POSITION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(source[I_NORMAL], AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
@@ -210,7 +216,13 @@ inital(void)
|
||||
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);
|
||||
|
||||
|
||||
alSource3f(source[I_HDD], AL_POSITION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(source[I_HDD], AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(source[I_HDD], AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSourcef(source[I_HDD], AL_ROLLOFF_FACTOR, 0.0f);
|
||||
alSourcei(source[I_HDD], 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);
|
||||
@@ -225,6 +237,7 @@ inital(void)
|
||||
memset(music_buf, 0, MUSICBUFLEN * 2 * sizeof(float));
|
||||
memset(wt_buf, 0, WTBUFLEN * 2 * sizeof(float));
|
||||
memset(fdd_buf, 0, BUFLEN * 2 * sizeof(float));
|
||||
memset(hdd_buf, 0, BUFLEN * 2 * sizeof(float));
|
||||
if (init_midi)
|
||||
memset(midi_buf, 0, midi_buf_size * sizeof(float));
|
||||
} else {
|
||||
@@ -233,6 +246,7 @@ inital(void)
|
||||
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));
|
||||
memset(hdd_buf_int16, 0, BUFLEN * 2 * sizeof(int16_t));
|
||||
if (init_midi)
|
||||
memset(midi_buf_int16, 0, midi_buf_size * sizeof(int16_t));
|
||||
}
|
||||
@@ -244,6 +258,7 @@ inital(void)
|
||||
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);
|
||||
alBufferData(buffers_hdd[c], AL_FORMAT_STEREO_FLOAT32, hdd_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 {
|
||||
@@ -252,6 +267,7 @@ inital(void)
|
||||
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);
|
||||
alBufferData(buffers_hdd[c], AL_FORMAT_STEREO16, hdd_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);
|
||||
}
|
||||
@@ -262,6 +278,7 @@ inital(void)
|
||||
alSourceQueueBuffers(source[I_WT], 4, buffers_wt);
|
||||
alSourceQueueBuffers(source[I_CD], 4, buffers_cd);
|
||||
alSourceQueueBuffers(source[I_FDD], 4, buffers_fdd);
|
||||
alSourceQueueBuffers(source[I_HDD], 4, buffers_hdd);
|
||||
if (init_midi)
|
||||
alSourceQueueBuffers(source[I_MIDI], 4, buffers_midi);
|
||||
alSourcePlay(source[I_NORMAL]);
|
||||
@@ -269,6 +286,7 @@ inital(void)
|
||||
alSourcePlay(source[I_WT]);
|
||||
alSourcePlay(source[I_CD]);
|
||||
alSourcePlay(source[I_FDD]);
|
||||
alSourcePlay(source[I_HDD]);
|
||||
if (init_midi)
|
||||
alSourcePlay(source[I_MIDI]);
|
||||
|
||||
@@ -280,6 +298,7 @@ inital(void)
|
||||
free(music_buf);
|
||||
free(buf);
|
||||
free(fdd_buf);
|
||||
free(hdd_buf);
|
||||
} else {
|
||||
if (init_midi)
|
||||
free(midi_buf_int16);
|
||||
@@ -288,6 +307,7 @@ inital(void)
|
||||
free(music_buf_int16);
|
||||
free(buf_int16);
|
||||
free(fdd_buf_int16);
|
||||
free(hdd_buf_int16);
|
||||
}
|
||||
|
||||
initialized = 1;
|
||||
@@ -328,35 +348,41 @@ givealbuffer_common(const void *buf, const uint8_t src, const int size, const in
|
||||
void
|
||||
givealbuffer(const void *buf)
|
||||
{
|
||||
givealbuffer_common(buf, 0, BUFLEN << 1, FREQ);
|
||||
givealbuffer_common(buf, I_NORMAL, BUFLEN << 1, FREQ);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_music(const void *buf)
|
||||
{
|
||||
givealbuffer_common(buf, 1, MUSICBUFLEN << 1, MUSIC_FREQ);
|
||||
givealbuffer_common(buf, I_MUSIC, MUSICBUFLEN << 1, MUSIC_FREQ);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_wt(const void *buf)
|
||||
{
|
||||
givealbuffer_common(buf, 2, WTBUFLEN << 1, WT_FREQ);
|
||||
givealbuffer_common(buf, I_WT, WTBUFLEN << 1, WT_FREQ);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_cd(const void *buf)
|
||||
{
|
||||
givealbuffer_common(buf, 3, CD_BUFLEN << 1, CD_FREQ);
|
||||
givealbuffer_common(buf, I_CD, CD_BUFLEN << 1, CD_FREQ);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_midi(const void *buf, const uint32_t size)
|
||||
{
|
||||
givealbuffer_common(buf, 5, (int) size, midi_freq);
|
||||
givealbuffer_common(buf, I_MIDI, (int) size, midi_freq);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_fdd(const void *buf, const uint32_t size)
|
||||
{
|
||||
givealbuffer_common(buf, 4, (int) size, FREQ);
|
||||
givealbuffer_common(buf, I_FDD, (int) size, FREQ);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_hdd(const void *buf, const uint32_t size)
|
||||
{
|
||||
givealbuffer_common(buf, I_HDD, (int) size, FREQ);
|
||||
}
|
||||
@@ -30,11 +30,11 @@
|
||||
#define I_CD 3
|
||||
#define I_MIDI 4
|
||||
#define I_FDD 5
|
||||
#define I_HDD 6
|
||||
|
||||
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 };
|
||||
|
||||
static struct sio_hdl* audio[7] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
|
||||
static struct sio_par info[7];
|
||||
static int freqs[7] = { SOUND_FREQ, MUSIC_FREQ, WT_FREQ, CD_FREQ, SOUND_FREQ, SOUND_FREQ, 0 };
|
||||
void
|
||||
closeal(void)
|
||||
{
|
||||
@@ -153,6 +153,12 @@ givealbuffer_fdd(const void *buf, const uint32_t size)
|
||||
{
|
||||
givealbuffer_common(buf, I_FDD, (int) size);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_hdd(const void *buf, const uint32_t size)
|
||||
{
|
||||
givealbuffer_common(buf, I_HDD, (int) size);
|
||||
}
|
||||
|
||||
void
|
||||
al_set_midi(const int freq, UNUSED(const int buf_size))
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <86box/snd_mpu401.h>
|
||||
#include <86box/sound.h>
|
||||
#include <86box/fdd_audio.h>
|
||||
#include <86box/hdd_audio.h>
|
||||
|
||||
typedef struct {
|
||||
const device_t *device;
|
||||
@@ -96,6 +97,12 @@ static event_t *sound_fdd_start_event;
|
||||
static volatile int fddaudioon = 0;
|
||||
static int fdd_thread_enable = 0;
|
||||
|
||||
static thread_t *sound_hdd_thread_h;
|
||||
static event_t *sound_hdd_event;
|
||||
static event_t *sound_hdd_start_event;
|
||||
static volatile int hddaudioon = 0;
|
||||
static int hdd_thread_enable = 0;
|
||||
|
||||
static void (*filter_cd_audio)(int channel, double *buffer, void *priv) = NULL;
|
||||
static void *filter_cd_audio_p = NULL;
|
||||
|
||||
@@ -614,6 +621,10 @@ sound_poll(UNUSED(void *priv))
|
||||
if (fdd_thread_enable) {
|
||||
thread_set_event(sound_fdd_event);
|
||||
}
|
||||
|
||||
if (hdd_thread_enable) {
|
||||
thread_set_event(sound_hdd_event);
|
||||
}
|
||||
sound_pos_global = 0;
|
||||
}
|
||||
}
|
||||
@@ -857,3 +868,59 @@ sound_fdd_thread_end(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sound_hdd_thread(UNUSED(void *param))
|
||||
{
|
||||
thread_set_event(sound_hdd_start_event);
|
||||
while (hddaudioon) {
|
||||
thread_wait_event(sound_hdd_event, -1);
|
||||
thread_reset_event(sound_hdd_event);
|
||||
|
||||
if (!hddaudioon)
|
||||
break;
|
||||
|
||||
static float hdd_float_buffer[SOUNDBUFLEN * 2];
|
||||
memset(hdd_float_buffer, 0, sizeof(hdd_float_buffer));
|
||||
hdd_audio_callback((int16_t*)hdd_float_buffer, SOUNDBUFLEN * 2);
|
||||
givealbuffer_hdd(hdd_float_buffer, SOUNDBUFLEN * 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_hdd_thread_init(void)
|
||||
{
|
||||
if (!hddaudioon) {
|
||||
hddaudioon = 1;
|
||||
hdd_thread_enable = 1;
|
||||
sound_hdd_start_event = thread_create_event();
|
||||
sound_hdd_event = thread_create_event();
|
||||
sound_hdd_thread_h = thread_create(sound_hdd_thread, NULL);
|
||||
|
||||
thread_wait_event(sound_hdd_start_event, -1);
|
||||
thread_reset_event(sound_hdd_start_event);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sound_hdd_thread_end(void)
|
||||
{
|
||||
if (hddaudioon) {
|
||||
hddaudioon = 0;
|
||||
hdd_thread_enable = 0;
|
||||
thread_set_event(sound_hdd_event);
|
||||
thread_wait(sound_hdd_thread_h);
|
||||
|
||||
if (sound_hdd_event) {
|
||||
thread_destroy_event(sound_hdd_event);
|
||||
sound_hdd_event = NULL;
|
||||
}
|
||||
|
||||
sound_hdd_thread_h = NULL;
|
||||
if (sound_hdd_start_event) {
|
||||
thread_destroy_event(sound_hdd_start_event);
|
||||
sound_hdd_start_event = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
85
src/sound/sound_util.c
Normal file
85
src/sound/sound_util.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <86box/86box.h>
|
||||
#include <86box/mem.h>
|
||||
#include <86box/rom.h>
|
||||
#include <86box/plat.h>
|
||||
#include <86box/sound_util.h>
|
||||
|
||||
int16_t *
|
||||
sound_load_wav(const char *filename, int *sample_count)
|
||||
{
|
||||
if ((filename == NULL) || (strlen(filename) == 0))
|
||||
return NULL;
|
||||
|
||||
if (strstr(filename, "..") != NULL)
|
||||
return NULL;
|
||||
|
||||
FILE *f = asset_fopen(filename, "rb");
|
||||
if (f == NULL)
|
||||
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;
|
||||
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;
|
||||
output_data = malloc(input_samples * 2 * sizeof(int16_t));
|
||||
if (!output_data) {
|
||||
free(input_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < input_samples; i++) {
|
||||
output_data[i * 2] = input_data[i];
|
||||
output_data[i * 2 + 1] = input_data[i];
|
||||
}
|
||||
|
||||
free(input_data);
|
||||
} else {
|
||||
output_data = input_data;
|
||||
output_samples = input_samples / 2;
|
||||
}
|
||||
|
||||
if (sample_count)
|
||||
*sample_count = output_samples;
|
||||
|
||||
return output_data;
|
||||
}
|
||||
@@ -54,6 +54,7 @@ static IXAudio2SourceVoice *srcvoicewt = NULL;
|
||||
static IXAudio2SourceVoice *srcvoicemidi = NULL;
|
||||
static IXAudio2SourceVoice *srcvoicecd = NULL;
|
||||
static IXAudio2SourceVoice *srcvoicefdd = NULL;
|
||||
static IXAudio2SourceVoice *srcvoicehdd = NULL;
|
||||
|
||||
#define FREQ SOUND_FREQ
|
||||
#define BUFLEN SOUNDBUFLEN
|
||||
@@ -184,6 +185,7 @@ inital(void)
|
||||
fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
|
||||
|
||||
(void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicefdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL);
|
||||
(void) IXAudio2_CreateSourceVoice(xaudio2, &srcvoicehdd, &fmt, 0, 2.0f, &callbacks, NULL, NULL);
|
||||
|
||||
(void) IXAudio2SourceVoice_SetVolume(srcvoice, 1, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_Start(srcvoice, 0, XAUDIO2_COMMIT_NOW);
|
||||
@@ -191,6 +193,7 @@ inital(void)
|
||||
(void) IXAudio2SourceVoice_Start(srcvoicemusic, 0, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_Start(srcvoicewt, 0, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_Start(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_Start(srcvoicehdd, 0, XAUDIO2_COMMIT_NOW);
|
||||
|
||||
const char *mdn = midi_out_device_get_internal_name(midi_output_device_current);
|
||||
|
||||
@@ -223,6 +226,8 @@ closeal(void)
|
||||
(void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicecd);
|
||||
(void) IXAudio2SourceVoice_Stop(srcvoicefdd, 0, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicefdd);
|
||||
(void) IXAudio2SourceVoice_Stop(srcvoicehdd, 0, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicehdd);
|
||||
if (srcvoicemidi) {
|
||||
(void) IXAudio2SourceVoice_Stop(srcvoicemidi, 0, XAUDIO2_COMMIT_NOW);
|
||||
(void) IXAudio2SourceVoice_FlushSourceBuffers(srcvoicemidi);
|
||||
@@ -231,6 +236,7 @@ closeal(void)
|
||||
IXAudio2SourceVoice_DestroyVoice(srcvoicewt);
|
||||
IXAudio2SourceVoice_DestroyVoice(srcvoicecd);
|
||||
IXAudio2SourceVoice_DestroyVoice(srcvoicefdd);
|
||||
IXAudio2SourceVoice_DestroyVoice(srcvoicehdd);
|
||||
IXAudio2SourceVoice_DestroyVoice(srcvoicemusic);
|
||||
IXAudio2SourceVoice_DestroyVoice(srcvoice);
|
||||
IXAudio2MasteringVoice_DestroyVoice(mastervoice);
|
||||
@@ -239,6 +245,7 @@ closeal(void)
|
||||
srcvoicecd = NULL;
|
||||
srcvoicemidi = NULL;
|
||||
srcvoicefdd = NULL;
|
||||
srcvoicehdd = NULL;
|
||||
mastervoice = NULL;
|
||||
xaudio2 = NULL;
|
||||
|
||||
@@ -312,6 +319,18 @@ givealbuffer_fdd(const void *buf, const uint32_t size)
|
||||
givealbuffer_common(buf, srcvoicefdd, size);
|
||||
}
|
||||
|
||||
void
|
||||
givealbuffer_hdd(const void *buf, const uint32_t size)
|
||||
{
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
if (!srcvoicefdd)
|
||||
return;
|
||||
|
||||
givealbuffer_common(buf, srcvoicehdd, size);
|
||||
}
|
||||
|
||||
void
|
||||
al_set_midi(const int freq, const int buf_size)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user