// // Created by bruno on 13. 7. 2026. // #include "encoder.h" #include #include #include #include "goertzel.h" #include "modem_settings.h" #include "packet.h" SDL_AudioDeviceID dev; AudioData encodedAudioData; Encoder encoder; const uint8_t syncSymbols[] = { 2,0, 2,2, 6,2, 1,2 }; static void encoder_set_symbol( AudioData *audio, Encoder *enc, unsigned symbol) { /* symbol: 0 ... number of FSK tones-1 */ uint16_t freq = enc->startFreq + symbol * enc->dataSpacing; freq = goertzel_bin_freq( freq, enc->sampleRate, enc->symbolSamples ); SynthVoice *v = &audio->synthVoices[DATA_VOICE]; v->frequency = freq; v->phaseStep = ((uint64_t)freq << 32) / enc->sampleRate; v->volume = 255; } static int encoder_get_symbol( Encoder *enc) { unsigned symbol=0; for(unsigned i=0; ibitsPerSymbol; i++) { if(enc->packetIndex >= enc->packetSize) return -1; int bit = (enc->packet[enc->packetIndex] >>enc->bitIndex)&1; symbol |= bit<bitIndex++; if(enc->bitIndex==8) { enc->bitIndex=0; enc->packetIndex++; } } return symbol; } 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) / (enc->sampleRate ? enc->sampleRate : SAMPLE_RATE); clock->volume = 255; // Debug: print clock toggle //printf("ENCODER CLOCK: toggled to %u Hz (state=%d)\n", freq, enc->clockState); } void encoder_next_symbol( Encoder *enc, AudioData *audio) { encoder_clock(enc,audio); switch(enc->state) { case TX_PREAMBLE: { unsigned symbol = enc->preambleSymbols % enc->toneCount; encoder_set_symbol( audio, enc, symbol); enc->preambleSymbols++; if(enc->preambleSymbols>=PREAMBLE_SYMBOLS) { enc->packetIndex=0; enc->bitIndex=0; enc->state=TX_PACKET; } break; } case TX_PACKET: { int symbol = encoder_get_symbol(enc); if(symbol<0) { enc->state=TX_END; break; } encoder_set_symbol( audio, enc, symbol); break; } case TX_END: stopEncoder(enc); break; default: break; } enc->samplesRemaining= enc->symbolSamples; } void startEncoder(Encoder *enc) { encoder_build_packet(enc, enc->buffer, enc->bufferSize); 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<2;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<2; 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; } } void initEncoder() { memset(&encoder,0,sizeof(Encoder)); encoder.bufferSize = 0; /* 8-FSK: 3 bits per symbol 8 frequencies */ encoder.bitsPerSymbol = 3; encoder.toneCount = 8; encoder.byteIndex = 0; encoder.startFreq = DEFAULT_START_FREQ; encoder.endFreq = DEFAULT_END_FREQ; uint16_t freqRange = encoder.endFreq - encoder.startFreq; /* N tones need N-1 intervals */ encoder.dataSpacing = freqRange / (encoder.toneCount-1); encoder.symbolSamples = SAMPLE_RATE / DEFAULT_SYMBOL_RATE; encoder.clock0 = goertzel_bin_freq( DEFAULT_CLOCK0_FREQ, SAMPLE_RATE, encoder.symbolSamples); encoder.clock1 = goertzel_bin_freq( DEFAULT_CLOCK1_FREQ, 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 = 960; // requested buffer spec.callback = audio_callback; encodedAudioData.encoder = &encoder; spec.userdata = &encodedAudioData; SDL_AudioSpec realSpec = {0}; dev = SDL_OpenAudioDevice(NULL, 0, &spec, &realSpec, 0); printf("Encoder device opened: dev=%u (requested %dHz/%d samples)\n", dev, spec.freq, spec.samples); if (dev == 0) { printf("Failed to open audio encoder: %s\n", SDL_GetError()); SDL_Quit(); } // Update encoder sample rate and symbolSamples based on actual device encoder.sampleRate = realSpec.freq; encoder.symbolSamples = realSpec.freq / DEFAULT_SYMBOL_RATE; encoder.clock0 = goertzel_bin_freq( DEFAULT_CLOCK0_FREQ, encoder.sampleRate, encoder.symbolSamples ); encoder.clock1 = goertzel_bin_freq( DEFAULT_CLOCK1_FREQ, encoder.sampleRate, encoder.symbolSamples ); printf("Encoder real spec: freq=%d channels=%d samples=%d -> symbolSamples=%u\n", realSpec.freq, realSpec.channels, realSpec.samples, encoder.symbolSamples); SDL_PauseAudioDevice(dev, 0); }