Source: ~/.unfirehose/unfirehose.db (project_id=81, 4 sessions covering
2026-03-29 through 2026-04-05). Reconstructed via chronological replay
of Write/Edit tool_input on file_paths under /home/fox/zebra-report/.
stats:
files reconstructed: 20
writes baselined: all (zero missing)
edits applied: 68
edits unapplied: 8 (1 SKIP pre-baseline, 6 FAIL old_string drift, 1 AMBIGUOUS)
unapplied edits represent small drift in 6 files; baseline content for
each is intact. quality verification deferred to phase 2.
recovered tree:
CLAUDE.md, Makefile
src/{tx,rx,pulse,carrier,chat,bt}.c
include/{modem,zebra}.h
test/{functional,integration,unit}.c, test/test.h
web/{index,kernel}.html, web/blog/style.css
blog/build.py, blog/posts/{001-volume-modem,002-sse-chatroom}.md
report: /tmp/zebra_recover_report.txt
script: /tmp/zebra_recover.py
188 lines
5.8 KiB
C
188 lines
5.8 KiB
C
/*
|
|
* carrier — create a named stereo PA sink input and hold it open
|
|
*
|
|
* Feeds silence into PulseAudio so the stream appears in pavucontrol
|
|
* and `./tx -l` as a controllable sink input — no browser required.
|
|
*
|
|
* Usage:
|
|
* ./carrier [-n NAME] [-c CHANNELS]
|
|
*
|
|
* -n NAME stream name shown in pavucontrol (default: zebra)
|
|
* -c CHANNELS 1 or 2 (default: 2, stereo required for Battle Toads)
|
|
*
|
|
* Run in background:
|
|
* ./carrier -n zebra-data &
|
|
* ./carrier -n zebra-ctrl &
|
|
* ./tx -l # find indices
|
|
* ./rx -s <data> -t <ctrl>
|
|
* ./tx -s <data> -r <ctrl>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
#include <pulse/pulseaudio.h>
|
|
|
|
static volatile int running = 1;
|
|
|
|
static void on_signal(int s) { (void)s; running = 0; }
|
|
|
|
/* ------------------------------------------------------------------ *
|
|
* PA callbacks *
|
|
* ------------------------------------------------------------------ */
|
|
|
|
static void stream_write_cb(pa_stream *s, size_t nbytes, void *ud) {
|
|
(void)ud;
|
|
/* feed silence — all zeros at whatever sample width */
|
|
void *buf = NULL;
|
|
size_t len = nbytes;
|
|
if (pa_stream_begin_write(s, &buf, &len) < 0 || !buf) return;
|
|
memset(buf, 0, len);
|
|
pa_stream_write(s, buf, len, NULL, 0, PA_SEEK_RELATIVE);
|
|
}
|
|
|
|
static void ctx_state_cb(pa_context *ctx, void *ud) {
|
|
pa_threaded_mainloop *ml = ud;
|
|
(void)ctx;
|
|
pa_threaded_mainloop_signal(ml, 0);
|
|
}
|
|
|
|
static void stream_state_cb(pa_stream *s, void *ud) {
|
|
pa_threaded_mainloop *ml = ud;
|
|
(void)s;
|
|
pa_threaded_mainloop_signal(ml, 0);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ *
|
|
* main *
|
|
* ------------------------------------------------------------------ */
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *name = "zebra";
|
|
int channels = 2;
|
|
int opt;
|
|
|
|
while ((opt = getopt(argc, argv, "n:c:h")) != -1) {
|
|
switch (opt) {
|
|
case 'n': name = optarg; break;
|
|
case 'c': channels = atoi(optarg); break;
|
|
case 'h':
|
|
fprintf(stderr,
|
|
"usage: %s [-n NAME] [-c CHANNELS]\n"
|
|
" -n NAME stream name (default: zebra)\n"
|
|
" -c CHANNELS 1 or 2 (default: 2)\n",
|
|
argv[0]);
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
if (channels < 1 || channels > 2) {
|
|
fprintf(stderr, "error: channels must be 1 or 2\n");
|
|
return 1;
|
|
}
|
|
|
|
signal(SIGINT, on_signal);
|
|
signal(SIGTERM, on_signal);
|
|
|
|
pa_threaded_mainloop *ml = pa_threaded_mainloop_new();
|
|
if (!ml) { fprintf(stderr, "error: pa_threaded_mainloop_new\n"); return 1; }
|
|
|
|
pa_mainloop_api *api = pa_threaded_mainloop_get_api(ml);
|
|
pa_context *ctx = pa_context_new(api, name);
|
|
if (!ctx) { fprintf(stderr, "error: pa_context_new\n"); return 1; }
|
|
|
|
pa_context_set_state_callback(ctx, ctx_state_cb, ml);
|
|
pa_threaded_mainloop_lock(ml);
|
|
pa_threaded_mainloop_start(ml);
|
|
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
|
|
|
/* wait for context ready */
|
|
for (;;) {
|
|
pa_context_state_t st = pa_context_get_state(ctx);
|
|
if (st == PA_CONTEXT_READY) break;
|
|
if (!PA_CONTEXT_IS_GOOD(st)) {
|
|
fprintf(stderr, "error: PA context failed\n");
|
|
pa_threaded_mainloop_unlock(ml);
|
|
return 1;
|
|
}
|
|
pa_threaded_mainloop_wait(ml);
|
|
}
|
|
|
|
/* create stereo 44100 Hz stream */
|
|
pa_sample_spec ss = {
|
|
.format = PA_SAMPLE_S16LE,
|
|
.rate = 44100,
|
|
.channels = (uint8_t)channels
|
|
};
|
|
pa_channel_map cm;
|
|
pa_channel_map_init_stereo(&cm);
|
|
if (channels == 1) pa_channel_map_init_mono(&cm);
|
|
|
|
pa_stream *stream = pa_stream_new(ctx, name, &ss, &cm);
|
|
if (!stream) {
|
|
fprintf(stderr, "error: pa_stream_new\n");
|
|
pa_threaded_mainloop_unlock(ml);
|
|
return 1;
|
|
}
|
|
|
|
pa_stream_set_state_callback(stream, stream_state_cb, ml);
|
|
pa_stream_set_write_callback(stream, stream_write_cb, NULL);
|
|
|
|
pa_buffer_attr ba = {
|
|
.maxlength = (uint32_t)-1,
|
|
.tlength = pa_usec_to_bytes(100000, &ss), /* 100ms latency */
|
|
.prebuf = (uint32_t)-1,
|
|
.minreq = (uint32_t)-1,
|
|
.fragsize = (uint32_t)-1
|
|
};
|
|
|
|
pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING
|
|
| PA_STREAM_AUTO_TIMING_UPDATE
|
|
| PA_STREAM_ADJUST_LATENCY;
|
|
|
|
if (pa_stream_connect_playback(stream, NULL, &ba, flags, NULL, NULL) < 0) {
|
|
fprintf(stderr, "error: pa_stream_connect_playback\n");
|
|
pa_threaded_mainloop_unlock(ml);
|
|
return 1;
|
|
}
|
|
|
|
/* wait for stream ready */
|
|
for (;;) {
|
|
pa_stream_state_t st = pa_stream_get_state(stream);
|
|
if (st == PA_STREAM_READY) break;
|
|
if (!PA_STREAM_IS_GOOD(st)) {
|
|
fprintf(stderr, "error: stream failed\n");
|
|
pa_threaded_mainloop_unlock(ml);
|
|
return 1;
|
|
}
|
|
pa_threaded_mainloop_wait(ml);
|
|
}
|
|
|
|
uint32_t idx = pa_stream_get_index(stream);
|
|
pa_threaded_mainloop_unlock(ml);
|
|
|
|
fprintf(stderr, "carrier: [%u] %s ch=%d rate=44100 (ctrl-c to stop)\n",
|
|
idx, name, channels);
|
|
|
|
/* hold open until signal */
|
|
while (running) {
|
|
struct timespec t = {0, 100000000L}; /* 100ms */
|
|
nanosleep(&t, NULL);
|
|
}
|
|
|
|
pa_threaded_mainloop_lock(ml);
|
|
pa_stream_disconnect(stream);
|
|
pa_stream_unref(stream);
|
|
pa_threaded_mainloop_unlock(ml);
|
|
pa_threaded_mainloop_stop(ml);
|
|
pa_context_disconnect(ctx);
|
|
pa_context_unref(ctx);
|
|
pa_threaded_mainloop_free(ml);
|
|
|
|
fprintf(stderr, "carrier: stopped\n");
|
|
return 0;
|
|
}
|