summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan C <ianc@noddybox.co.uk>2026-06-29 09:41:42 +0900
committerIan C <ianc@noddybox.co.uk>2026-06-29 09:41:42 +0900
commitc527acfc79f3bda8cfe13a7b51b0b16a32683960 (patch)
treee5bbd119a070848301a03b52f1232a797b35c119
parentc31b779c20638ce25f11489c445a53798a663b13 (diff)
First partial working attempt at sound. Producing output, but very distorted
-rw-r--r--src/Makefile4
-rw-r--r--src/audio.c30
-rw-r--r--src/audio.h10
-rw-r--r--src/spec.c115
-rw-r--r--src/z80.c19
-rw-r--r--src/z80.h16
-rw-r--r--src/z80_private.h10
7 files changed, 194 insertions, 10 deletions
diff --git a/src/Makefile b/src/Makefile
index 15db934..37e1d26 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -85,7 +85,7 @@ clean:
main.o: z80.h spec.h gfx.h gui.h memmenu.h config.h kbbmp.h exit.h tape.h audio.h
main.o: util.h
-spec.o: spec.h z80.h snap.h gfx.h gui.h config.h exit.h tape.h util.h
+spec.o: spec.h z80.h snap.h gfx.h gui.h config.h exit.h tape.h util.h audio.h
snap.o: snap.h z80.h util.h
config.o: exit.h config.h
gfx.o: gfx.h exit.h config.h util.h font.h
@@ -99,4 +99,4 @@ symtochar.o: symtochar.h
font.o: font.h
z80.o: z80.h z80_private.h
z80_decode.o: z80.h z80_private.h
-audio.o: audio.c audio.h
+audio.o: audio.c audio.h util.h
diff --git a/src/audio.c b/src/audio.c
index b5ce1df..7eef433 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -27,17 +27,41 @@
#include <SDL.h>
#include "audio.h"
+#include "util.h"
-/* ---------------------------------------- INTERFACES
+/* ---------------------------------------- INTERNAL DATA
*/
+static SDL_AudioSpec actual;
+static SDL_AudioDeviceID device;
+
+/* ---------------------------------------- INTERFACES
+*/
int AUDIOInit(void)
{
- return 0;
+ SDL_AudioSpec spec = {0};
+
+ spec.freq = SAMPLE_RATE;
+ spec.format = AUDIO_S8;
+ spec.channels = 1;
+ spec.samples = 4096;
+
+ device = SDL_OpenAudioDevice(NULL, 0, &spec, &actual, 0);
+
+ if (device != 0)
+ {
+ SDL_PauseAudioDevice(device, 0);
+ }
+
+ return device != 0;
}
-void AUDIOQueue(char *buffer, size_t len)
+void AUDIOQueue(Uint8 *buffer, size_t len)
{
+ if (device != 0)
+ {
+ SDL_QueueAudio(device, buffer, len);
+ }
}
diff --git a/src/audio.h b/src/audio.h
index 83980e4..b799abd 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -28,6 +28,11 @@
#define ESPEC_AUDIO_H
#include <stdlib.h>
+#include <SDL.h>
+
+/* The sample rate
+*/
+#define SAMPLE_RATE 48000
/* ---------------------------------------- INTERFACES
*/
@@ -39,10 +44,11 @@
int AUDIOInit(void);
-/* Add the passed buffer to the sound queue. It is a signed 8-bit mono
+/* Add the passed buffer to the sound queue. It is an unsigned 8-bit mono
sample which will be converted as appropriate by the audio interface.
+ Well turns out SDL does that for you.
*/
-void AUDIOQueue(char *buffer, size_t len);
+void AUDIOQueue(Uint8 *buffer, size_t len);
#endif
diff --git a/src/spec.c b/src/spec.c
index 2ddf661..6393ef2 100644
--- a/src/spec.c
+++ b/src/spec.c
@@ -26,6 +26,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <SDL.h>
#include "spec.h"
#include "snap.h"
#include "gfx.h"
@@ -33,6 +34,7 @@
#include "config.h"
#include "exit.h"
#include "tape.h"
+#include "audio.h"
#include "util.h"
#ifndef TRUE
@@ -77,7 +79,9 @@ static Z80Byte mem[0x10000];
/* Number of cycles per scan lines and scan line control
*/
-static Z80Val SCAN_CYCLES=224;
+#define SCAN_CYCLES 224
+#define FRAME_CYCLES 69888
+
static int scanline=0;
static int enable_screen=TRUE;
@@ -202,6 +206,22 @@ static const MatrixMap keymap[]=
{SDLK_UNKNOWN, 0,0,0,0},
};
+static size_t sample_buff_sz;
+static double cycles_per_sample;
+
+typedef struct SoundRecord
+{
+ Z80Val cycle;
+ Uint8 level;
+ struct SoundRecord *next;
+} SoundRecord;
+
+static struct
+{
+ SoundRecord *head;
+ SoundRecord *tail;
+ Uint8 last_level;
+} sound;
/* ---------------------------------------- DEBUG FUNCTIONS
*/
@@ -251,6 +271,39 @@ static void ClearKeys(void)
}
}
+static void AddSoundRecord(Z80Val cycle, Uint8 level)
+{
+ SoundRecord *new = Malloc(sizeof *new);
+
+ new->cycle = cycle;
+ new->level = level;
+ new->next = NULL;
+
+ if (sound.tail)
+ {
+ sound.tail->next = new;
+ sound.tail = new;
+ }
+ else
+ {
+ sound.head = new;
+ sound.tail = new;
+ }
+}
+
+static void ClearSoundRecord(void)
+{
+ while(sound.head)
+ {
+ SoundRecord *next = sound.head->next;
+ free(sound.head);
+ sound.head = next;
+ }
+
+ sound.tail = NULL;
+}
+
+
static void DrawScanlineAt(int y, int sline)
{
int aline;
@@ -485,6 +538,55 @@ static int CheckTimers(Z80 *z80, Z80Val val)
{
selected_out_tape--;
}
+
+ if (IConfig(CONF_SOUND))
+ {
+ Uint8 *sample = Malloc(sample_buff_sz);
+ SoundRecord *current = sound.head;
+ int offset = 0;
+
+ memset(sample, 0, sample_buff_sz);
+
+ if (current)
+ {
+ int f;
+ int first_offset = (int)(current->cycle / cycles_per_sample);
+
+ for(f = 0; f < first_offset; f++)
+ {
+ sample[f] = sound.last_level;
+ }
+
+ while(current)
+ {
+ int next_offset;
+
+ if (current->next)
+ {
+ next_offset = (int)(current->cycle / cycles_per_sample);
+ }
+ else
+ {
+ next_offset = sample_buff_sz;
+ }
+
+ for(f = offset; f < next_offset; f++)
+ {
+ sample[f] = current->level;
+ }
+
+ offset = next_offset;
+ sound.last_level = current->level;
+ current = current->next;
+ }
+ }
+
+ AUDIOQueue(sample, sample_buff_sz);
+
+ free(sample);
+ ClearSoundRecord();
+ }
+
}
/* Draw scanline
@@ -494,7 +596,7 @@ static int CheckTimers(Z80 *z80, Z80Val val)
if (y>=0 && y<GFX_HEIGHT && enable_screen)
DrawScanline(y);
- /* TODO: Process sound emulation */
+ Z80SetTimer(z80, eZ80_Timer_1, 0);
}
return TRUE;
@@ -532,6 +634,14 @@ void SPECInit(Z80 *z80)
Z80LodgeCallback(z80,eZ80_EDHook,EDCallback);
Z80LodgeCallback(z80,eZ80_Instruction,CheckTimers);
+ /* Calculate the audio buffer sizes
+ */
+ if (IConfig(CONF_SOUND))
+ {
+ sample_buff_sz = SAMPLE_RATE / IConfig(CONF_FRAMES_PER_SEC);
+ cycles_per_sample = (double)FRAME_CYCLES / (double)SAMPLE_RATE;
+ }
+
SPECReset(z80);
}
@@ -622,6 +732,7 @@ void SPECWritePort(Z80 *z80, Z80Word port, Z80Byte val)
{
case 0xfe: /* ULA */
border=val&0x07;
+ AddSoundRecord(Z80GetTimer(z80, eZ80_Timer_1), val & 0x10 ? 64 : 0);
break;
case 0xfb: /* TODO: ZX Printer */
diff --git a/src/z80.c b/src/z80.c
index deccb8c..5935d5d 100644
--- a/src/z80.c
+++ b/src/z80.c
@@ -149,9 +149,15 @@ Z80 *Z80Init(Z80ReadMemory read_memory,
void Z80Reset(Z80 *cpu)
{
+ int f;
+
cpu->cycle=0;
cpu->PC=0;
+ cpu->timer[eZ80_Timer_1] = 0;
+ cpu->timer[eZ80_Timer_2] = 0;
+ cpu->timer[eZ80_Timer_3] = 0;
+
cpu->AF.w=0xffff;
cpu->BC.w=0xffff;
cpu->DE.w=0xffff;
@@ -362,4 +368,17 @@ const char *Z80Disassemble(Z80 *cpu, Z80Word *pc)
#endif
}
+
+Z80Val Z80GetTimer(Z80 *cpu, Z80Timer timer)
+{
+ return cpu->timer[timer];
+}
+
+
+void Z80SetTimer(Z80 *cpu, Z80Timer timer, Z80Val value)
+{
+ cpu->timer[timer] = value;
+}
+
+
/* END OF FILE */
diff --git a/src/z80.h b/src/z80.h
index 66385e7..601127d 100644
--- a/src/z80.h
+++ b/src/z80.h
@@ -151,6 +151,16 @@ typedef struct
} Z80Label;
+/* The Z80 provides a number of cycle timers
+*/
+typedef enum
+{
+ eZ80_Timer_1,
+ eZ80_Timer_2,
+ eZ80_Timer_3
+} Z80Timer;
+
+
/* ---------------------------------------- INTERFACES
*/
@@ -230,6 +240,12 @@ void Z80GetState(Z80 *cpu, Z80State *state);
void Z80SetState(Z80 *cpu, const Z80State *state);
+/* Timers that count in cycle counts
+*/
+Z80Val Z80GetTimer(Z80 *cpu, Z80Timer timer);
+void Z80SetTimer(Z80 *cpu, Z80Timer timer, Z80Val cycles);
+
+
/* Set address to label mappings for the disassembler
*/
void Z80SetLabels(Z80Label labels[]);
diff --git a/src/z80_private.h b/src/z80_private.h
index 628e23d..b015d6f 100644
--- a/src/z80_private.h
+++ b/src/z80_private.h
@@ -96,6 +96,8 @@ struct Z80
Z80Callback callback[eZ80_NO_CALLBACK][MAX_PER_CALLBACK];
int last_cb;
+
+ Z80Val timer[3];
};
@@ -159,7 +161,13 @@ struct Z80
#define IS_IX_IY (cpu->shift==0xdd || cpu->shift==0xfd)
#define OFFSET(off) off=(IS_IX_IY ? (Z80Relative)FETCH_BYTE:0)
-#define TSTATE(n) cpu->cycle+=n
+#define TSTATE(n) do \
+ { \
+ cpu->cycle+=n; \
+ cpu->timer[eZ80_Timer_1]+=n; \
+ cpu->timer[eZ80_Timer_2]+=n; \
+ cpu->timer[eZ80_Timer_3]+=n; \
+ } while(0)
#define ADD_R(v) cpu->R=((cpu->R&0x80)|((cpu->R+(v))&0x7f))
#define INC_R ADD_R(1)