#include <Bela.h>
#include <vector>
#include <fstream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
/*
* ============================================================================
* 铁板反射法 — Bela播放+录音+UDP实时发送 (v9: 2麦克风版本)
*
* 音频线程 render() 只做单值写入环形缓冲 (O(1))
* 所有 memcpy / int16转换 / sendto 在 AuxiliaryTask 完成
* 手写环形缓冲, 不依赖任何外部库
*
* 信号: tx_iron_plate_20zhen.bin (ZC 19→42kHz)
* 帧: 50ms ZC, 无静默, 20帧=1秒
* 通道: 2(Mic1近) + 4
* Bela IDE: 96kHz, Block=64
* ============================================================================
*/
#define UDP_PORT 9999
#define MAC_IP "192.168.7.1"
#define BLOCK_SIZE 4800
#define NUM_MICS 2
#define RBUF_SIZE (BLOCK_SIZE * 4)
std::vector<int> gSelectedChannels = {5, 4};
std::string gInputFile = "tx_iron_plate_20zhen.bin";
std::vector<float> gPlayBuffer;
std::vector<std::vector<float>> gRecordBuffers;
unsigned int gReadPtr = 0;
unsigned int gWritePtr = 0;
unsigned int gTotalFrames = 0;
unsigned int gCycleCount = 0;
bool gIsRunning = false;
// ── 手写环形缓冲 (单生产者单消费者, 无需锁) ──
float gRing1[RBUF_SIZE], gRing2[RBUF_SIZE];
volatile unsigned int gWrIdx = 0; // 音频线程写指针
volatile unsigned int gRdIdx = 0; // 辅助线程读指针
unsigned int gUdpIdx = 0;
static inline unsigned int ring_count() {
return (gWrIdx - gRdIdx) % RBUF_SIZE;
}
// UDP
int gSockFd = -1;
struct sockaddr_in gDestAddr;
AuxiliaryTask gUdpTask;
int gFrameCount = 0;
void send_udp_block(void*) {
static int16_t hdr[4];
static int16_t data[BLOCK_SIZE * NUM_MICS];
// 环形缓冲不够一帧→等待
if (((gWrIdx - gRdIdx) % RBUF_SIZE) < BLOCK_SIZE) return;
// 从环形缓冲读出一帧 (辅助线程)
float buf1[BLOCK_SIZE], buf2[BLOCK_SIZE];
unsigned int rd = gRdIdx;
for (int i = 0; i < BLOCK_SIZE; i++) {
buf1[i] = gRing1[rd];
buf2[i] = gRing2[rd];
rd = (rd + 1) % RBUF_SIZE;
}
gRdIdx = rd;
// 打包 header
hdr[0] = (int16_t)(gFrameCount & 0xFFFF);
hdr[1] = (int16_t)((gFrameCount >> 16) & 0xFFFF);
hdr[2] = (int16_t)(gCycleCount & 0xFFFF);
hdr[3] = (int16_t)BLOCK_SIZE;
// float→int16 交错 (2通道: ch2, ch4)
for (int i = 0; i < BLOCK_SIZE; i++) {
data[i*2] = (int16_t)(buf1[i] * 32767.0f);
data[i*2+1] = (int16_t)(buf2[i] * 32767.0f);
}
sendto(gSockFd, hdr, sizeof(hdr), 0,
(struct sockaddr*)&gDestAddr, sizeof(gDestAddr));
sendto(gSockFd, data, sizeof(data), 0,
(struct sockaddr*)&gDestAddr, sizeof(gDestAddr));
gFrameCount++;
}
void restart_playback() {
gReadPtr = 0;
gWritePtr = 0;
gCycleCount++;
gIsRunning = true;
}
bool setup(BelaContext *context, void *userData) {
if (context->audioSampleRate != 96000) {
rt_printf("ERR: sample rate must be 96000, got %f\n",
context->audioSampleRate);
return false;
}
std::ifstream infile(gInputFile, std::ios::binary | std::ios::ate);
if (!infile) {
rt_printf("ERR: cannot open %s\n", gInputFile.c_str());
return false;
}
gTotalFrames = (size_t)infile.tellg() / sizeof(float);
infile.seekg(0, std::ios::beg);
gPlayBuffer.resize(gTotalFrames);
infile.read((char*)gPlayBuffer.data(), gTotalFrames * sizeof(float));
rt_printf("FILE %s (%.1fs, %u samples)\n", gInputFile.c_str(),
gTotalFrames/(float)context->audioSampleRate, gTotalFrames);
gRecordBuffers.resize(gSelectedChannels.size());
for (auto& buf : gRecordBuffers) buf.resize(gTotalFrames);
gSockFd = socket(AF_INET, SOCK_DGRAM, 0);
if (gSockFd < 0) {
rt_printf("ERR: socket failed\n");
return false;
}
int sndbuf = 256 * 1024;
setsockopt(gSockFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
gDestAddr.sin_family = AF_INET;
gDestAddr.sin_port = htons(UDP_PORT);
inet_pton(AF_INET, MAC_IP, &gDestAddr.sin_addr);
gUdpTask = Bela_createAuxiliaryTask(send_udp_block, 85, "udp-send");
rt_printf("UDP 2ch → %s:%d | pkt=%dB | rbuf=%d\n",
MAC_IP, UDP_PORT,
(int)(sizeof(int16_t)*(4 + BLOCK_SIZE*NUM_MICS)), RBUF_SIZE);
gIsRunning = true;
return true;
}
void render(BelaContext *context, void *userData) {
for (unsigned int n = 0; n < context->audioFrames; n++) {
float out = 0;
if (gReadPtr < gTotalFrames) {
out = gPlayBuffer[gReadPtr++];
}
for (unsigned int ch = 0; ch < context->audioOutChannels; ch++) {
audioWrite(context, n, ch, out);
}
if (gWritePtr < gTotalFrames) {
float m1 = audioRead(context, n, gSelectedChannels[0]);
float m4 = audioRead(context, n, gSelectedChannels[1]);
gRecordBuffers[0][gWritePtr] = m1;
gRecordBuffers[1][gWritePtr] = m4;
// 单值写入环形缓冲
unsigned int wr = gWrIdx;
gRing1[wr] = m1;
gRing2[wr] = m4;
gWrIdx = (wr + 1) % RBUF_SIZE;
gUdpIdx++;
if (gUdpIdx >= BLOCK_SIZE) {
gUdpIdx = 0;
Bela_scheduleAuxiliaryTask(gUdpTask);
}
gWritePtr++;
} else if (gIsRunning) {
rt_printf("LOOP %d\n", gCycleCount + 1);
restart_playback();
}
}
}
void cleanup(BelaContext *context, void *userData) {
if (gWritePtr > 0) {
rt_printf("SAVE WAV...\n");
for (unsigned int i = 0; i < gSelectedChannels.size(); i++) {
std::string fname = std::to_string(gSelectedChannels[i]) + "_000.wav";
std::ofstream out(fname, std::ios::binary);
if (!out) { rt_printf("ERR: %s\n", fname.c_str()); continue; }
short nc = 1;
int ds = gWritePtr * nc * 2;
int fsz = 36 + ds;
out.write("RIFF", 4); out.write((char*)&fsz, 4);
out.write("WAVEfmt ", 8);
int s1 = 16; out.write((char*)&s1, 4);
short af = 1; out.write((char*)&af, 2);
out.write((char*)&nc, 2);
int sr = context->audioSampleRate;
out.write((char*)&sr, 4);
int br = sr * nc * 2; out.write((char*)&br, 4);
short ba = nc * 2; out.write((char*)&ba, 2);
short bps = 16; out.write((char*)&bps, 2);
out.write("data", 4); out.write((char*)&ds, 4);
for (unsigned int t = 0; t < gWritePtr; t++) {
float s = gRecordBuffers[i][t];
if (s > 1.0f) s = 1.0f; if (s < -1.0f) s = -1.0f;
short pcm = (short)(s * 32767.0f);
out.write((char*)&pcm, 2);
}
out.close();
rt_printf("OK %s\n", fname.c_str());
}
}
close(gSockFd);
rt_printf("UDP closed, %d blocks\n", gFrameCount);
}