all repos — xyw @ 293019a52f305ca205bb91fd2d35b970542d15e2

A minimal virtual machine and assembler for terminals.

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
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <AudioToolbox/AudioToolbox.h>
#include <unistd.h>
#include <math.h>
#elif defined(__linux__)
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kd.h>
#else /* Other Unix (BSD, etc.) */
#include <unistd.h>
#endif
#include "../xyw.h"

/* beeper device */
#define BEEPER_PITCH 0x0
#define BEEPER_LENGTH 0x2
#define BEEPER_LEVEL 0x4

#define BEEPER_MAX_FREQ 0x2000 /* 8192 Hz */
#define BEEPER_MAX_LEVEL 0x0F

#ifdef __APPLE__
/* macOS: Generate sine wave tone using AudioQueue */
#define APPLE_SAMPLE_RATE 44100.0
#define APPLE_NUM_BUFFERS 3

typedef struct
{
    AudioQueueRef queue;
    double phase;
    double phase_increment;
    float amplitude;
    UInt32 samples_remaining;
    int finished;
} AppleToneState;

static void apple_audio_callback(void *user_data, AudioQueueRef queue, AudioQueueBufferRef buffer)
{
    AppleToneState *state = (AppleToneState *)user_data;
    SInt16 *samples = (SInt16 *)buffer->mAudioData;
    UInt32 num_samples = buffer->mAudioDataByteSize / sizeof(SInt16);

    if (state->samples_remaining == 0)
    {
        /* Fill with silence and mark finished */
        for (UInt32 i = 0; i < num_samples; i++)
            samples[i] = 0;
        state->finished = 1;
    }
    else
    {
        UInt32 to_generate = num_samples < state->samples_remaining ? num_samples : state->samples_remaining;
        for (UInt32 i = 0; i < to_generate; i++)
        {
            samples[i] = (SInt16)(sin(state->phase) * 32767.0 * state->amplitude);
            state->phase += state->phase_increment;
            if (state->phase > 2.0 * M_PI)
                state->phase -= 2.0 * M_PI;
        }
        for (UInt32 i = to_generate; i < num_samples; i++)
            samples[i] = 0;
        state->samples_remaining -= to_generate;
    }
    AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
}

static void apple_play_tone(xyw_word freq, unsigned long duration_ms, xyw_byte level)
{
    AppleToneState state = {0};
    AudioStreamBasicDescription format = {0};
    AudioQueueBufferRef buffers[APPLE_NUM_BUFFERS];

    format.mSampleRate = APPLE_SAMPLE_RATE;
    format.mFormatID = kAudioFormatLinearPCM;
    format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
    format.mBitsPerChannel = 16;
    format.mChannelsPerFrame = 1;
    format.mBytesPerFrame = 2;
    format.mFramesPerPacket = 1;
    format.mBytesPerPacket = 2;

    state.phase = 0.0;
    state.phase_increment = 2.0 * M_PI * freq / APPLE_SAMPLE_RATE;
    state.amplitude = (float)level / (float)BEEPER_MAX_LEVEL;
    state.samples_remaining = (UInt32)(APPLE_SAMPLE_RATE * duration_ms / 1000.0);
    state.finished = 0;

    if (AudioQueueNewOutput(&format, apple_audio_callback, &state, NULL, NULL, 0, &state.queue) != noErr)
        return;

    UInt32 buffer_size = 4096;
    for (int i = 0; i < APPLE_NUM_BUFFERS; i++)
    {
        AudioQueueAllocateBuffer(state.queue, buffer_size, &buffers[i]);
        buffers[i]->mAudioDataByteSize = buffer_size;
        apple_audio_callback(&state, state.queue, buffers[i]);
    }

    AudioQueueStart(state.queue, NULL);

    /* Wait for playback to complete */
    while (!state.finished)
        usleep(10000);
    usleep(50000); /* Small delay for buffer drain */

    AudioQueueStop(state.queue, true);
    AudioQueueDispose(state.queue, true);
}
#endif /* __APPLE__ */

#if defined(__linux__)
/* Linux: Use KIOCSOUND ioctl for PC speaker frequency control */
#define CLOCK_TICK_RATE 1193180

static void linux_play_tone(xyw_word freq, unsigned long duration_ms)
{
    int fd = open("/dev/console", O_WRONLY);
    if (fd < 0)
        fd = open("/dev/tty0", O_WRONLY);

    if (fd >= 0 && freq > 0)
    {
        /* Set frequency: KIOCSOUND takes clock_tick_rate / frequency, or 0 to stop */
        unsigned int period = CLOCK_TICK_RATE / freq;
        ioctl(fd, KIOCSOUND, period);
        usleep((useconds_t)duration_ms * 1000);
        ioctl(fd, KIOCSOUND, 0); /* Stop */
        close(fd);
    }
    else
    {
        /* Fallback: BEL character (no frequency control) */
        if (fd >= 0)
            close(fd);
        fputc('\a', stdout);
        fflush(stdout);
        usleep((useconds_t)duration_ms * 1000);
    }
}
#endif /* __linux__ */

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 values */
        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;

#ifdef _WIN32
            /* Windows Beep() function - ignores volume level */
            (void)level;
            Beep((DWORD)pitch, (DWORD)duration_ms);
#elif defined(__APPLE__)
            /* macOS: Generate sine wave via AudioQueue */
            apple_play_tone(pitch, duration_ms, level);
#elif defined(__linux__)
            /* Linux: Use PC speaker via KIOCSOUND ioctl (ignores volume) */
            (void)level;
            linux_play_tone(pitch, duration_ms);
#else
            /* Other Unix: BEL fallback (no frequency control) */
            (void)pitch;
            (void)level;
            fputc('\a', stdout);
            fflush(stdout);
            usleep((useconds_t)duration_ms * 1000);
#endif
        }
        break;
    }
    }
}

xyw_byte beeper_input(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
    (void)error;
    return data[addr];
}