323 lines
6.4 KiB
C
323 lines
6.4 KiB
C
//
|
|
// Created by bruno on 13. 7. 2026.
|
|
//
|
|
|
|
#include "encoder.h"
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_audio.h>
|
|
#include <SDL_stdinc.h>
|
|
|
|
#include "goertzel.h"
|
|
|
|
SDL_AudioDeviceID dev;
|
|
AudioData encodedAudioData;
|
|
|
|
Encoder encoder;
|
|
|
|
// Internal audio bus
|
|
float internalBusSamples[INTERNAL_BUFFER_SIZE] = {0};
|
|
volatile uint32_t internalBusWrite = 0;
|
|
volatile uint32_t internalBusRead = 0;
|
|
|
|
#define PREAMBLE_SYMBOLS 128
|
|
|
|
const uint8_t syncBytes[4] =
|
|
{
|
|
'B',
|
|
'R',
|
|
'N',
|
|
'Q'
|
|
};
|
|
|
|
static void encoder_set_bit(
|
|
AudioData *audio,
|
|
Encoder *enc,
|
|
unsigned bitIndex,
|
|
int bit)
|
|
{
|
|
/*
|
|
float spacing =
|
|
(float)(enc->endFreq - enc->startFreq) /
|
|
(enc->bitCount * 2 - 1);
|
|
*/
|
|
|
|
uint16_t freq =
|
|
enc->startFreq +
|
|
enc->dataSpacing * (bitIndex * 2 + bit);
|
|
|
|
freq = goertzel_bin_freq(
|
|
freq,
|
|
SAMPLE_RATE,
|
|
enc->symbolSamples
|
|
);
|
|
|
|
SynthVoice *v =
|
|
&audio->synthVoices[bitIndex + 1];
|
|
|
|
v->frequency = freq;
|
|
v->phaseStep =
|
|
((uint64_t)freq << 32) / SAMPLE_RATE;
|
|
v->volume = 255;
|
|
}
|
|
|
|
static int encoder_get_bit(Encoder *enc) {
|
|
if (enc->byteIndex >= enc->bufferSize)
|
|
return -1;
|
|
|
|
int bit = (enc->buffer[enc->byteIndex] >> enc->bitIndex) & 1;
|
|
|
|
enc->bitIndex++;
|
|
|
|
if (enc->bitIndex == 8) {
|
|
enc->bitIndex = 0;
|
|
enc->byteIndex++;
|
|
}
|
|
|
|
return bit;
|
|
}
|
|
|
|
static void encoder_clock(
|
|
Encoder *enc,
|
|
AudioData *audio)
|
|
{
|
|
enc->clockState ^= 1;
|
|
|
|
SynthVoice *clock =
|
|
&audio->synthVoices[0];
|
|
|
|
uint16_t freq =
|
|
enc->clockState ?
|
|
enc->clock1 :
|
|
enc->clock0;
|
|
|
|
clock->frequency = freq;
|
|
clock->phaseStep =
|
|
((uint64_t)freq << 32) /
|
|
SAMPLE_RATE;
|
|
clock->volume = 255;
|
|
}
|
|
|
|
void encoder_next_symbol(
|
|
Encoder *enc,
|
|
AudioData *audio)
|
|
{
|
|
encoder_clock(enc, audio);
|
|
|
|
switch(enc->state)
|
|
{
|
|
case TX_PREAMBLE:
|
|
|
|
/* transmit all-zero symbol */
|
|
|
|
for(unsigned i=0;i<enc->bitCount;i++)
|
|
encoder_set_bit(audio,enc,i,0);
|
|
|
|
enc->preambleSymbols++;
|
|
|
|
if(enc->preambleSymbols >= PREAMBLE_SYMBOLS)
|
|
{
|
|
enc->syncIndex = 0;
|
|
enc->bitIndex = 0;
|
|
enc->state = TX_SYNC;
|
|
}
|
|
|
|
break;
|
|
|
|
case TX_SYNC:
|
|
{
|
|
uint8_t byte =
|
|
syncBytes[enc->syncIndex];
|
|
|
|
for(unsigned i=0;i<enc->bitCount;i++)
|
|
{
|
|
encoder_set_bit(
|
|
audio,
|
|
enc,
|
|
i,
|
|
(byte >> i) & 1);
|
|
}
|
|
|
|
enc->syncIndex++;
|
|
|
|
if(enc->syncIndex ==
|
|
sizeof(syncBytes))
|
|
{
|
|
enc->state = TX_DATA;
|
|
enc->byteIndex = 0;
|
|
enc->bitIndex = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TX_DATA:
|
|
{
|
|
int finished = 0;
|
|
|
|
for(unsigned i=0;i<enc->bitCount;i++)
|
|
{
|
|
int bit =
|
|
encoder_get_bit(enc);
|
|
|
|
if(bit < 0)
|
|
{
|
|
bit = 0;
|
|
finished++;
|
|
}
|
|
|
|
encoder_set_bit(
|
|
audio,
|
|
enc,
|
|
i,
|
|
bit);
|
|
}
|
|
|
|
if(finished ==
|
|
enc->bitCount)
|
|
{
|
|
enc->state = TX_END;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TX_END:
|
|
|
|
stopEncoder(enc);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
enc->samplesRemaining =
|
|
enc->symbolSamples;
|
|
}
|
|
|
|
void startEncoder(Encoder *enc)
|
|
{
|
|
enc->state = TX_PREAMBLE;
|
|
enc->transmitting = true;
|
|
|
|
enc->byteIndex = 0;
|
|
enc->bitIndex = 0;
|
|
enc->samplesRemaining = 0;
|
|
enc->clockState = false;
|
|
|
|
enc->preambleSymbols = 0;
|
|
enc->syncIndex = 0;
|
|
}
|
|
|
|
void stopEncoder(Encoder *enc)
|
|
{
|
|
enc->transmitting=false;
|
|
enc->state=TX_IDLE;
|
|
|
|
for(int i=0;i<MAX_BITS+1;i++)
|
|
{
|
|
encodedAudioData.synthVoices[i].volume=0;
|
|
}
|
|
}
|
|
|
|
void audio_callback(void *userdata, Uint8 *stream, int len) {
|
|
AudioData *audio = userdata;
|
|
|
|
Encoder *enc = audio->encoder;
|
|
|
|
if (enc->state == TX_END ||!enc->transmitting) {
|
|
memset(stream, 0, len);
|
|
return;
|
|
}
|
|
|
|
float *out = (float *) stream;
|
|
int samples = len / sizeof(float);
|
|
|
|
for (int i = 0; i < samples; i++) {
|
|
if (enc->samplesRemaining == 0)
|
|
encoder_next_symbol(enc, audio);
|
|
|
|
enc->samplesRemaining--;
|
|
|
|
float mix = 0.0f;
|
|
int active = 0;
|
|
|
|
for (unsigned v = 0; v < enc->bitCount + 1; v++) {
|
|
SynthVoice *voice = &audio->synthVoices[v];
|
|
|
|
if (voice->volume == 0)
|
|
continue;
|
|
|
|
voice->phase += voice->phaseStep;
|
|
|
|
float sample = sinf(
|
|
((double) voice->phase / 4294967296.0) *
|
|
(2.0 * M_PI));
|
|
|
|
mix += sample * (voice->volume / 255.0f);
|
|
|
|
active++;
|
|
}
|
|
|
|
float output = active ? mix / (active + 1) : 0.0f;
|
|
out[i] = output;
|
|
|
|
// Write to internal bus for loopback
|
|
internalBusSamples[internalBusWrite] = output;
|
|
internalBusWrite = (internalBusWrite + 1) % INTERNAL_BUFFER_SIZE;
|
|
}
|
|
}
|
|
|
|
void initEncoder()
|
|
{
|
|
memset(&encoder,0,sizeof(Encoder));
|
|
|
|
encoder.bufferSize = 0;
|
|
encoder.bitCount = 8;
|
|
encoder.byteIndex = 0;
|
|
|
|
encoder.startFreq = 1000;
|
|
encoder.endFreq = 5000;
|
|
encoder.dataSpacing = 250;
|
|
encoder.symbolSamples = SAMPLE_RATE / 50;
|
|
|
|
encoder.clock0 = goertzel_bin_freq(
|
|
6000,
|
|
SAMPLE_RATE,
|
|
encoder.symbolSamples
|
|
);
|
|
|
|
encoder.clock1 = goertzel_bin_freq(
|
|
6300,
|
|
SAMPLE_RATE,
|
|
encoder.symbolSamples
|
|
);
|
|
|
|
|
|
encoder.state = TX_IDLE;
|
|
encoder.transmitting = false;
|
|
|
|
}
|
|
|
|
void initAudioEncoder() {
|
|
memset(&encodedAudioData, 0, sizeof(AudioData));
|
|
initEncoder();
|
|
|
|
SDL_AudioSpec spec = {0};
|
|
spec.freq = SAMPLE_RATE;
|
|
spec.format = AUDIO_F32SYS;
|
|
spec.channels = 1;
|
|
spec.samples = 4096;
|
|
spec.callback = audio_callback;
|
|
encodedAudioData.encoder = &encoder;
|
|
spec.userdata = &encodedAudioData;
|
|
|
|
dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
|
|
printf("Encoder device opened: dev=%u\n", dev);
|
|
if (dev == 0) {
|
|
printf("Failed to open audio encoder: %s\n", SDL_GetError());
|
|
SDL_Quit();
|
|
}
|
|
SDL_PauseAudioDevice(dev, 0);
|
|
}
|