Compare commits

...

2 commits

Author SHA1 Message Date
9f49977bfb Minimal audio implementation
I investigated various implementations of sound in other emulators,
which tended to be a bit tricky. After brainstorming with @ladyada
we hit upon the idea of simply assuming samples are written as
bytes in order and trapping on the write to the RAM address of the
final sample to collect a full 370 sample buffer.

This is good enough to play the system beep. It also works with some
games (dark castle) but not others (glider) and still does not
successfully play samples in hypercard. This may have to do with
the incomplete implementation of the VIA timer2, because debug
messages about timer2 are printed when trying to play sounds in
hypercard.
2025-03-28 06:56:07 -05:00
297d0ca8b3 rom: Fix sound buffer address in BootBeep
.. for nonstandard RAM sizes
2025-03-28 06:45:48 -05:00
6 changed files with 121 additions and 6 deletions

View file

@ -27,6 +27,7 @@
DEBUG ?= 0
MEMSIZE ?= 128
ENABLE_AUDIO ?= 1
SOURCES = $(wildcard src/*.c)
@ -54,7 +55,7 @@ endif
# Basic support for changing screen res (with the MacPlusV3 ROM)
DISP_WIDTH ?= 512
DISP_HEIGHT ?= 342
CFLAGS_CFG = -DDISP_WIDTH=$(DISP_WIDTH) -DDISP_HEIGHT=$(DISP_HEIGHT)
CFLAGS_CFG = -DDISP_WIDTH=$(DISP_WIDTH) -DDISP_HEIGHT=$(DISP_HEIGHT) -DENABLE_AUDIO=$(ENABLE_AUDIO)
all: main

View file

@ -81,6 +81,8 @@ extern int overlay;
* But, that should never happen post-boot.
*/
#define CLAMP_RAM_ADDR(x) ((x) >= RAM_SIZE ? (x) % RAM_SIZE : (x))
/* Is the address (previously clamped with CLAMP_RAM_ADDR) the audio trap address? (last byte of audio data) */
#define IS_RAM_AUDIO_TRAP(x) (x == umac_get_audio_offset() + 2 * 369)
#define IS_VIA(x) ((ADR24(x) & 0xe80000) == 0xe80000)
#define IS_IWM(x) ((ADR24(x) >= 0xdfe1ff) && (ADR24(x) < (0xdfe1ff + 0x2000)))

View file

@ -53,4 +53,11 @@ static inline unsigned int umac_get_fb_offset(void)
return RAM_SIZE - ((DISP_WIDTH * DISP_HEIGHT / 8) + 0x380);
}
#if ENABLE_AUDIO
#define umac_get_audio_offset() (RAM_SIZE - 768)
void umac_audio_trap();
void umac_audio_cfg(int volume, int sndres);
#endif
#endif

View file

@ -40,6 +40,7 @@
#include <errno.h>
#include <setjmp.h>
#include "umac.h"
#include "machw.h"
#include "m68k.h"
#include "via.h"
@ -70,6 +71,9 @@ static unsigned int g_int_controller_highest_int = 0; /* Highest pending interr
uint8_t *_ram_base;
uint8_t *_rom_base;
#if ENABLE_AUDIO
static int umac_volume, umac_sndres;
#endif
int overlay = 1;
static uint64_t global_time_us = 0;
static int sim_done = 0;
@ -155,6 +159,14 @@ static void via_ra_changed(uint8_t val)
update_overlay_layout();
}
#if ENABLE_AUDIO
uint8_t vol = val & 7;
if (vol != umac_volume) {
umac_volume = val & 7;
umac_audio_cfg(umac_volume, umac_sndres);
}
#endif
oldval = val;
}
@ -166,7 +178,15 @@ static void via_rb_changed(uint8_t val)
// 4 = mouse4 (in, mouse X2)
// 3 = mouse7 (in, 0 = button pressed)
// [2:0] = RTC controls
#if ENABLE_AUDIO
uint8_t sndres = val >> 7;
if(sndres != umac_sndres) {
umac_sndres = sndres;
umac_audio_cfg(umac_volume, umac_sndres);
}
#else
(void)val;
#endif
}
static uint8_t via_ra_in(void)
@ -437,7 +457,13 @@ unsigned int cpu_read_long_dasm(unsigned int address)
void FAST_FUNC(cpu_write_byte)(unsigned int address, unsigned int value)
{
if (IS_RAM(address)) {
RAM_WR8(CLAMP_RAM_ADDR(address), value);
address = CLAMP_RAM_ADDR(address);
RAM_WR8(address, value);
#if ENABLE_AUDIO
if(IS_RAM_AUDIO_TRAP(address)) {
umac_audio_trap();
}
#endif
return;
}

View file

@ -116,6 +116,9 @@ static void rom_patch_plusv3(uint8_t *rom_base)
* allowing the ROM checksum to fail (it returns failure, then
* we carry on). This avoids wild RAM addrs being accessed.
*/
/* Fix up the sound buffer as used by BootBeep */
ROM_WR32(0x292, RAM_SIZE - 768);
#endif
#if DISP_WIDTH != 512 || DISP_HEIGHT != 342

