From dea461790667378fb8060fdc0f2be61d7da275dc Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Tue, 28 Apr 2026 20:24:15 +0200 Subject: Floppy sound (#15072) * floppy_sound: Allows definition of custom sound lists and fixes Amiga pitch glitches in floppy sound. --- src/devices/imagedev/floppy.cpp | 758 +++++++++++++++++++++++++++------------- src/devices/imagedev/floppy.h | 239 ++++++++++--- 2 files changed, 711 insertions(+), 286 deletions(-) diff --git a/src/devices/imagedev/floppy.cpp b/src/devices/imagedev/floppy.cpp index e646353be60..00614095261 100644 --- a/src/devices/imagedev/floppy.cpp +++ b/src/devices/imagedev/floppy.cpp @@ -36,17 +36,19 @@ #include -/* - Debugging flags. Set to 0 or 1. -*/ +// Some debug output -// Show step operation -#define TRACE_STEP 0 -#define TRACE_AUDIO 0 +#define LOG_STEP (1U << 1) +#define LOG_TRACK (1U << 2) +#define LOG_SND (1U << 3) +#define LOG_SND_CONFIG (1U << 4) +#define LOG_SND_DETAIL (1U << 5) +#define LOG_MACDRIVE (1U << 6) +#define VERBOSE ( LOG_SND_CONFIG ) -#define PITCH_SEEK_SAMPLES 1 +#include "logmacro.h" -#define FLOPSND_TAG "floppysound" +#define FLOPSND_TAG "flopsnd" // device type definition DEFINE_DEVICE_TYPE(FLOPPY_CONNECTOR, floppy_connector, "floppy_connector", "Floppy drive connector abstraction") @@ -218,7 +220,8 @@ floppy_connector::floppy_connector(const machine_config &mconfig, const char *ta device_t(mconfig, FLOPPY_CONNECTOR, tag, owner, clock), device_slot_interface(mconfig, *this), formats(nullptr), - m_enable_sound(false), + m_use_sound(false), + m_samples(nullptr), m_sectoring_type(floppy_image::SOFT) { } @@ -227,6 +230,26 @@ floppy_connector::~floppy_connector() { } +//------------------------------------------- +// Activate floppy sounds with default lists +//------------------------------------------- + +void floppy_connector::enable_sound(bool use_sound) +{ + m_use_sound = use_sound; + if (use_sound) enable_sound(nullptr); +} + +//------------------------------------------------------ +// Activate floppy sounds with lists for various formats +//------------------------------------------------------ + +void floppy_connector::enable_sound(floppy_sound_samples *samples) +{ + m_samples = samples; + m_use_sound = true; +} + void floppy_connector::device_start() { } @@ -237,7 +260,6 @@ void floppy_connector::device_config_complete() if(dev) { dev->set_formats(formats); - dev->enable_sound(m_enable_sound); dev->set_sectoring_type(m_sectoring_type); } } @@ -277,7 +299,7 @@ floppy_image_device::floppy_image_device(const machine_config &mconfig, device_t m_track_dirty(false), m_ready_counter(0), m_make_sound(false), - m_sound_out(nullptr) + m_sound_out(*this, FLOPSND_TAG) { m_extension_list[0] = '\0'; } @@ -514,7 +536,6 @@ const software_list_loader &floppy_image_device::get_software_list_loader() cons return image_software_list_loader::instance(); } - //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- @@ -543,7 +564,12 @@ void floppy_image_device::device_start() m_ready_counter = 0; m_phases = 0; - if (m_make_sound) m_sound_out = subdevice(FLOPSND_TAG); + floppy_connector *conn = dynamic_cast(device().owner()); + if (conn != nullptr) // just in case that the floppy connects to something else + { + m_sound_out->set_samples(conn->get_samples(), m_form_factor); + m_make_sound = conn->use_sound(); + } save_item(NAME(m_dir)); save_item(NAME(m_stp)); @@ -718,6 +744,8 @@ void floppy_image_device::call_unload() mon_w(1); } + if (m_make_sound) m_sound_out->unload(); + set_ready(true); } @@ -861,7 +889,6 @@ TIMER_CALLBACK_MEMBER(floppy_image_device::index_resync) if(m_idx && m_ready) { m_ready_counter--; if(!m_ready_counter) { - // logerror("Drive spun up\n"); set_ready(false); } } @@ -923,11 +950,6 @@ void floppy_image_device::track_changed() void floppy_image_device::stp_w(int state) { - // Before spin-up is done, ignore step pulses - // TODO: There are reports about drives supporting step operation with - // stopped spindle. Need to check that on real drives. - // if (m_ready_counter > 0) return; - if ( m_stp != state ) { cache_clear(); m_stp = state; @@ -940,10 +962,8 @@ void floppy_image_device::stp_w(int state) } if(ocyl != m_cyl) { - if (TRACE_STEP) logerror("track %d\n", m_cyl); - // Do we want a stepper sound? - // We plan for 5 zones with possibly specific sounds - if (m_make_sound) m_sound_out->step(m_cyl*5/m_tracks); + LOGMASKED(LOG_TRACK, "track %d\n", m_cyl); + if (m_make_sound) m_sound_out->step(m_cyl); track_changed(); } /* Update disk detection if applicable */ @@ -994,7 +1014,7 @@ void floppy_image_device::seek_phase_w(int _phases) cache_clear(); if(next_pos != cur_pos) { - if (TRACE_STEP) logerror("track %d.%d\n", m_cyl, m_subcyl); + LOGMASKED(LOG_STEP, "track %d.%d\n", m_cyl, m_subcyl); if (m_make_sound) m_sound_out->step(m_subcyl); } @@ -1334,148 +1354,367 @@ uint32_t floppy_image_device::get_variant() const return m_image ? m_image->get_variant() : 0; } -//=================================================================== -// Floppy sound -// -// In order to enable floppy sound you must call -// enable_sound(true) -// and you must put audio samples (44100Hz, mono) with names as -// shown in floppy_sample_names into the directory samples/floppy -// Sound will be disabled when these samples are missing. -// -// MZ, Aug 2015 -//=================================================================== +/* =================================================================== + Floppy sound + For usage description see floppy.h + + Some implementation details: + + Not all spin_kinds need to be defined. See the array replace_sample in + find_spin for the rules by which samples are used when the proper ones + are not available. The ultimate default is SPIN_LOADED, so this sample + is mandatory. + + The fast repetition of a step sample does not yield a seek sound (a sequence + of steps). Hence, the implementation must find out whether this is a single + step or a seek); in the first case, a step sample is played, while in + the second, a seek sample must be played. For this, it checks whether + there is a new step during the playback of the step sample. In this case, + a seek is assumed. + + The seek samples are chosen by the detected step rate n (in milliseconds). + The seek sample whose max_rate is minimally higher than n is taken, and the + playback is pitched up or down related to the sample's actual rate. -enum + This works well with the majority of the system, except for the Amiga. The + Amiga seems to have direct control on the stepper motor in the drive so that + the step rates vary widely. In order to avoid switching between different + sample files all the time, a new rate is assumed only if it is more than + 10% off the current rate. This may make it difficult to reproduce music + with the floppy hardware ("Floppytron"), but we'll probably have to go for + a different approach in that case anyway. + + Step sounds are played when the interval between them is long enough, so no + seek sound would be produced. If only one step sample shall be used, + the range should be set as (0,99). Otherwise, the step sample is played + whose range contains the current track. + +=================================================================== */ + +floppy_sound_samples::floppy_sound_samples() : + m_current_form_factor(floppy_image::FF_UNKNOWN), + m_current_dir(nullptr) { - QUIET=-1, - SPIN_START_EMPTY=0, - SPIN_START_LOADED, - SPIN_EMPTY, - SPIN_LOADED, - SPIN_END }; -enum +void floppy_sound_samples::select(int form_factor) { - STEP_SINGLE=0, - STEP_SEEK2, - STEP_SEEK6, - STEP_SEEK12, - STEP_SEEK20 -}; + bool found = false; + + while (!found) + { + int index = 0; // index of the sample in the sample name list + + for (floppy_sound_entry& entry : m_fulllist) + { + if (entry.form_factor == form_factor && entry.directory != nullptr) + { + if (index == 0) // new list + { + // Create the asterisked first entry for the subdirectory + m_basedir = "*" + std::string(entry.directory); + m_samplenames.push_back(m_basedir.c_str()); + index++; + found = true; + } + entry.index = index++; // keep record of position in the sample list + m_samplenames.push_back(entry.filename); + } + } + + if (!found) + { + // If we don't have 3" samples, try to use 3.5" samples + if (form_factor == floppy_image::FF_3) + form_factor = floppy_image::FF_35; + else + { + // If we don't find 3", 3.5", and 8" samples, try 5.25" + if (form_factor != floppy_image::FF_525) + form_factor = floppy_image::FF_525; + else + { + // If this also fails, don't use sound at all. + form_factor = 0; + break; + } + } + } + } + m_current_form_factor = form_factor; +} + +void floppy_sound_samples::set_form_factor(int form_factor, const char* dir) +{ + m_current_dir = dir; + m_current_form_factor = form_factor; +} + +void floppy_sound_samples::add_spin_sample(const char* filename, int type) +{ + floppy_sound_entry entry; + entry.type = SPIN; + entry.spintype = type; + entry.filename = filename; + entry.form_factor = m_current_form_factor; + entry.directory = m_current_dir; + m_fulllist.push_back(entry); +} + +void floppy_sound_samples::add_step_sample(const char* filename, int mintrack, int maxtrack) +{ + floppy_sound_entry entry; + entry.type = STEP; + entry.mintrack = mintrack; + entry.maxtrack = maxtrack; + entry.filename = filename; + entry.form_factor = m_current_form_factor; + entry.directory = m_current_dir; + m_fulllist.push_back(entry); +} + +void floppy_sound_samples::add_seek_sample(const char* filename, int nominal_rate, int max_rate, int mintrack, int maxtrack) +{ + floppy_sound_entry entry; + entry.type = SEEK; + entry.rate = nominal_rate; + entry.maxrate = max_rate; + entry.filename = filename; + entry.form_factor = m_current_form_factor; + entry.directory = m_current_dir; + m_fulllist.push_back(entry); +} + +const char* const* floppy_sound_samples::get_names() +{ + m_samplenames.push_back(nullptr); + return &m_samplenames[0]; +} /* - Unless labeled "constructed", all samples were recorded from real floppy drives. - The 3.5" floppy drive is a Sony MPF420-1. - The 5.25" floppy drive is a Chinon FZ502. + Find a suitable spinning sound in the list. */ -static const char *const floppy35_sample_names[] = -{ -// Subdirectory - "*floppy", -// Spinning sounds - "35_spin_start_empty", - "35_spin_start_loaded", - "35_spin_empty", - "35_spin_loaded", - "35_spin_end", -// Stepping sounds - "35_step_1_1", -// Seeking sounds - "35_seek_2ms", // constructed - "35_seek_6ms", - "35_seek_12ms", - "35_seek_20ms", - nullptr -}; +int floppy_sound_samples::find_spin(int spintype) const +{ + // If a sample is not available (left), take the one on the right. + // Simple index replacement. + int replace_sample[7] = + { + /* START_EMPTY -> */ SPIN_EMPTY, + /* SPIN_EMPTY -> */ SPIN_LOADED, + /* END_EMPTY -> */ END_LOADED, + /* START_LOADED_INITIAL -> */ START_LOADED, + /* START_LOADED -> */ SPIN_LOADED, + /* SPIN_LOADED -> */ QUIET, + /* END_LOADED -> */ SPIN_LOADED + }; -static const char *const floppy525_sample_names[] = -{ -// Subdirectory - "*floppy", -// Spinning sounds - "525_spin_start_empty", - "525_spin_start_loaded", - "525_spin_empty", - "525_spin_loaded", - "525_spin_end", -// Stepping sounds - "525_step_1_1", -// Seeking sounds - "525_seek_2ms", // unrealistically fast, but needed for 3.5 (constructed) - "525_seek_6ms", - "525_seek_12ms", - "525_seek_20ms", - nullptr -}; + while (spintype != QUIET) + { + for (const floppy_sound_entry& entry : m_fulllist) + { + if (entry.form_factor == m_current_form_factor && + entry.type == SPIN && + entry.spintype == spintype) + return entry.index; // found it + } + // Not found, take another kind (maybe try several times) + spintype = replace_sample[spintype]; + } + return QUIET; // not found +} + +/* + Find a suitable step sample. The samples may be different by track. + In the definition, the range must be specified, where (0, 99) is used for + all tracks (all emulated drives have less than 99 tracks). +*/ +int floppy_sound_samples::find_step(int track) const +{ + for (const floppy_sound_entry& entry : m_fulllist) + { + if (entry.form_factor == m_current_form_factor && + entry.type == STEP && + track >= entry.mintrack && track <= entry.maxtrack) + return entry.index; // found it + } + return QUIET; +} + +/* + Find a suitable seek sample. We allow for a given sample to be played + for a rate that is in some range around that sample, defined in the list. + That is, each seek sample defines its actual rate (e.g. 6 ms) and the + slowest rate that it may be used for (e.g. 8 ms). If the determined rate + is 7 ms, the 6 ms sample will be chosen, and playback will be pitched down + by 6/7 = 0.86. If the rate is 5 ms, playback will be pitched up by 6/5 = 1.2, + unless there is a sample for a faster rate that covers 5 ms. + + If the determined rate is slower than the maximum rate (here, 8 ms), the + next sample will be used for a slower rate (e.g. 10 ms) if available. If + there is no slower rate, -1 is returned. The caller should then use single + step sounds. +*/ +int floppy_sound_samples::find_seek(double rate, int track, double& pitch) const +{ + int index = QUIET; + int maxrate = 100; + + pitch = 1.0; + + for (const floppy_sound_entry& entry : m_fulllist) + { + // Can the sample be used for this track? + if (entry.form_factor == m_current_form_factor && + entry.type == SEEK && + track >= entry.mintrack && + track <= entry.maxtrack) + { + // The rate must not exceed the maxrate of the sample + // Also, if we already found an entry with a lower maxrate, + // skip this one + if ((rate <= entry.maxrate) && (entry.maxrate < maxrate)) + { + index = entry.index; + maxrate = entry.maxrate; + pitch = entry.rate / (double)rate; + } + } + } + return index; +} + +// ================================= floppy_sound_device::floppy_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : samples_device(mconfig, FLOPPYSOUND, tag, owner, clock), m_sound(nullptr), - m_step_base(0), - m_spin_samples(0), - m_step_samples(0), - m_spin_samplepos(0), - m_step_samplepos(0), - m_seek_sound_timeout(0), - m_zones(0), - m_spin_playback_sample(QUIET), - m_step_playback_sample(QUIET), - m_seek_playback_sample(QUIET), + m_samplelist(nullptr), m_motor_on(false), m_with_disk(false), - m_loaded(false), + m_spin_kind(floppy_sound_samples::QUIET), + m_spin_sample(floppy_sound_samples::QUIET), + m_spin_samplepos(0), + m_step_sample(floppy_sound_samples::QUIET), + m_step_samplepos(0), + m_seek_sample(floppy_sound_samples::QUIET), + m_seek_samplepos(0.0), m_seek_pitch(1.0), - m_seek_samplepos(0.0) -{ + m_seek_sound_timeout(0), + m_last_step_time(), + m_firstturn(true), + m_samples_available(false), + m_in_seek(false), + m_step_rate(0.0) +{ + // Set up the default sample list + + // Unless labeled "constructed", all samples were recorded from real floppy drives. + // The 3.5" floppy drive is a Sony MPF420-1. + // The 5.25" floppy drive is a Chinon FZ502. + // "floppy" is the subdirectory in the samples path where the following samples are stored + + m_default_samples.clear(); + m_default_samples.set_form_factor(floppy_image::FF_35, "floppy"); + m_default_samples.add_spin_sample("35_spin_start_empty", floppy_sound_samples::START_EMPTY); + m_default_samples.add_spin_sample("35_spin_start_loaded", floppy_sound_samples::START_LOADED); + m_default_samples.add_spin_sample("35_spin_empty", floppy_sound_samples::SPIN_EMPTY); + m_default_samples.add_spin_sample("35_spin_loaded", floppy_sound_samples::SPIN_LOADED); + m_default_samples.add_spin_sample("35_spin_end", floppy_sound_samples::END_LOADED); + m_default_samples.add_step_sample("35_step_1_1"); + m_default_samples.add_seek_sample("35_seek_2ms", 2, 3); // constructed + m_default_samples.add_seek_sample("35_seek_6ms", 6, 9); + m_default_samples.add_seek_sample("35_seek_12ms", 12, 15); + m_default_samples.add_seek_sample("35_seek_20ms", 20, 50); + + m_default_samples.set_form_factor(floppy_image::FF_525, "floppy"); + m_default_samples.add_spin_sample("525_spin_start_empty", floppy_sound_samples::START_EMPTY); + m_default_samples.add_spin_sample("525_spin_start_loaded", floppy_sound_samples::START_LOADED); + m_default_samples.add_spin_sample("525_spin_empty", floppy_sound_samples::SPIN_EMPTY); + m_default_samples.add_spin_sample("525_spin_loaded", floppy_sound_samples::SPIN_LOADED); + m_default_samples.add_spin_sample("525_spin_end", floppy_sound_samples::END_LOADED); + m_default_samples.add_step_sample("525_step_1_1"); + m_default_samples.add_seek_sample("525_seek_6ms", 6, 9); + m_default_samples.add_seek_sample("525_seek_12ms", 12, 15); + m_default_samples.add_seek_sample("525_seek_20ms", 20, 50); } void floppy_sound_device::register_for_save_states() { - save_item(NAME(m_step_base)); - save_item(NAME(m_spin_samples)); - save_item(NAME(m_step_samples)); + save_item(NAME(m_motor_on)); + save_item(NAME(m_with_disk)); + save_item(NAME(m_spin_kind)); + save_item(NAME(m_spin_sample)); save_item(NAME(m_spin_samplepos)); + save_item(NAME(m_step_sample)); save_item(NAME(m_step_samplepos)); + save_item(NAME(m_seek_sample)); save_item(NAME(m_seek_samplepos)); - save_item(NAME(m_seek_sound_timeout)); - save_item(NAME(m_zones)); - save_item(NAME(m_spin_playback_sample)); - save_item(NAME(m_step_playback_sample)); - save_item(NAME(m_seek_playback_sample)); - save_item(NAME(m_motor_on)); - save_item(NAME(m_with_disk)); - save_item(NAME(m_loaded)); save_item(NAME(m_seek_pitch)); + save_item(NAME(m_seek_sound_timeout)); + save_item(NAME(m_firstturn)); + save_item(NAME(m_samples_available)); + save_item(NAME(m_in_seek)); + save_item(NAME(m_step_rate)); } void floppy_sound_device::device_start() { - // What kind of drive do we have? - bool is525 = strstr(tag(), "525") != nullptr; - set_samples_names(is525 ? floppy525_sample_names : floppy35_sample_names); - - m_motor_on = false; - - // Offsets in the sample collection - m_spin_samples = 5; - m_step_base = 5; - m_step_samples = 1; - m_zones = 1; // > 1 needs more than one step sample - - m_spin_samplepos = m_step_samplepos = m_seek_samplepos = 0; - m_spin_playback_sample = m_step_playback_sample = QUIET; + m_samples_available = false; - // Read audio samples. The samples are stored in the list m_samples. - m_loaded = load_samples(); + // Set up floppy sound samples (for those systems which use the sound feature) + if (m_samplelist != nullptr) + { + // Only load if there is a matching form factor in the list + if (m_samplelist->get_assumed_form_factor() != 0) + { + set_samples_names(m_samplelist->get_names()); + LOGMASKED(LOG_SND_CONFIG, "Loading custom samples\n"); + // Try to read the audio samples. + m_samples_available = load_samples(); + } + } - // If we don't have all samples, don't allocate a stream or access sample data. - if (m_loaded) + // Cannot load custom samples, so try the predefined list + if (!m_samples_available) { - m_sound = stream_alloc(0, 1, clock()); // per-floppy stream + // The default list should always have a matching form factor + if (m_default_samples.get_assumed_form_factor() != 0) + { + set_samples_names(m_default_samples.get_names()); + LOGMASKED(LOG_SND_CONFIG, "Loading default samples\n"); + // Try to read the default audio samples + m_samples_available = load_samples(); + m_samplelist = &m_default_samples; + } } + + // If we don't have samples, don't allocate a sound stream + if (m_samples_available) + m_sound = stream_alloc(0, 1, clock()); // per-floppy stream + register_for_save_states(); + + m_motor_on = false; + m_spin_kind = floppy_sound_samples::QUIET; + m_spin_sample = floppy_sound_samples::QUIET; + m_step_sample = floppy_sound_samples::QUIET; + m_spin_samplepos = 0; + m_step_samplepos = 0; + m_seek_samplepos = 0; + m_last_step_time = attotime::zero; + m_in_seek = false; + m_step_rate = 0; + m_firstturn = true; +} + +void floppy_sound_device::set_samples(floppy_sound_samples *samples, int form_factor) +{ + m_samplelist = samples; + if (m_samplelist != nullptr) + m_samplelist->select(form_factor); + + m_default_samples.select(form_factor); } /* @@ -1489,20 +1728,34 @@ void floppy_sound_device::motor(bool running, bool withdisk) { m_sound->update(); // required - if ((m_spin_playback_sample==QUIET || m_spin_playback_sample==SPIN_END) && running) // motor was either off or already spinning down + if ((m_spin_kind==floppy_sound_samples::QUIET + || m_spin_kind==floppy_sound_samples::END_EMPTY + || m_spin_kind==floppy_sound_samples::END_LOADED ) && running) // motor was either off or already spinning down { m_spin_samplepos = 0; - m_spin_playback_sample = withdisk ? SPIN_START_LOADED : SPIN_START_EMPTY; // (re)start the motor sound + // 3.5" floppy disks have a special first turn sound when the + // spindle motor latch meets the central metal hub hole. + m_spin_kind = withdisk? (m_firstturn? floppy_sound_samples::START_LOADED_INITIAL : floppy_sound_samples::START_LOADED) : floppy_sound_samples::START_EMPTY; + m_firstturn = false; } else { // Motor has been running and is turned off now - if ((m_spin_playback_sample == SPIN_EMPTY || m_spin_playback_sample == SPIN_LOADED) && !running) + if ((m_spin_kind == floppy_sound_samples::SPIN_EMPTY || m_spin_kind == floppy_sound_samples::SPIN_LOADED) && !running) { m_spin_samplepos = 0; - m_spin_playback_sample = SPIN_END; // go to spin down sound when loop is finished + m_spin_kind = withdisk? floppy_sound_samples::END_LOADED : floppy_sound_samples::END_EMPTY; // go to spin down sound when loop is finished } } + + int old_sample = m_spin_sample; + m_spin_sample = (m_spin_kind==floppy_sound_samples::QUIET)? floppy_sound_samples::QUIET : m_samplelist->find_spin(m_spin_kind); + + if (m_spin_sample == floppy_sound_samples::QUIET) + LOGMASKED(LOG_SND, "Spin off\n"); + else + if (m_spin_sample != old_sample) + LOGMASKED(LOG_SND, "Spin sample = %d\n", m_spin_sample); } m_motor_on = running; m_with_disk = withdisk; @@ -1510,92 +1763,104 @@ void floppy_sound_device::motor(bool running, bool withdisk) /* Activate the step sound. - The zone parameter should be used to select specific samples for the - current head position (if available). Its value should range from 0 to 4. */ -void floppy_sound_device::step(int zone) +void floppy_sound_device::step(int track) { if (samples_loaded()) { m_sound->update(); // required - // Pick one of the step samples - // TODO: This is only preliminary, need to complete that. - if (zone >= m_zones) zone = m_zones-1; - m_step_playback_sample = (zone * m_step_samples) + (machine().rand() % m_step_samples); + m_step_sample = m_samplelist->find_step(track); + attotime now = machine().time(); + double rate = 0; + rate = (m_last_step_time == attotime::zero)? 0 : (now - m_last_step_time).as_double() * 1000; + m_last_step_time = now; + + bool recalc = false; + + // Cases: + // step, previous step sample completed (step_samplepos == 0) -> new step output + // step, previous step sample not completed (step_samplepos > 0) -> + // not in seek -> determine seek sample, freeze step output + // in seek -> continue with seek sample + // (seek sample timeout is set to twice the step rate) - if (m_step_samplepos > 0) + // If the step rate changed by more than 10%, we have to change the + // seek sample + if (m_step_rate == 0) + { + recalc = true; + m_step_rate = rate; + } + else { - if (m_seek_playback_sample == QUIET) + if (rate > 0 && rate < 200) // safe values { - // The last step sample was not completed; - // we need to find out the step rate - // With a sample rate of 44100 Hz we can calculate the - // rate from the sample position - // 2ms = 88 - // 6ms = 265 - // 12ms = 529 - // 20ms = 882 - - if (m_step_samplepos < 100) + double raterel = (m_step_rate - rate) / m_step_rate; + if (raterel < 0) raterel = -raterel; + if (raterel > 0.10 && m_in_seek) { - // Should only used for 3.5 drives - m_seek_playback_sample = STEP_SEEK2; - m_seek_pitch = 1.0; // don't use a pitch + recalc = true; + LOGMASKED(LOG_SND, "Step rate has changed from %.1f to %.1f ms\n", m_step_rate, rate); } - else + m_step_rate = rate; + } + } + + if (m_step_samplepos > 0 && m_step_rate < 100) // in seek, or transitioning into seek + { + if (recalc || !m_in_seek) + { + LOGMASKED(LOG_SND, "Seeking with rate = %.1f ms\n", m_step_rate); + + int newseek = m_samplelist->find_seek(m_step_rate, track, m_seek_pitch); + if (newseek == floppy_sound_samples::QUIET) { - if (m_step_samplepos < 400) // Use this for 8 ms also - { - m_seek_playback_sample = STEP_SEEK6; - m_seek_pitch = 265.0 / m_step_samplepos; - } - else - { - if (m_step_samplepos < 600) - { - m_seek_playback_sample = STEP_SEEK12; - m_seek_pitch = 529.0 / m_step_samplepos; - } - else - { - if (m_step_samplepos < 1200) - { - m_seek_playback_sample = STEP_SEEK20; - m_seek_pitch = 882.0 / m_step_samplepos; - } - else - // For 30ms and longer we replay the step sound - m_seek_playback_sample = QUIET; - } - } + // Could not find a proper seek sample, i.e. the step + // interval became too long + m_in_seek = false; + // Last step sample was completed, this is not a seek process + m_seek_sample = floppy_sound_samples::QUIET; + m_seek_samplepos = 0; } + else + { + // Start the new seek sound from the beginning (but only if + // we changed it, or we will get ugly sounds in the output) + if (newseek != m_seek_sample) m_seek_samplepos = 0; - // Start the new seek sound from the beginning. - m_seek_samplepos = 0; - } - - // Changing the pitch does not always sound convincing - if (!PITCH_SEEK_SAMPLES) m_seek_pitch = 1; + m_seek_sample = newseek; - if (TRACE_AUDIO) logerror("Seek sample = %d, pitch = %f\n", m_seek_playback_sample, m_seek_pitch); + LOGMASKED(LOG_SND_DETAIL, "Step rate = %.1f ms, seek sample = %d, pitch = %f\n", m_step_rate, m_seek_sample, m_seek_pitch); + } + } // Set the timeout for the seek sound. When it expires, // we assume that the seek process is over, and we'll play the // rest of the step sound. // This will be retriggered with each step pulse. - m_seek_sound_timeout = m_step_samplepos * 2; + m_seek_sound_timeout = (int)(m_step_rate * 2 * 44.1); } else { + LOGMASKED(LOG_SND_DETAIL, "Step sample completed\n"); + m_in_seek = false; // Last step sample was completed, this is not a seek process - m_seek_playback_sample = QUIET; + m_seek_sample = floppy_sound_samples::QUIET; + m_seek_samplepos = 0; } // If we switch to the seek sample, let's keep the position of the // step sample; else reset the step sample position. - if (m_seek_playback_sample == QUIET) + if (m_seek_sample == floppy_sound_samples::QUIET) + { m_step_samplepos = 0; + m_in_seek = false; + } + else + { + m_in_seek = true; + } } } @@ -1610,7 +1875,6 @@ void floppy_sound_device::sound_stream_update(sound_stream &stream) // one sample rate of 44100 for all samples int16_t out; - int m_idx = 0; int sampleend = 0; for (int sampindex = 0; sampindex < stream.samples(); sampindex++) @@ -1618,67 +1882,89 @@ void floppy_sound_device::sound_stream_update(sound_stream &stream) out = 0; // Motor sound - if (m_spin_playback_sample != QUIET) + if (m_spin_sample != floppy_sound_samples::QUIET) { - m_idx = m_spin_playback_sample; - sampleend = m_sample[m_idx].data.size(); - out = m_sample[m_idx].data[m_spin_samplepos++]; + // The samples list starts at 0 with the first entry after DIR, + // so we adjust by -1 + sampleend = m_sample[m_spin_sample-1].data.size(); + out = m_sample[m_spin_sample-1].data[m_spin_samplepos++]; if (m_spin_samplepos >= sampleend) { + LOGMASKED(LOG_SND_DETAIL, "Spin sample %d completed\n", m_spin_sample); // Motor sample has completed - switch (m_spin_playback_sample) + switch (m_spin_kind) { - case SPIN_START_EMPTY: + case floppy_sound_samples::START_EMPTY: // After start, switch to the continued spinning sound - m_spin_playback_sample = SPIN_EMPTY; // move to looping sound + m_spin_kind = floppy_sound_samples::SPIN_EMPTY; // move to looping sound break; - case SPIN_START_LOADED: + case floppy_sound_samples::START_LOADED: + case floppy_sound_samples::START_LOADED_INITIAL: // After start, switch to the continued spinning sound - m_spin_playback_sample = SPIN_LOADED; // move to looping sound + m_spin_kind = floppy_sound_samples::SPIN_LOADED; // move to looping sound break; - case SPIN_EMPTY: + case floppy_sound_samples::SPIN_EMPTY: // As long as the motor pin is asserted, restart the sample // play the spindown sample - if (!m_motor_on) m_spin_playback_sample = SPIN_END; // motor was turned off already (during spin-up maybe) -> spin down + if (!m_motor_on) m_spin_kind = floppy_sound_samples::END_EMPTY; // motor was turned off already (during spin-up maybe) -> spin down break; - case SPIN_LOADED: - if (!m_motor_on) m_spin_playback_sample = SPIN_END; // motor was turned off already (during spin-up maybe) -> spin down + case floppy_sound_samples::SPIN_LOADED: + if (!m_motor_on) m_spin_kind = floppy_sound_samples::END_LOADED; // motor was turned off already (during spin-up maybe) -> spin down break; - case SPIN_END: + case floppy_sound_samples::END_EMPTY: + case floppy_sound_samples::END_LOADED: // Spindown sample over, be quiet or restart if the // motor has been restarted if (m_motor_on) - m_spin_playback_sample = m_with_disk ? SPIN_START_LOADED : SPIN_START_EMPTY; + { + LOGMASKED(LOG_SND_DETAIL, "Restart spinning sound\n"); + m_spin_kind = m_with_disk ? floppy_sound_samples::START_LOADED : floppy_sound_samples::START_EMPTY; + } else - m_spin_playback_sample = QUIET; + m_spin_kind = floppy_sound_samples::QUIET; + break; + + default: break; } + + int old_sample = m_spin_sample; + m_spin_sample = (m_spin_kind==floppy_sound_samples::QUIET)? floppy_sound_samples::QUIET : m_samplelist->find_spin(m_spin_kind); + + if (m_spin_sample == floppy_sound_samples::QUIET) + LOGMASKED(LOG_SND, "Spin off\n"); + else + if (m_spin_sample != old_sample) + LOGMASKED(LOG_SND, "Spin sample = %d\n", m_spin_sample); + // Restart the selected sample m_spin_samplepos = 0; } } // Seek sound - // As long as we have a seek sound, there is no step sound + // As long as we have a seek sound, there is a pending step sound if (m_seek_sound_timeout == 1) { + LOGMASKED(LOG_SND_DETAIL, "Finish step sound\n"); // Not retriggered; switch back to the last step sound - m_seek_playback_sample = QUIET; + m_seek_sample = floppy_sound_samples::QUIET; m_seek_sound_timeout = 0; // Skip 1/100 sec to dampen the loudest pulse // yep, a somewhat dirty trick; we don't have to record yet another sample m_step_samplepos += 441; } - if (m_seek_playback_sample != QUIET) + if (m_seek_sample != floppy_sound_samples::QUIET) { m_seek_sound_timeout--; - m_idx = m_step_base + m_seek_playback_sample; - sampleend = m_sample[m_idx].data.size(); + sampleend = m_sample[m_seek_sample-1].data.size(); + // Mix it into the stream value - out += m_sample[m_idx].data[(int)m_seek_samplepos]; + out += m_sample[m_seek_sample-1].data[(int)m_seek_samplepos]; + // By adding different values than 1, we can change the playback speed // This will be used to adjust the seek sound m_seek_samplepos += m_seek_pitch; @@ -1690,18 +1976,17 @@ void floppy_sound_device::sound_stream_update(sound_stream &stream) else { // Stepper sound - if (m_step_playback_sample != QUIET) + if (m_step_sample != floppy_sound_samples::QUIET) { - m_idx = m_step_base + m_step_playback_sample; - sampleend = m_sample[m_idx].data.size(); + sampleend = m_sample[m_step_sample-1].data.size(); // Mix it into the stream value - out += m_sample[m_idx].data[m_step_samplepos++]; + out += m_sample[m_step_sample-1].data[m_step_samplepos++]; if (m_step_samplepos >= sampleend) { // Step sample done m_step_samplepos = 0; - m_step_playback_sample = QUIET; + m_step_sample = floppy_sound_samples::QUIET; } } } @@ -1716,11 +2001,10 @@ void floppy_sound_device::sound_stream_update(sound_stream &stream) void floppy_image_device::device_add_mconfig(machine_config &config) { SPEAKER(config, FLOPSPK).front_center(); - FLOPPYSOUND(config, FLOPSND_TAG, 44100).add_route(ALL_OUTPUTS, FLOPSPK, 0.5); + FLOPPYSOUND(config, m_sound_out, 44100).add_route(ALL_OUTPUTS, FLOPSPK, 0.5); } - -DEFINE_DEVICE_TYPE(FLOPPYSOUND, floppy_sound_device, "flopsnd", "Floppy sound") +DEFINE_DEVICE_TYPE(FLOPPYSOUND, floppy_sound_device, FLOPSND_TAG, "Floppy sound") //************************************************************************** @@ -2869,7 +3153,7 @@ bool mac_floppy_device::wpt_r() m_reg = (m_reg & 7) | (m_actual_ss ? 8 : 0); if(0 && (m_reg != 4 && m_reg != 12 && m_reg != 5 && m_reg != 13)) - logerror("fdc disk sense reg %x %s %p\n", m_reg, regnames[m_reg], m_image.get()); + LOGMASKED(LOG_MACDRIVE, "fdc disk sense reg %x %s %p\n", m_reg, regnames[m_reg], m_image.get()); switch(m_reg) { case 0x0: // Step direction @@ -2948,43 +3232,43 @@ void mac_floppy_device::seek_phase_w(int phases) if(m_strb && !prev_strb) { switch(m_reg) { case 0x0: // Step to cylinder + 1 - logerror("cmd step dir +1\n"); + LOGMASKED(LOG_MACDRIVE, "cmd step dir +1\n"); dir_w(0); break; case 0x1: // Step on - logerror("cmd step on\n"); + LOGMASKED(LOG_MACDRIVE, "cmd step on\n"); stp_w(0); // There should be a delay, but it's not necessary stp_w(1); break; case 0x2: // Motor on - logerror("cmd motor on\n"); + LOGMASKED(LOG_MACDRIVE, "cmd motor on\n"); floppy_image_device::mon_w(0); break; case 0x3: // End eject - logerror("cmd end eject\n"); + LOGMASKED(LOG_MACDRIVE, "cmd end eject\n"); break; case 0x4: // Step to cylinder - 1 - logerror("cmd step dir -1\n"); + LOGMASKED(LOG_MACDRIVE, "cmd step dir -1\n"); dir_w(1); break; case 0x6: // Motor off - logerror("cmd motor off\n"); + LOGMASKED(LOG_MACDRIVE, "cmd motor off\n"); floppy_image_device::mon_w(1); break; case 0x7: // Start eject - logerror("cmd start eject\n"); + LOGMASKED(LOG_MACDRIVE, "cmd start eject\n"); unload(); break; case 0x9: // MFM mode on - logerror("cmd mfm on\n"); + LOGMASKED(LOG_MACDRIVE, "cmd mfm on\n"); if(m_has_mfm) { m_mfm = true; track_changed(); @@ -2992,18 +3276,18 @@ void mac_floppy_device::seek_phase_w(int phases) break; case 0xc: // Clear m_dskchg - logerror("cmd clear m_dskchg\n"); + LOGMASKED(LOG_MACDRIVE, "cmd clear m_dskchg\n"); m_dskchg = 1; break; case 0xd: // GCR mode on - logerror("cmd gcr on\n"); + LOGMASKED(LOG_MACDRIVE, "cmd gcr on\n"); m_mfm = false; track_changed(); break; default: - logerror("cmd reg %x %s\n", m_reg, regnames[m_reg]); + LOGMASKED(LOG_MACDRIVE, "cmd reg %x %s\n", m_reg, regnames[m_reg]); break; } } diff --git a/src/devices/imagedev/floppy.h b/src/devices/imagedev/floppy.h index ab199bcb058..d73bc3e273b 100644 --- a/src/devices/imagedev/floppy.h +++ b/src/devices/imagedev/floppy.h @@ -22,7 +22,181 @@ namespace fs { class meta_data; }; -class floppy_sound_device; +/* + Floppy drive sound + + MZ, August 2015 + Updated April 2026 + + In order to activate floppy drive sounds with predefined samples for 3.5" + and 5.25" drives, call + + * enable_sound() or enable_sound(true) or enable_sound(nullptr) + + on the instances of floppy_connector, usually appearing in device_add_mconfig + of the device where the drives are connected. If you prefer custom sounds + for the drive, create an instance of floppy_sound_samples, and register + samples on it as follows: + + * clear() + Clear the sample list. Recommended to use at code locations that + may be called several times (like device_add_mconfig). + + * set_form_factor(form_factor, directory) + All following add operations will use the given form factor and + assume that the samples are found in the provided directory. May be + called several times in order to add samples for different form factors. + + * add_spin_sample(filename, type): + For spinning motor samples. See the enum below for type values. + + * add_step_sample(filename, start, end): + Stepper sound for single steps, used in the track range from start to + end; when start and end are omitted, 0 and 99 are assumed, covering the + whole disk. + + * add_seek_sample(filename, nominal_rate, max_rate, start, end): + Stepper sound for continuous movement for a rate not exceeding max_rate. + The pitch is adjusted according to the ratio of the actual rate and + the nominal rate, thus, the sample is played back at natural speed when + the actual rate matches the nominal rate. The sample is selected whose + maximum rate is the minimum among those whose maximum rate is higher + than the actual rate, and if its range contains the current track number. + When not specified, the range covers the whole disk (0..99). + + For an example, see the predefined sample list in the constructor of + floppy_sound_device. + + For custom samples, pass the address of the specific floppy_sound_samples + instance as + + * enable_sound(&myfloppysamples); + + If the custom samples cannot be found, the default samples are used. If + those cannot be found either, sound is disabled. + + If the samples list does not contain a matching form factor, the following + replacement strategy is used: + + * If 3" samples are requested but not found, 3.5" samples are used. + * If 3.5" or 8" samples are requested but not found, 5.25" samples are used. +*/ + +class floppy_sound_samples +{ +public: + floppy_sound_samples(); + + /* Clear the list. */ + void clear() { m_fulllist.clear(); } + + /* Set the form factor for the following add operations. */ + void set_form_factor(int form_factor, const char* dir); + + enum // spin type + { + QUIET=-1, // Also used as silence for steps and seeks + START_EMPTY=0, // Start spinning without disk + SPIN_EMPTY, // Spinning without disk + END_EMPTY, // Stop spinning spinning without disk + START_LOADED_INITIAL, // Start spinning with disk, 3.5" drives make a click when latching in + START_LOADED, // Start spinning with disk, already latched in + SPIN_LOADED, // Spinning with disk (mandatory sample) + END_LOADED // Stop spinning with disk + }; + + /* Add spin, step, and seek samples. */ + void add_spin_sample(const char* filename, int type); + void add_step_sample(const char* filename, int start=0, int end=99); + void add_seek_sample(const char* filename, int nominal_rate, int max_rate, int mintrack=0, int maxtrack=99); + + /* Deliver the list of names for the parent class samples_device. */ + const char* const* get_names(); + + /* Selects the matching form factor and prepares the samples list. */ + void select(int form_factor); + int get_assumed_form_factor() { return m_current_form_factor; } + + /* Search for a suitable spinning sample. Return the index into the + samples list. */ + int find_spin(int kind) const; + + /* Search for a suitable step sample. */ + int find_step(int track) const; + + /* Search for a suitable seek sample. */ + int find_seek(double rate, int track, double& pitch) const; + +private: + enum + { + SPIN = 0, + STEP, + SEEK + }; + + struct floppy_sound_entry + { + int index = 0; + int type = 0; // type: SPIN, STEP, SEEK + int form_factor; // indicates the form factor of the drive + int mintrack = 0; // valid from here (including), meaningless for spin entries + int maxtrack = 99; // to here (including), meaningless for spin entries + int rate = 0; // rate of the seek sample + int maxrate = 0; // max rate for pitching up the seek sample + int spintype = 0; // type for spin entries + const char *directory; // directory where the sample is stored + const char *filename; + }; + + std::string m_basedir; // Subdirectory which contains the samples + std::vector m_samplenames; + + std::vector m_fulllist; + + int m_current_form_factor; + const char* m_current_dir; +}; + +class floppy_sound_device : public samples_device +{ +public: + floppy_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + void motor(bool on, bool withdisk); + void step(int track); + void unload() { m_firstturn = true; } + bool samples_loaded() { return m_samples_available; } + void register_for_save_states(); + void set_samples(floppy_sound_samples *samples, int form_factor); + +protected: + void device_start() override ATTR_COLD; + +private: + // device_sound_interface overrides + virtual void sound_stream_update(sound_stream &stream) override; + sound_stream* m_sound; + + floppy_sound_samples* m_samplelist; + floppy_sound_samples m_default_samples; + + bool m_motor_on; + bool m_with_disk; + int m_spin_kind; + int m_spin_sample; + int m_spin_samplepos; + int m_step_sample; + int m_step_samplepos; + int m_seek_sample; + double m_seek_samplepos; // we may using a non-integer pitch + double m_seek_pitch; + int m_seek_sound_timeout; + attotime m_last_step_time; + bool m_firstturn; // see START_LOADED_INITIAL + bool m_samples_available; + bool m_in_seek; + double m_step_rate; +}; /*************************************************************************** TYPE DEFINITIONS @@ -157,9 +331,6 @@ public: static void default_mfm_floppy_formats(format_registration &fr); static void default_pc_floppy_formats(format_registration &fr); - // Enable sound - void enable_sound(bool doit) { m_make_sound = doit; } - protected: struct fs_enum; @@ -273,9 +444,10 @@ protected: void cache_fill(const attotime &when); void cache_weakness_setup(); - // Sound - bool m_make_sound; - floppy_sound_device* m_sound_out; + // Sound support + bool m_make_sound; + const floppy_sound_samples *m_samples; + required_device m_sound_out; }; #define DECLARE_FLOPPY_IMAGE_DEVICE(Type, Name, Interface) \ @@ -396,46 +568,6 @@ DECLARE_DEVICE_TYPE(OAD34V, oa_d34v_device) DECLARE_DEVICE_TYPE(MFD51W, mfd51w_device) DECLARE_DEVICE_TYPE(MFD75W, mfd75w_device) - -/* - Floppy drive sound -*/ - -class floppy_sound_device : public samples_device -{ -public: - floppy_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - void motor(bool on, bool withdisk); - void step(int track); - bool samples_loaded() { return m_loaded; } - void register_for_save_states(); - -protected: - void device_start() override ATTR_COLD; - -private: - // device_sound_interface overrides - virtual void sound_stream_update(sound_stream &stream) override; - sound_stream* m_sound; - - int m_step_base; - int m_spin_samples; - int m_step_samples; - int m_spin_samplepos; - int m_step_samplepos; - int m_seek_sound_timeout; - int m_zones; - int m_spin_playback_sample; - int m_step_playback_sample; - int m_seek_playback_sample; - bool m_motor_on; - bool m_with_disk; - bool m_loaded; - double m_seek_pitch; - double m_seek_samplepos; -}; - - class floppy_connector: public device_t, public device_slot_interface { @@ -465,18 +597,27 @@ public: virtual ~floppy_connector(); template void set_formats(T &&_formats) { formats = std::forward(_formats); } - void enable_sound(bool doit) { m_enable_sound = doit; } + void set_sectoring_type(uint32_t sectoring_type) { m_sectoring_type = sectoring_type; } floppy_image_device *get_device(); + // Sound support + bool use_sound() { return m_use_sound; } + void enable_sound(bool doit = true); + void enable_sound(floppy_sound_samples *samples); + floppy_sound_samples *get_samples() { return m_samples; } + protected: virtual void device_start() override ATTR_COLD; virtual void device_config_complete() override; private: std::function formats; - bool m_enable_sound; + + bool m_use_sound; + floppy_sound_samples *m_samples; + uint32_t m_sectoring_type; }; -- cgit v1.2.3