zebra-report/src/tx.c
Russell Ballestrini b77da42bbe phase 1: unfirehose reconstruction from session JSONL ingest
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
2026-05-27 13:51:14 -04:00

147 lines
5.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}