View file

@ -27,6 +27,7 @@
* SOFTWARE.
*/
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <getopt.h>
@ -35,6 +36,9 @@
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#if ENABLE_AUDIO
#include <stdatomic.h>
#endif
#include "SDL.h"
#include "rom.h"
@ -78,6 +82,42 @@ static void copy_fb(uint32_t *fb_out, uint8_t *fb_in)
}
}
#if ENABLE_AUDIO
// audio_callback is called from a thread, so pending_v_retrace can't be a regular variable
atomic_int pending_v_retrace;
static int volscale;
uint8_t *audio_base;
int16_t audio[370];
void umac_audio_cfg(int umac_volume, int umac_sndres) {
volscale = umac_sndres ? 0 : 65536 * umac_volume / 7;
}
void umac_audio_trap() {
int32_t offset = 128;
uint16_t *audiodata = (uint16_t*)audio_base;
int scale = volscale;
if (!scale) {
memset(audio, 0, sizeof(audio));
return;
}
int16_t *stream = audio;
for(int i=0; i<370; i++) {
int32_t a = (*audiodata++ & 0xff) - offset;
a = (a * scale) >> 8;
*stream++ = a;
}
}
static void audio_callback(void *userdata, Uint8 *stream_in, int len_bytes) {
(void) userdata;
assert(len_bytes == sizeof(audio));
atomic_store(&pending_v_retrace, 1);
memcpy(stream_in, audio, sizeof(audio));
}
#endif
/**********************************************************************/
/* The emulator core expects to be given ROM and RAM pointers,
@ -232,7 +272,11 @@ int main(int argc, char *argv[])
SDL_Renderer *renderer;
SDL_Texture *texture;
SDL_Init(SDL_INIT_VIDEO);
SDL_Init(SDL_INIT_VIDEO
#if ENABLE_AUDIO
| SDL_INIT_AUDIO
#endif
);
SDL_Window *window = SDL_CreateWindow("umac",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
@ -264,18 +308,47 @@ int main(int argc, char *argv[])
return 1;
}
#if ENABLE_AUDIO
SDL_AudioSpec desired, obtained;
audio_base = (uint8_t*)ram_base + umac_get_audio_offset();
SDL_zero(desired);
desired.freq = 22256; // wat
desired.channels = 1;
desired.samples = 370;
desired.userdata = (uint8_t*)ram_base + umac_get_audio_offset();
desired.callback = audio_callback;
desired.format = AUDIO_S16;
SDL_AudioDeviceID audio_device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (!audio_device) {
char buf[500];
SDL_GetErrorMsg(buf, sizeof(buf));
printf("SDL audio_deviceSDL_GetError() -> %s\n", buf);
return 1;
}
#endif
////////////////////////////////////////////////////////////////////////
// Emulator init
umac_init(ram_base, rom_base, discs);
umac_opt_disassemble(opt_disassemble);
#if ENABLE_AUDIO
// Default state is paused, this unpauses it
SDL_PauseAudioDevice(audio_device, 0);
#endif
////////////////////////////////////////////////////////////////////////
// Main loop
int done = 0;
int mouse_button = 0;
#if !ENABLE_AUDIO
uint64_t last_vsync = 0;
#endif
uint64_t last_1hz = 0;
do {
struct timeval tv_now;
@ -321,11 +394,14 @@ int main(int argc, char *argv[])
uint64_t now_usec = (tv_now.tv_sec * 1000000) + tv_now.tv_usec;
/* Passage of time: */
if ((now_usec - last_vsync) >= 16667) {
#if ENABLE_AUDIO
int do_v_retrace = atomic_exchange(&pending_v_retrace, 0);
#else
int do_v_retrace = (now_usec - last_vsync) >= 16667;
#endif
if (do_v_retrace) {
umac_vsync_event();
last_vsync = now_usec;
/* Cheapo framerate limiting: */
copy_fb(framebuffer, ram_get_base() + umac_get_fb_offset());
SDL_UpdateTexture(texture, NULL, framebuffer,
DISP_WIDTH * sizeof (Uint32));