libretro: drive LEDs and floppy drive sound, from the hatari2014 core

This commit is contained in:
WizzardSK authored and Craig Carnell committed 2026-11-04 18:15:06 +00:00
1 parent a7413b2b32
commit fac13aeeb8
9 files changed
+524 -6

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ add_library(UiRetro OBJECT
audio.c
disk_control.c
file_retro.c
floppy_sound.c
harddisk.c
joy_ui.c
gui_event.c
+7
View File
@@ -18,6 +18,7 @@ const char Audio_fileid[] = "Hatari audio.c";
#include "dmaSnd.h"
#include "crossbar.h"
#include "video.h"
#include "floppy_sound.h"
bool bSoundWorking = false; /* Is sound OK */
@@ -80,12 +81,18 @@ void Audio_Unlock(void)
{
if (AudioMixBuffer_pos_read + nGeneratedSamples <= AUDIOMIXBUFFER_SIZE)
{
FloppySound_Mix(&AudioMixBuffer[AudioMixBuffer_pos_read][0],
nGeneratedSamples, nAudioFrequency);
audio_sample_batch_cb(&AudioMixBuffer[AudioMixBuffer_pos_read][0],
nGeneratedSamples);
}
else
{
int samples_at_end = AUDIOMIXBUFFER_SIZE - AudioMixBuffer_pos_read;
FloppySound_Mix(&AudioMixBuffer[AudioMixBuffer_pos_read][0],
samples_at_end, nAudioFrequency);
FloppySound_Mix(&AudioMixBuffer[0][0],
nGeneratedSamples - samples_at_end, nAudioFrequency);
audio_sample_batch_cb(&AudioMixBuffer[AudioMixBuffer_pos_read][0],
samples_at_end);
audio_sample_batch_cb(&AudioMixBuffer[0][0],
+256
View File
@@ -0,0 +1,256 @@
/*
Hatari - floppy_sound.c
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
Floppy drive sounds for the libretro core, from the hatari2014 core
(libretro/floppy_sound.c there): a click and seek sound when the led of
drive A or B comes on, repeated for as long as the floppy controller keeps
working on the drive, for both at the same time.
A sample of one's own can be put in the frontend's system directory as
floppy.raw: signed 16-bit little-endian stereo at 44100 Hz, no header
(ffmpeg -i click.wav -f s16le -ar 44100 -ac 2 floppy.raw). Without it a
synthetic one is used. Both are made to fit the core's audio rate, which
can change at run time.
*/
const char FloppySound_fileid[] = "Hatari floppy_sound.c";
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
#include "main_retro.h"
#include "floppy_sound.h"
#include "vfs.h"
#define RAW_RATE 44100
static int16_t *raw_pcm; /* floppy.raw as loaded, at RAW_RATE */
static int raw_frames;
static int16_t *sample; /* what is mixed, at sample_rate */
static int sample_frames;
static int sample_rate;
static int play_pos[2] = { -1, -1 };
static bool enabled = true;
static int volume = 192; /* 0-256 */
/**
* The built-in sound, ~55 ms: a low mechanical thud, the stepper motor's
* buzz, a descending head seek chirp and a short chassis rattle.
*/
static int16_t *FloppySound_Synthesize(int rate, int *frames)
{
const float pi = 3.14159265f;
int n = rate * 55 / 1000;
int16_t *pcm = malloc(n * 2 * sizeof(*pcm));
int i;
if (!pcm)
return NULL;
for (i = 0; i < n; i++)
{
float t = (float)i / (float)rate;
float s = 0.0f;
/* thud: 120 Hz, decays in ~12 ms */
s += sinf(2.0f * pi * 120.0f * t) * expf(-t / 0.012f) * 0.55f;
/* stepper buzz: ~800 Hz with a wobble, 2 ms onset */
{
float onset = t > 0.002f ? 1.0f : t / 0.002f;
float freq = 800.0f + 60.0f * sinf(2.0f * pi * 180.0f * t);
s += sinf(2.0f * pi * freq * t) * onset * expf(-t / 0.008f) * 0.30f;
}
/* head seek chirp: 2400 -> 700 Hz over 25 ms */
if (t < 0.025f)
{
float freq = 2400.0f - 1700.0f * (t / 0.025f);
s += sinf(2.0f * pi * freq * t) * expf(-t / 0.018f) * 0.20f;
}
/* rattle: products of unrelated frequencies, 5-17 ms */
{
float t2 = t - 0.005f;
if (t2 > 0.0f && t2 < 0.012f)
{
float noise = sinf(2.0f * pi * 3700.0f * t2)
* sinf(2.0f * pi * 2300.0f * t2)
* sinf(2.0f * pi * 970.0f * t2);
s += noise * expf(-t2 / 0.005f) * 0.18f;
}
}
if (s > 1.0f)
s = 1.0f;
if (s < -1.0f)
s = -1.0f;
pcm[i * 2] = pcm[i * 2 + 1] = (int16_t)(s * 28000.0f);
}
*frames = n;
return pcm;
}
/**
* floppy.raw at another rate, linearly interpolated
*/
static int16_t *FloppySound_Resample(int rate, int *frames)
{
int n = (int)((int64_t)raw_frames * rate / RAW_RATE);
int16_t *pcm;
int i, c;
if (n <= 0 || !(pcm = malloc(n * 2 * sizeof(*pcm))))
return NULL;
for (i = 0; i < n; i++)
{
int64_t pos = (int64_t)i * RAW_RATE * 256 / rate;
int idx = (int)(pos >> 8), frac = (int)(pos & 255);
int next = idx + 1 < raw_frames ? idx + 1 : idx;
for (c = 0; c < 2; c++)
pcm[i * 2 + c] = (int16_t)((raw_pcm[idx * 2 + c] * (256 - frac) +
raw_pcm[next * 2 + c] * frac) >> 8);
}
*frames = n;
return pcm;
}
static void FloppySound_Prepare(int rate)
{
free(sample);
sample = NULL;
sample_frames = 0;
sample_rate = rate;
if (raw_pcm)
sample = FloppySound_Resample(rate, &sample_frames);
if (!sample)
sample = FloppySound_Synthesize(rate, &sample_frames);
play_pos[0] = play_pos[1] = -1;
}
void FloppySound_Init(const char *system_dir)
{
char path[FILENAME_MAX];
VFS_FILE f;
int64_t size;
FloppySound_UnInit();
if (!system_dir || !system_dir[0])
return;
snprintf(path, sizeof(path), "%s%cfloppy.raw", system_dir, PATHSEP);
f = VFS_fopen(path, "rb");
if (!f)
return;
size = VFS_fsize(f);
if (size >= 4 && size <= 16 * 1024 * 1024 && (raw_pcm = malloc(size)) != NULL)
{
if (VFS_fread(raw_pcm, 1, size, f) == (size_t)size)
{
raw_frames = (int)(size / 4);
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
{
int i;
for (i = 0; i < raw_frames * 2; i++)
{
uint16_t v = (uint16_t)raw_pcm[i];
raw_pcm[i] = (int16_t)((v >> 8) | (v << 8));
}
}
#endif
}
else
{
free(raw_pcm);
raw_pcm = NULL;
}
}
VFS_fclose(f);
}
void FloppySound_UnInit(void)
{
free(raw_pcm);
raw_pcm = NULL;
raw_frames = 0;
free(sample);
sample = NULL;
sample_frames = 0;
sample_rate = 0;
play_pos[0] = play_pos[1] = -1;
}
void FloppySound_SetEnabled(bool on)
{
enabled = on;
if (!on)
play_pos[0] = play_pos[1] = -1;
}
void FloppySound_SetVolume(int percent)
{
volume = percent * 256 / 100;
}
void FloppySound_Trigger(int drive)
{
if (enabled && (drive == 0 || drive == 1))
play_pos[drive] = 0;
}
bool FloppySound_Playing(int drive)
{
return (drive == 0 || drive == 1) && play_pos[drive] >= 0;
}
void FloppySound_Mix(int16_t *buf, int frames, int rate)
{
int d, i;
if (!enabled || rate <= 0 || (play_pos[0] < 0 && play_pos[1] < 0))
return;
if (rate != sample_rate)
{
int pos[2] = { play_pos[0], play_pos[1] };
FloppySound_Prepare(rate);
play_pos[0] = pos[0] >= 0 ? 0 : -1;
play_pos[1] = pos[1] >= 0 ? 0 : -1;
}
if (!sample)
return;
for (d = 0; d < 2; d++)
{
if (play_pos[d] < 0)
continue;
for (i = 0; i < frames && play_pos[d] < sample_frames; i++, play_pos[d]++)
{
int l = buf[i * 2] + ((sample[play_pos[d] * 2] * volume) >> 8);
int r = buf[i * 2 + 1] + ((sample[play_pos[d] * 2 + 1] * volume) >> 8);
buf[i * 2] = l > 32767 ? 32767 : l < -32768 ? -32768 : l;
buf[i * 2 + 1] = r > 32767 ? 32767 : r < -32768 ? -32768 : r;
}
if (play_pos[d] >= sample_frames)
play_pos[d] = -1;
}
}
+30
View File
@@ -0,0 +1,30 @@
/*
Hatari - floppy_sound.h
This file is distributed under the GNU General Public License, version 2
or at your option any later version. Read the file gpl.txt for details.
*/
#ifndef HATARI_RETRO_FLOPPY_SOUND_H
#define HATARI_RETRO_FLOPPY_SOUND_H
#include <stdbool.h>
#include <stdint.h>
/* Load floppy.raw from the system directory, if there is one */
void FloppySound_Init(const char *system_dir);
void FloppySound_UnInit(void);
void FloppySound_SetEnabled(bool enabled);
void FloppySound_SetVolume(int percent);
/* Start the drive sound for drive 0 (A) or 1 (B) */
void FloppySound_Trigger(int drive);
/* Whether the sound for drive 0 (A) or 1 (B) is still playing */
bool FloppySound_Playing(int drive);
/* Mix the playing drive sounds into 'frames' stereo frames at 'rate' Hz */
void FloppySound_Mix(int16_t *buf, int frames, int rate);
#endif /* ifndef HATARI_RETRO_FLOPPY_SOUND_H */
+5
View File
@@ -13,6 +13,7 @@
#include "disk_control.h"
#include "file.h"
#include "floppy.h"
#include "floppy_sound.h"
#include "harddisk.h"
#include "log.h"
#include "memorySnapShot.h"
@@ -197,6 +198,7 @@ RETRO_API void retro_init(void)
if (environment_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) && system_dir)
{
retro_system_directory = system_dir;
FloppySound_Init(system_dir);
snprintf(tos_path, sizeof(tos_path), "%s%c%s",
system_dir, PATHSEP, "tos.img");
@@ -231,6 +233,7 @@ RETRO_API void retro_deinit(void)
{
scratch_path[0] = '\0';
Main_UnInit();
FloppySound_UnInit();
}
RETRO_API void retro_get_system_info(struct retro_system_info *info)
@@ -332,6 +335,8 @@ RETRO_API void retro_run(void)
}
/* Note: video_refresh_cb is called in Screen_Draw() already! */
/* Video callbacks have already submitted the frame and its LEDs. */
Statusbar_FrameDone();
}
RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device)
+12
View File
@@ -5,9 +5,21 @@
or at your option any later version. Read the file gpl.txt for details.
*/
#pragma once
#include <libretro.h>
extern bool has_cpu_config_changed;
extern const char *retro_system_directory;
extern const char *retro_save_directory;
extern retro_environment_t environment_cb;
extern retro_video_refresh_t video_refresh_cb;
extern retro_input_state_t input_state_cb;
/* screen.c: cache the drive LED option when runtime options are applied */
void Screen_ShowDriveLeds(bool show);
/* statusbar.c: the drive leds, drawn over the frame */
void Statusbar_ShowLeds(bool show);
bool Statusbar_LedsVisible(void);
void Statusbar_DrawLeds(uint32_t *pixels, int width, int height, int pitch);
void Statusbar_FrameDone(void);
+49
View File
@@ -9,6 +9,7 @@
#include "configuration.h"
#include "fdc.h"
#include "floppy_sound.h"
#include "main_retro.h"
#include "options.h"
#include "reset.h"
@@ -350,6 +351,50 @@ static struct retro_core_option_v2_definition option_defs_us[] = {
},
"off"
},
{
"hatari_drive_leds",
"Drive LEDs",
"Drive LEDs",
"Show drive activity in the top right corner of the screen: an amber LED per floppy drive, as on an STFM, and a green one for the hard disk.",
NULL,
"floppy",
{
{ "enabled", NULL },
{ "disabled", NULL },
{ NULL, NULL },
},
"enabled"
},
{
"hatari_floppy_sound",
"Floppy Drive Sound",
"Floppy Drive Sound",
"Play a mechanical floppy drive sound when a drive is accessed. For a sound of your own, put floppy.raw (signed 16-bit stereo at 44100 Hz, no header) in the frontend's system directory.",
NULL,
"floppy",
{
{ "enabled", NULL },
{ "disabled", NULL },
{ NULL, NULL },
},
"enabled"
},
{
"hatari_floppy_sound_volume",
"Floppy Drive Sound Volume",
"Floppy Drive Sound Volume",
"Volume of the floppy drive sound.",
NULL,
"floppy",
{
{ "25", "25%" },
{ "50", "50%" },
{ "75", "75%" },
{ "100", "100%" },
{ NULL, NULL },
},
"75"
},
{
"hatari_drive_a_enable",
"Drive A Enabled",
@@ -817,6 +862,10 @@ void Core_ApplyRuntimeOptions(void)
FDC_Drive_Set_Enable(1, ConfigureParams.DiskImage.EnableDriveB);
FDC_Drive_Set_NumberOfHeads(0, ConfigureParams.DiskImage.DriveA_NumberOfHeads);
FDC_Drive_Set_NumberOfHeads(1, ConfigureParams.DiskImage.DriveB_NumberOfHeads);
Screen_ShowDriveLeds(Core_VarBool("hatari_drive_leds", true));
FloppySound_SetEnabled(Core_VarBool("hatari_floppy_sound", true));
FloppySound_SetVolume(Core_VarInt("hatari_floppy_sound_volume", 75));
/* --- Atari Screen --- */
str = Core_VarStr("hatari_monitor_type", "rgb");
+51 -6
View File
@@ -47,6 +47,16 @@ bool bInFullScreen = false; /* true if in full screen */
static uint32_t *framebuffer;
static int screen_width, screen_height;
static bool drive_leds_enabled = true;
static bool drive_leds_changed;
void Screen_ShowDriveLeds(bool show)
{
if (drive_leds_enabled != show)
drive_leds_changed = true;
drive_leds_enabled = show;
Statusbar_ShowLeds(show);
}
/**
@@ -233,6 +243,31 @@ void Screen_UnLock(void)
{
}
/**
* draw overlays (like drive LEDs) on top of the ST screen.
*/
static uint32_t *Core_DrawOverlays(uint32_t *pixels, int width, int height, int pitch)
{
static uint32_t *overlay;
static size_t overlay_size;
size_t size = (size_t)pitch * height;
if (!drive_leds_enabled || !pixels || !Statusbar_LedsVisible())
return pixels;
if (size > overlay_size)
{
uint32_t *bigger = realloc(overlay, size);
if (!bigger)
return pixels;
overlay = bigger;
overlay_size = size;
}
memcpy(overlay, pixels, size);
Statusbar_DrawLeds(overlay, width, height, pitch);
return overlay;
}
/**
* Draw ST screen to window/full-screen
@@ -241,15 +276,22 @@ void Screen_UnLock(void)
bool Screen_Draw(bool bForceFlip)
{
bool screen_changed;
int width, height, pitch;
uint32_t *pixels;
/* And draw (if screen contents changed) */
screen_changed = ConvST_DrawFrame();
/* Clear the last overlay once when LEDs are disabled at runtime. */
if (!screen_changed && !bForceFlip && !drive_leds_enabled && !drive_leds_changed)
return false;
if (screen_changed)
{
video_refresh_cb(framebuffer, screen_width, screen_height,
screen_width * 4);
}
Screen_GetDimension(&pixels, &width, &height, &pitch);
pixels = Core_DrawOverlays(pixels, width, height, pitch);
/* LEDs can change even when the emulated screen has not changed.
* Submit the clean frame too, so an LED that goes off is erased. */
video_refresh_cb(pixels, width, height, pitch);
drive_leds_changed = false;
return screen_changed;
}
@@ -257,8 +299,11 @@ bool Screen_Draw(bool bForceFlip)
void Screen_GenConvUpdate(bool update_statusbar)
{
video_refresh_cb(framebuffer, screen_width, screen_height,
uint32_t *pixels = Core_DrawOverlays(framebuffer, screen_width,
screen_height, screen_width * 4);
video_refresh_cb(pixels, screen_width, screen_height,
screen_width * 4);
drive_leds_changed = false;
}
uint32_t Screen_GetGenConvWidth(void)
+113
View File
@@ -5,6 +5,11 @@
or at your option any later version. Read the file gpl.txt for details.
Code to draw statusbar area, floppy leds etc.
The libretro core has no statusbar area: the drive LEDs are drawn over the
top right corner of the frame instead, a small amber bar per floppy drive
like the one on an STFM, and a green one for the hard disk (from the
hatari2014 core). A floppy LED going on also starts the drive sound.
*/
const char Statusbar_fileid[] = "Hatari statusbar.c";
@@ -23,8 +28,17 @@ const char Statusbar_fileid[] = "Hatari statusbar.c";
#include "blitter.h"
#include "str.h"
#include "lilo.h"
#include "floppy_sound.h"
#include "main_retro.h"
#define HD_LED_FRAMES 10 /* frames the HD led stays on */
static bool leds_shown = true;
static drive_led_t floppy_led[2];
static bool floppy_busy[2]; /* the controller has worked on the drive this frame */
static int hd_led_frames;
/**
* Return statusbar height for given width and height
*/
@@ -59,6 +73,8 @@ int Statusbar_GetHeight(void)
*/
void Statusbar_EnableHDLed(drive_led_t state)
{
if (state != LED_STATE_OFF)
hd_led_frames = HD_LED_FRAMES;
}
/**
@@ -68,8 +84,105 @@ void Statusbar_EnableHDLed(drive_led_t state)
void Statusbar_SetFloppyLed(drive_index_t drive, drive_led_t state)
{
assert(drive == DRIVE_LED_A || drive == DRIVE_LED_B);
if (state == LED_STATE_ON_BUSY)
floppy_busy[drive] = true;
floppy_led[drive] = state;
}
/**
* Show or hide the drive leds drawn over the frame
*/
void Statusbar_ShowLeds(bool show)
{
leds_shown = show;
}
/**
* Whether any drive led is to be drawn over this frame
*/
bool Statusbar_LedsVisible(void)
{
return leds_shown && (floppy_led[0] != LED_STATE_OFF ||
floppy_led[1] != LED_STATE_OFF || hd_led_frames > 0);
}
/**
* Once per retro_run: what times the HD led out, and what starts the floppy
* sound. Drive leds are looked at once a frame, as the hatari2014 core did:
* the sound starts when a led has come on since the last frame, and plays
* again each time it has finished for as long as the controller has worked
* on the drive during the frame - so a load sounds for as long as it lasts,
* and the sample is never cut off to start it over. TOS selecting drive A
* and B in turn every eight VBLs to check for a disk change is over within
* the frame and carries out no command, so it is not heard.
*/
void Statusbar_FrameDone(void)
{
static bool was_on[2];
int d;
for (d = 0; d < 2; d++)
{
bool on = floppy_led[d] != LED_STATE_OFF;
bool busy = floppy_busy[d] || floppy_led[d] == LED_STATE_ON_BUSY;
if ((on && !was_on[d]) || (busy && !FloppySound_Playing(d)))
FloppySound_Trigger(d);
was_on[d] = on;
floppy_busy[d] = false;
}
if (hd_led_frames > 0)
hd_led_frames--;
}
/**
* Draw the drive leds into a copy of the frame about to be handed to the
* frontend
*/
void Statusbar_DrawLeds(uint32_t *pixels, int width, int height, int pitch)
{
static const uint32_t floppy_color = 0xF8D800; /* STFM amber */
static const uint32_t hd_color = 0x30E030;
bool on[3];
int led_w, led_h, gap, right, top, i, x, y;
int stride = pitch / 4;
if (!Statusbar_LedsVisible() || !pixels)
return;
on[0] = floppy_led[0] != LED_STATE_OFF;
on[1] = floppy_led[1] != LED_STATE_OFF;
on[2] = hd_led_frames > 0;
/* The hatari2014 core's 13x5 bar in a 384 pixel wide frame, scaled */
led_w = width * 13 / 384;
led_h = height * 5 / 276;
if (led_w < 7)
led_w = 7;
if (led_w > 64)
led_w = 64;
if (led_h < 3)
led_h = 3;
if (led_h > 16)
led_h = 16;
gap = led_w / 2;
right = width - led_w / 2;
top = led_h;
if (right - 3 * led_w - 2 * gap < 0 || top + led_h > height)
return;
/* A rightmost, then B, then the hard disk */
for (i = 0; i < 3; i++)
{
int left = right - (i + 1) * led_w - i * gap;
if (!on[i])
continue;
for (y = top; y < top + led_h; y++)
for (x = left; x < left + led_w; x++)
pixels[y * stride + x] = i < 2 ? floppy_color : hd_color;
}
}
/**
* Set TOS etc information and initial help message