devices/beeper.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
#define MINIAUDIO_IMPLEMENTATION
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MA_NO_RESOURCE_MANAGER
#define MA_NO_NODE_GRAPH
#define MA_NO_ENGINE
#define MA_NO_GENERATION
#include "miniaudio.h"
#include "../xyw.h"
/* beeper device */
#define BEEPER_PITCH 0x0
#define BEEPER_LENGTH 0x2
#define BEEPER_LEVEL 0x4
/* 8-bit era constraints */
#define BEEPER_MAX_FREQ 0x2000 // 8192 Hz - typical PC speaker limit
#define BEEPER_MAX_LEVEL 0x0F // 4-bit volume (0-15)
#define BEEPER_SAMPLE_RATE 22050 // Period-accurate low sample rate
// State for the beeper callback
typedef struct
{
double phase;
double phase_increment;
float amplitude;
ma_uint64 samples_remaining;
} beeper_state;
// Audio callback to generate the square wave tone
static void beeper_callback(ma_device *device, void *output, const void *input, ma_uint32 frame_count)
{
beeper_state *state = (beeper_state *)device->pUserData;
float *out = (float *)output;
(void)input;
for (ma_uint32 i = 0; i < frame_count; i++)
{
if (state->samples_remaining == 0)
{
out[i] = 0.0f;
}
else
{
// Generate square wave sample (-amplitude or +amplitude)
out[i] = (state->phase < MA_PI) ? state->amplitude : -state->amplitude;
state->phase += state->phase_increment;
if (state->phase >= MA_TAU)
state->phase -= MA_TAU;
state->samples_remaining--;
}
}
}
// Helper function to play a tone with given frequency, duration, and level
static void beeper_play_tone(xyw_word freq, unsigned long duration_ms, xyw_byte level)
{
ma_device_config config;
ma_device device;
beeper_state state = {0};
// Initialize state for the tone
state.phase = 0.0;
state.phase_increment = MA_TAU * (double)freq / BEEPER_SAMPLE_RATE;
state.amplitude = (float)level / (float)BEEPER_MAX_LEVEL;
state.samples_remaining = (ma_uint64)(BEEPER_SAMPLE_RATE * duration_ms / 1000);
// Initialize and start the audio device
config = ma_device_config_init(ma_device_type_playback);
config.playback.format = ma_format_f32;
config.playback.channels = 1;
config.sampleRate = BEEPER_SAMPLE_RATE;
config.dataCallback = beeper_callback;
config.pUserData = &state;
if (ma_device_init(NULL, &config, &device) != MA_SUCCESS)
return;
if (ma_device_start(&device) != MA_SUCCESS)
{
ma_device_uninit(&device);
return;
}
// Wait for playback to complete
while (state.samples_remaining > 0)
ma_sleep(1);
// Brief drain delay for audio buffers to ensure the tone finishes playing
ma_sleep(10);
ma_device_uninit(&device);
}
void beeper_output(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
(void)error;
switch (addr)
{
case BEEPER_PITCH:
case BEEPER_PITCH + 1:
case BEEPER_LENGTH:
case BEEPER_LENGTH + 1:
// Just store the value, beep triggers on level write
break;
case BEEPER_LEVEL:
{
// Get pitch (2 bytes, big-endian)
xyw_word pitch = XYW_PEEKW(&data[BEEPER_PITCH]);
// Get length in 1/256 second units (2 bytes, big-endian)
xyw_word length = XYW_PEEKW(&data[BEEPER_LENGTH]);
// Get volume level (0-15)
xyw_byte level = data[BEEPER_LEVEL];
// Clamp to 8-bit era constraints
if (pitch > BEEPER_MAX_FREQ)
pitch = BEEPER_MAX_FREQ;
if (level > BEEPER_MAX_LEVEL)
level = BEEPER_MAX_LEVEL;
// Only beep if level > 0 and pitch > 0 and length > 0
if (level > 0 && pitch > 0 && length > 0)
{
// Convert length from 1/256 seconds to milliseconds
// 1/256 second = 1000/256 ms ≈ 3.90625 ms
unsigned long duration_ms = ((unsigned long)length * 1000) / 256;
if (duration_ms == 0)
duration_ms = 1;
beeper_play_tone(pitch, duration_ms, level);
}
break;
}
}
}
xyw_byte beeper_input(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
(void)error;
return data[addr];
}
|