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
147 lines
5.5 KiB
C
147 lines
5.5 KiB
C
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <time.h>
|
||
#include "zebra.h"
|
||
#include "modem.h"
|
||
|
||
static void usage(const char *prog) {
|
||
fprintf(stderr,
|
||
"usage: %s -s SINK_INDEX | -n SINK_NAME [-b BAUD] [-r REV_SINK] [-l]\n"
|
||
"\n"
|
||
" reads stdin, transmits via PulseAudio volume modulation\n"
|
||
"\n"
|
||
" -s INDEX target sink-input by PulseAudio index\n"
|
||
" -n NAME target sink-input by name/app substring match\n"
|
||
" -b BAUD baud rate (default %d, max %d) — ignored when -r is used\n"
|
||
" -r SINK auto-negotiate: listen on SINK for RX handshake (NAME or INDEX)\n"
|
||
" -l list all sink inputs and exit\n",
|
||
prog, ZEBRA_BAUD_DEFAULT, ZEBRA_BAUD_MAX);
|
||
}
|
||
|
||
/* resolve NAME-or-INDEX to a PA sink index */
|
||
static uint32_t resolve(zebra_pulse_t *z, const char *arg) {
|
||
char *end;
|
||
long idx = strtol(arg, &end, 10);
|
||
if (*end == '\0' && idx >= 0) return (uint32_t)idx;
|
||
zebra_sink_t s;
|
||
if (zebra_find_sink(z, arg, &s) < 0) {
|
||
fprintf(stderr, "error: no sink matching '%s' — run ./tx -l to list\n", arg);
|
||
exit(1);
|
||
}
|
||
fprintf(stderr, " found: [%u] %s (%s)\n", s.index, s.name, s.app_name);
|
||
return s.index;
|
||
}
|
||
|
||
int main(int argc, char **argv) {
|
||
int baud = ZEBRA_BAUD_DEFAULT;
|
||
int sink_index = -1;
|
||
int do_list = 0;
|
||
char sink_name[256] = {0};
|
||
char *rev_arg = NULL; /* reverse channel for auto-negotiate */
|
||
int opt;
|
||
|
||
while ((opt = getopt(argc, argv, "s:n:b:r:lh")) != -1) {
|
||
switch (opt) {
|
||
case 's': sink_index = atoi(optarg); break;
|
||
case 'n': snprintf(sink_name, sizeof(sink_name), "%s", optarg); break;
|
||
case 'b': baud = atoi(optarg); break;
|
||
case 'r': rev_arg = optarg; break;
|
||
case 'l': do_list = 1; break;
|
||
case 'h': usage(argv[0]); return 0;
|
||
default: usage(argv[0]); return 1;
|
||
}
|
||
}
|
||
|
||
zebra_pulse_t z;
|
||
if (zebra_pulse_connect(&z, "zebra-tx") < 0) {
|
||
fprintf(stderr, "error: could not connect to PulseAudio\n");
|
||
return 1;
|
||
}
|
||
|
||
if (do_list) {
|
||
zebra_sink_t buf[64];
|
||
int n = zebra_list_sinks(&z, buf, 64);
|
||
if (n < 0) { fprintf(stderr, "error: list failed\n"); return 1; }
|
||
printf("%-6s %-4s %-3s %s\n", "INDEX", "VOL%", "CH", "NAME (APP)");
|
||
for (int i = 0; i < n; i++)
|
||
printf("%-6u %-4u %-3u %s (%s)\n",
|
||
buf[i].index, buf[i].volume_pct,
|
||
buf[i].channels, buf[i].name, buf[i].app_name);
|
||
zebra_pulse_disconnect(&z);
|
||
return 0;
|
||
}
|
||
|
||
if (sink_index < 0 && !sink_name[0]) {
|
||
fprintf(stderr, "error: specify -s INDEX or -n NAME (use -l to list)\n");
|
||
zebra_pulse_disconnect(&z);
|
||
return 1;
|
||
}
|
||
if (!rev_arg && (baud < ZEBRA_BAUD_MIN || baud > ZEBRA_BAUD_MAX)) {
|
||
fprintf(stderr, "error: baud must be %d–%d\n", ZEBRA_BAUD_MIN, ZEBRA_BAUD_MAX);
|
||
zebra_pulse_disconnect(&z);
|
||
return 1;
|
||
}
|
||
|
||
uint8_t channels = 2;
|
||
if (sink_index < 0) {
|
||
zebra_sink_t s;
|
||
if (zebra_find_sink(&z, sink_name, &s) < 0) {
|
||
fprintf(stderr, "error: sink matching '%s' not found\n", sink_name);
|
||
zebra_pulse_disconnect(&z);
|
||
return 1;
|
||
}
|
||
sink_index = (int)s.index;
|
||
channels = s.channels ? s.channels : 2;
|
||
fprintf(stderr, "sink: [%u] %s (%s)\n", s.index, s.name, s.app_name);
|
||
} else {
|
||
/* resolve channels for numeric index */
|
||
zebra_sink_t buf[64];
|
||
int n = zebra_list_sinks(&z, buf, 64);
|
||
for (int i = 0; i < n; i++)
|
||
if (buf[i].index == (uint32_t)sink_index)
|
||
{ channels = buf[i].channels ? buf[i].channels : 2; break; }
|
||
}
|
||
|
||
/* auto-negotiate baud from RX if reverse channel specified */
|
||
if (rev_arg) {
|
||
if (baud != ZEBRA_BAUD_DEFAULT)
|
||
fprintf(stderr, "tx: -b ignored, using negotiated baud\n");
|
||
fprintf(stderr, "tx: waiting for RX handshake on '%s' (30s timeout)...\n",
|
||
rev_arg);
|
||
uint32_t rev_sink = resolve(&z, rev_arg);
|
||
uint16_t neg_baud = 0;
|
||
if (zebra_recv_handshake(&z, rev_sink, 30000, &neg_baud) < 0) {
|
||
fprintf(stderr, "error: handshake timeout — no frame received\n");
|
||
zebra_pulse_disconnect(&z);
|
||
return 1;
|
||
}
|
||
baud = (int)neg_baud;
|
||
fprintf(stderr, "tx: negotiated %d baud — waiting for READY\n", baud);
|
||
if (zebra_recv_ready(&z, rev_sink, ZEBRA_HS_READY_WAIT) < 0) {
|
||
fprintf(stderr, "error: READY timeout — RX did not confirm\n");
|
||
zebra_pulse_disconnect(&z);
|
||
return 1;
|
||
}
|
||
fprintf(stderr, "tx: READY received — sending data\n");
|
||
}
|
||
|
||
long period_ns = 1000000000L / baud;
|
||
fprintf(stderr, "tx: %d baud, %ld ms/symbol, sink %d, ch %u\n",
|
||
baud, period_ns / 1000000L, sink_index, channels);
|
||
|
||
zebra_set_volume_fast(&z, (uint32_t)sink_index, channels, ZEBRA_VOL_MARK);
|
||
|
||
struct timespec next;
|
||
clock_gettime(CLOCK_MONOTONIC, &next);
|
||
ts_add_ns(&next, period_ns * 10); /* 10-symbol MARK preamble — let RX settle */
|
||
|
||
int c;
|
||
while ((c = fgetc(stdin)) != EOF)
|
||
zebra_send_byte(&z, (uint32_t)sink_index, channels, (uint8_t)c, &next, period_ns);
|
||
|
||
zebra_set_volume_fast(&z, (uint32_t)sink_index, channels, ZEBRA_VOL_MARK);
|
||
zebra_pulse_disconnect(&z);
|
||
return 0;
|
||
}
|