From f02150453f70c122f8299bd785152e843bc052c6 Mon Sep 17 00:00:00 2001 From: Karlis Goba Date: Fri, 22 Nov 2019 13:45:42 +0200 Subject: [PATCH] Moving towards more OOP --- decode_ft8.cpp | 82 ++- ft8/constants.h | 1 + ft8/decode.cpp | 77 ++- ft8/decode.h | 23 + ft8/ldpc.cpp | 169 +++--- ft8/ldpc.h | 20 +- ft8/message77.cpp | 49 ++ ft8/message77.h | 48 ++ ft8/unpack.cpp | 23 +- gen_ft8.cpp | 69 ++- wsjtx2/ft2/portaudio.h | 1123 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1571 insertions(+), 113 deletions(-) create mode 100644 ft8/message77.cpp create mode 100644 ft8/message77.h create mode 100644 wsjtx2/ft2/portaudio.h diff --git a/decode_ft8.cpp b/decode_ft8.cpp index 1f4bc59..69d96cd 100644 --- a/decode_ft8.cpp +++ b/decode_ft8.cpp @@ -15,6 +15,9 @@ #define LOG_LEVEL LOG_INFO +const int kFreq_osr = 2; +const int kTime_osr = 2; + const int kMin_score = 40; // Minimum sync score threshold for candidates const int kMax_candidates = 120; const int kLDPC_iterations = 25; @@ -22,11 +25,6 @@ const int kLDPC_iterations = 25; const int kMax_decoded_messages = 50; const int kMax_message_length = 25; -const int kFreq_osr = 2; -const int kTime_osr = 2; - -const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate - void usage() { fprintf(stderr, "Decode a 15-second WAV file.\n"); } @@ -60,10 +58,63 @@ float blackman_i(int i, int N) { return a0 - a1*x1 + a2*x2; } -static float max2(float a, float b) { - return (a >= b) ? a : b; +class Monitor : public ft8::Monitor1Base { +public: + Monitor(float sample_rate); +protected: + virtual void fft_forward(const float *in, std::complex *out) override; +private: + kiss_fftr_cfg fft_cfg; +}; + +Monitor::Monitor(float sample_rate) : Monitor1Base(sample_rate) +{ + window_fn = new float[nfft]; // [nfft] + fft_frame = new float[nfft]; // [nfft] + last_frame = new float[nfft * 3/4]; // [nfft * 3/4] + freqdata = new std::complex[nfft/2 + 1]; // [nfft/2 + 1] + + fft_cfg = kiss_fftr_alloc(nfft, 0, NULL, NULL); } +void Monitor::fft_forward(const float *in, std::complex *out) { + kiss_fftr(fft_cfg, + reinterpret_cast(in), + reinterpret_cast(out)); +} + +// #include + +// class FFT_r2c { +// public: +// // [N] real --> [N/2 + 1] complex +// virtual void forward(const float *in, std::complex *out) = 0; +// }; + +// class KissFFT_r2c : public FFT_r2c { +// public: +// KissFFT_r2c(int N) { +// LOG(LOG_INFO, "N_FFT = %d\n", N); +// // size_t fft_work_size; +// // kiss_fftr_alloc(N, 0, 0, &fft_work_size); +// // LOG(LOG_INFO, "FFT work area = %lu\n", fft_work_size); +// // fft_work = malloc(fft_work_size); +// // fft_cfg = kiss_fftr_alloc(N, 0, fft_work, &fft_work_size); +// fft_cfg = kiss_fftr_alloc(N, 0, NULL, NULL); +// } +// ~KissFFT_r2c() { +// // free(fft_work); +// kiss_fftr_free(fft_cfg); +// } +// virtual void forward(const float *in, std::complex *out) override { +// kiss_fftr(fft_cfg, in, reinterpret_cast(out)); +// } + +// private: +// void *fft_work; +// kiss_fftr_cfg fft_cfg; +// }; + // Compute FFT magnitudes (log power) for each timeslot in the signal void extract_power(const float signal[], ft8::MagArray * power) { const int block_size = 2 * power->num_bins; // Average over 2 bins per FSK tone @@ -106,17 +157,16 @@ void extract_power(const float signal[], ft8::MagArray * power) { // Compute log magnitude in decibels for (int j = 0; j < nfft/2 + 1; ++j) { float mag2 = (freqdata[j].i * freqdata[j].i + freqdata[j].r * freqdata[j].r); - mag_db[j] = 10.0f * log10f(1E-10f + mag2 * fft_norm * fft_norm); + mag_db[j] = 10.0f * log10f(1E-12f + mag2 * fft_norm * fft_norm); } // Loop over two possible frequency bin offsets (for averaging) for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) { for (int j = 0; j < power->num_bins; ++j) { - float db1 = mag_db[j * power->freq_osr + freq_sub]; + //float db1 = mag_db[j * power->freq_osr + freq_sub]; //float db2 = mag_db[j * 2 + freq_sub + 1]; //float db = (db1 + db2) / 2; - float db = db1; - //float db = sqrtf(db1 * db2); + float db = mag_db[j * power->freq_osr + freq_sub]; // Scale decibels to unsigned 8-bit range and clamp the value int scaled = (int)(2 * (db + 120)); @@ -180,7 +230,7 @@ int main(int argc, char **argv) { normalize_signal(signal, num_samples); // Compute DSP parameters that depend on the sample rate - const int num_bins = (int)(sample_rate / (2 * kFSK_dev)); + const int num_bins = (int)(sample_rate / (2 * ft8::FSK_dev)); const int block_size = 2 * num_bins; const int subblock_size = block_size / kTime_osr; const int nfft = block_size * kFreq_osr; @@ -212,8 +262,8 @@ int main(int argc, char **argv) { ft8::Candidate &cand = candidate_list[idx]; if (cand.score < kMin_score) continue; - float freq_hz = (cand.freq_offset + (float)cand.freq_sub / kFreq_osr) * kFSK_dev; - float time_sec = (cand.time_offset + (float)cand.time_sub / kTime_osr) / kFSK_dev; + float freq_hz = (cand.freq_offset + (float)cand.freq_sub / kFreq_osr) * ft8::FSK_dev; + float time_sec = (cand.time_offset + (float)cand.time_sub / kTime_osr) / ft8::FSK_dev; float log174[ft8::N]; ft8::extract_likelihood(&power, cand, ft8::kGray_map, log174); @@ -222,7 +272,9 @@ int main(int argc, char **argv) { uint8_t plain[ft8::N]; int n_errors = 0; ft8::bp_decode(log174, kLDPC_iterations, plain, &n_errors); - //ft8::ldpc_decode(log174, kLDPC_iterations, plain, &n_errors); + if (n_errors > 0) { + // ft8::ldpc_decode(log174, kLDPC_iterations, plain, &n_errors); + } if (n_errors > 0) { LOG(LOG_DEBUG, "ldpc_decode() = %d (%.0f Hz)\n", n_errors, freq_hz); diff --git a/ft8/constants.h b/ft8/constants.h index edd6c60..676e769 100644 --- a/ft8/constants.h +++ b/ft8/constants.h @@ -3,6 +3,7 @@ #include namespace ft8 { + constexpr float FSK_dev = 6.25f; // tone deviation in Hz and symbol rate // Define FT8 symbol counts constexpr int ND = 58; // Data symbols diff --git a/ft8/decode.cpp b/ft8/decode.cpp index ae6d490..b96c801 100644 --- a/ft8/decode.cpp +++ b/ft8/decode.cpp @@ -17,6 +17,57 @@ static int get_index(const MagArray *power, int block, int time_sub, int freq_su return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin; } +Monitor1Base::Monitor1Base(float sample_rate, int time_osr, int freq_osr, float fmin, float fmax) { + int block_size = (int)(0.5f + sample_rate / ft8::FSK_dev); // Samples per FSK tone + nfft = block_size * freq_osr; // FFT over symbols with frequency oversampling + int bin1 = (block_size * fmin) / sample_rate; + int bin2 = (block_size * fmax) / sample_rate; + + power.time_osr = time_osr; + power.freq_osr = freq_osr; + power.num_bins = bin2 - bin1; + power.num_blocks = 0; +} + +void Monitor1Base::feed(const float *frame) { + // Fill the first 3/4 of analysis frame + for (int i = 0; i < 3 * nfft / 4; ++i) { + fft_frame[i] = window_fn[i] * last_frame[i]; + } + + // Shift the frame history + for (int i = 0; i < nfft / 2; ++i) { + last_frame[i] = last_frame[i + nfft / 4]; + } + + // Now fill the last_frame array + for (int i = 0; i < nfft / 4; ++i) { + last_frame[i + nfft / 2] = frame[i]; + } + + // Fill the last 1/4 of analysis frame + for (int i = 3 * nfft / 4, j = nfft / 2; i < nfft; ++i, ++j) { + fft_frame[i] = window_fn[i] * last_frame[j]; + } + + fft_forward(fft_frame, freqdata); + + for (int freq_sub = 0; freq_sub < power.freq_osr; ++freq_sub) { + for (int i = 0; i < power.num_bins; i += power.freq_osr) { + float mag2 = std::norm(freqdata[i]); // re^2 + im^2 + float mag_db = 10.0f * log10f(1E-12f + mag2); + int scaled = (int)(2 * (mag_db + 120)); + power.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled); + ++offset; + } + } + + if (++time_sub >= power.time_osr) { + time_sub = 0; + ++power.num_blocks; + } +} + // Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) // We treat and organize the candidate list as a min-heap (empty initially). int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score) { @@ -35,13 +86,14 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates // Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79) int num_symbols = 0; for (int m = 0; m <= 72; m += 36) { + // Iterate over 7 Costas synchronisation symbols for (int k = 0; k < 7; ++k) { + int n = time_offset + k + m; // Check for time boundaries - if (time_offset + k + m < 0) continue; - if (time_offset + k + m >= power->num_blocks) break; + if (n < 0) continue; + if (n >= power->num_blocks) break; - // int offset = ((time_offset + k + m) * num_alt + alt) * power->num_bins + freq_offset; - int offset = get_index(power, time_offset + k + m, time_sub, freq_sub, freq_offset); + int offset = get_index(power, n, time_sub, freq_sub, freq_offset); const uint8_t *p8 = power->mag + offset; // Weighted difference between the expected and all other symbols @@ -60,19 +112,19 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates // look at one frequency bin higher score += p8[sm] - p8[sm + 1]; } - if (k > 0) { + if (k > 0 && n - 1 >= 0) { // look one symbol back in time score += p8[sm] - p8[sm - num_alt * power->num_bins]; } - if (k < 6) { + if (k < 6 && n + 1 < power->num_blocks) { // look one symbol forward in time score += p8[sm] - p8[sm + num_alt * power->num_bins]; } - - ++num_symbols; + ++num_symbols; } } - score /= num_symbols; + if (num_symbols > 0) + score /= num_symbols; if (score < min_score) continue; @@ -121,10 +173,11 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin int sym_idx = (k < ft8::ND / 2) ? (k + 7) : (k + 14); int bit_idx = 3 * k; - // Pointer to 8 bins of the current symbol - const uint8_t *ps = power->mag + (offset + sym_idx * num_alt * power->num_bins); + // Index of the 8 bins of the current symbol + int sym_offset = offset + sym_idx * num_alt * power->num_bins; - decode_symbol(ps, code_map, bit_idx, log174); + decode_symbol(power->mag + sym_offset, code_map, bit_idx, log174); + // decode_multi_symbols(power->mag + sym_offset, power->num_bins, n_syms, code_map, bit_idx, log174); } // Compute the variance of log174 diff --git a/ft8/decode.h b/ft8/decode.h index 0894ac6..4bde2bf 100644 --- a/ft8/decode.h +++ b/ft8/decode.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace ft8 { @@ -20,6 +21,28 @@ struct Candidate { uint8_t freq_sub; }; +class Monitor1Base { +public: + Monitor1Base(float sample_rate, int time_osr = 2, int freq_osr = 2, float fmin = 300, float fmax = 3000); + + void feed(const float *frame); + void search(); + void reset(); +protected: + float *window_fn; // [nfft] + float *fft_frame; // [nfft] + float *last_frame; // [nfft * 3/4] + std::complex *freqdata; // [nfft/2 + 1] + int nfft; + + int offset; + int time_sub; + ft8::MagArray power; + + // [N] real --> [N/2 + 1] log magnitudes (decibels) + // virtual void fft_forward_mag_db(const float *frame, uint8_t *mag_db) = 0; + virtual void fft_forward(const float *in, std::complex *out) = 0; +}; // Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) // We treat and organize the candidate list as a min-heap (empty initially). diff --git a/ft8/ldpc.cpp b/ft8/ldpc.cpp index fd1eafb..549e79a 100644 --- a/ft8/ldpc.cpp +++ b/ft8/ldpc.cpp @@ -8,11 +8,11 @@ // from Sarah Johnson's Iterative Error Correction book. // codeword[i] = log ( P(x=0) / P(x=1) ) // +#include "ldpc.h" #include #include #include -#include "constants.h" namespace ft8 { @@ -47,11 +47,13 @@ void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]) { // codeword is 174 log-likelihoods. // plain is a return value, 174 ints, to be 0 or 1. // max_iters is how hard to try. -// ok == 87 means success. -void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { +// n_errors == 87 means success. +void ldpc_decode(const float codeword[], int max_iters, uint8_t plain[], int *n_errors) { float m[ft8::M][ft8::N]; // ~60 kB float e[ft8::M][ft8::N]; // ~60 kB int min_errors = ft8::M; + int n_err_last = 0; + int n_cnt = 0; for (int j = 0; j < ft8::M; j++) { for (int i = 0; i < ft8::N; i++) { @@ -93,6 +95,22 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { } } + // Early stopping criterion + if (iter > 0) { + int nd = errors - n_err_last; + if (nd < 0) { + n_cnt = 0; + } + else { + ++n_cnt; + } + if (n_cnt >= 5 && iter >= 10 && errors >= 15) { + *n_errors = errors; + return; + } + } + n_err_last = errors; + for (int i = 0; i < ft8::N; i++) { for (int ji1 = 0; ji1 < 3; ji1++) { int j1 = kMn[i][ji1] - 1; @@ -108,7 +126,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { } } - *ok = min_errors; + *n_errors = min_errors; } @@ -133,85 +151,102 @@ static int ldpc_check(uint8_t codeword[]) { } -void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { - float tov[ft8::N][3]; - float toc[ft8::M][7]; - - int min_errors = ft8::M; - - int nclast = 0; - int ncnt = 0; - +void BPDecoderState::reset() { // initialize messages to checks - for (int i = 0; i < ft8::M; ++i) { - for (int j = 0; j < kNrw[i]; ++j) { - toc[i][j] = codeword[kNm[i][j] - 1]; - } - } - for (int i = 0; i < ft8::N; ++i) { for (int j = 0; j < 3; ++j) { tov[i][j] = 0; } } +} - for (int iter = 0; iter < max_iters; ++iter) { - float zn[ft8::N]; - // Update bit log likelihood ratios (tov=0 in iter 0) - for (int i = 0; i < ft8::N; ++i) { - zn[i] = codeword[i] + tov[i][0] + tov[i][1] + tov[i][2]; - plain[i] = (zn[i] > 0) ? 1 : 0; - } +int BPDecoderState::iterate(const float codeword[], uint8_t plain[]) { + // Update bit log likelihood ratios (tov=0 in iter 0) + float zn[ft8::N]; + for (int i = 0; i < ft8::N; ++i) { + zn[i] = codeword[i] + tov[i][0] + tov[i][1] + tov[i][2]; + plain[i] = (zn[i] > 0) ? 1 : 0; + } - // Check to see if we have a codeword (check before we do any iter) - int errors = ldpc_check(plain); + // Check to see if we have a codeword (check before we do any iter) + int errors = ldpc_check(plain); - if (errors < min_errors) { - // we have a better guess - update the result - min_errors = errors; + if (errors == 0) { + return 0; // Found a perfect answer + } - if (errors == 0) { - break; // Found a perfect answer - } - } - - // Send messages from bits to check nodes - for (int i = 0; i < ft8::M; ++i) { - for (int j = 0; j < kNrw[i]; ++j) { - int ibj = kNm[i][j] - 1; - toc[i][j] = zn[ibj]; - for (int kk = 0; kk < 3; ++kk) { - // subtract off what the bit had received from the check - if (kMn[ibj][kk] - 1 == i) { - toc[i][j] -= tov[ibj][kk]; - } + // Send messages from bits to check nodes + for (int i = 0; i < ft8::M; ++i) { + for (int j = 0; j < kNrw[i]; ++j) { + int ibj = kNm[i][j] - 1; + toc[i][j] = zn[ibj]; + for (int kk = 0; kk < 3; ++kk) { + // subtract off what the bit had received from the check + if (kMn[ibj][kk] - 1 == i) { + toc[i][j] -= tov[ibj][kk]; } } } - - // send messages from check nodes to variable nodes - for (int i = 0; i < ft8::M; ++i) { - for (int j = 0; j < kNrw[i]; ++j) { - toc[i][j] = fast_tanh(-toc[i][j] / 2); - } - } - - for (int i = 0; i < ft8::N; ++i) { - for (int j = 0; j < 3; ++j) { - int ichk = kMn[i][j] - 1; // kMn(:,j) are the checks that include bit j - float Tmn = 1.0f; - for (int k = 0; k < kNrw[ichk]; ++k) { - if (kNm[ichk][k] - 1 != i) { - Tmn *= toc[ichk][k]; - } - } - tov[i][j] = 2 * fast_atanh(-Tmn); - } - } + } + + // send messages from check nodes to variable nodes + for (int i = 0; i < ft8::M; ++i) { + for (int j = 0; j < kNrw[i]; ++j) { + toc[i][j] = fast_tanh(-toc[i][j] / 2); + } } - *ok = min_errors; + for (int i = 0; i < ft8::N; ++i) { + for (int j = 0; j < 3; ++j) { + int ichk = kMn[i][j] - 1; // kMn(:,j) are the checks that include bit j + float Tmn = 1.0f; + for (int k = 0; k < kNrw[ichk]; ++k) { + if (kNm[ichk][k] - 1 != i) { + Tmn *= toc[ichk][k]; + } + } + tov[i][j] = 2 * fast_atanh(-Tmn); + } + } + + return errors; +} + + + +void bp_decode(const float codeword[], int max_iters, uint8_t plain[], int *n_errors) { + BPDecoderState state; + + int n_err_last = 0; + int n_cnt = 0; + + state.reset(); + *n_errors = ft8::M; + + for (int iter = 0; iter < max_iters; ++iter) { + int errors = state.iterate(codeword, plain); + if (errors < *n_errors) { + *n_errors = errors; + } + if (errors == 0) return; + + // Early stopping criterion + if (iter > 0) { + int nd = errors - n_err_last; + if (nd < 0) { + n_cnt = 0; + } + else { + ++n_cnt; + } + if (n_cnt >= 5 && iter >= 10 && errors >= 15) { + *n_errors = errors; + return; + } + } + n_err_last = errors; + } } // https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/ diff --git a/ft8/ldpc.h b/ft8/ldpc.h index 75290f5..347eac0 100644 --- a/ft8/ldpc.h +++ b/ft8/ldpc.h @@ -1,17 +1,29 @@ #pragma once +#include "constants.h" + namespace ft8 { + class BPDecoderState { + public: + void reset(); + int iterate(const float codeword[], uint8_t plain[]); + + private: + float tov[ft8::N][3]; + float toc[ft8::M][7]; + }; + // codeword is 174 log-likelihoods. // plain is a return value, 174 ints, to be 0 or 1. // iters is how hard to try. - // ok == 87 means success. - void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok); + // n_errors == 0 means success. + void ldpc_decode(const float codeword[], int max_iters, uint8_t plain[], int *n_errors); - void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok); + void bp_decode(const float codeword[], int max_iters, uint8_t plain[], int *n_errors); // Packs a string of bits each represented as a zero/non-zero byte in plain[], // as a string of packed bits starting from the MSB of the first byte of packed[] void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]); -} \ No newline at end of file +} diff --git a/ft8/message77.cpp b/ft8/message77.cpp new file mode 100644 index 0000000..034abc3 --- /dev/null +++ b/ft8/message77.cpp @@ -0,0 +1,49 @@ +#include "message77.h" +#include "unpack.h" + +#include + +namespace ft8 { + +Message77::Message77() { + i3 = n3 = 0; + field1[0] = field2[0] = field3[0] = '\0'; +} + +int Message77::str(char *buf, int buf_sz) const { + // Calculate the available space sans the '\0' terminator + int rem_sz = buf_sz - 1; + int field1_sz = strlen(field1); + int field2_sz = strlen(field2); + int field3_sz = strlen(field3); + int msg_sz = field1_sz + (field2_sz > 0 ? 1 : 0) + + field2_sz + (field3_sz > 0 ? 1 : 0) + + field3_sz; + + if (rem_sz < msg_sz) return -1; + + char *dst = buf; + + dst = stpcpy(dst, field1); + *dst++ = ' '; + dst = stpcpy(dst, field2); + *dst++ = ' '; + dst = stpcpy(dst, field3); + *dst = '\0'; + + return msg_sz; +} + +int Message77::unpack(const uint8_t *a77) { + // Extract n3 (bits 71..73) and i3 (bits 74..76) + n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03); + i3 = (a77[9] >> 3) & 0x07; + + int rc = unpack77_fields(a77, field1, field2, field3); + if (rc < 0) { + field1[0] = field2[0] = field3[0] = '\0'; + } + return rc; +} + +} // namespace ft8 diff --git a/ft8/message77.h b/ft8/message77.h new file mode 100644 index 0000000..b583097 --- /dev/null +++ b/ft8/message77.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace ft8 { + +class CallsignHasher { + virtual void save_callsign(void *obj, const char *callsign) = 0; + virtual bool hash10(void *obj, uint16_t hash, char *result) = 0; + virtual bool hash12(void *obj, uint16_t hash, char *result) = 0; + virtual bool hash22(void *obj, uint32_t hash, char *result) = 0; +}; + +class EmptyHasher : public CallsignHasher { + virtual void save_callsign(void *obj, const char *callsign) override { + } + virtual bool hash10(void *obj, uint16_t hash, char *result) override { + strcpy(result, "..."); + return true; + } + virtual bool hash12(void *obj, uint16_t hash, char *result) override { + strcpy(result, "..."); + return true; + } + virtual bool hash22(void *obj, uint32_t hash, char *result) override { + strcpy(result, "..."); + return true; + } +}; + +struct Message77 { + uint8_t i3, n3; + + // 11 chars nonstd call + 2 chars <...> + // 6 chars for grid/report/courtesy + char field1[13 + 1]; + char field2[13 + 1]; + char field3[6 + 1]; + + Message77(); + int unpack(const uint8_t *packed77); + void pack(uint8_t *packed77); + int str(char *buf, int buf_sz) const; + +private: +}; + +} // namespace ft8 diff --git a/ft8/unpack.cpp b/ft8/unpack.cpp index b509de5..6bb86c4 100644 --- a/ft8/unpack.cpp +++ b/ft8/unpack.cpp @@ -53,11 +53,11 @@ int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result) { // This is a 22-bit hash of a result //call hash22(n22,c13) !Retrieve result from hash table // TODO: implement - // strcpy(result, "<...>"); - result[0] = '<'; - int_to_dd(result + 1, n28, 7); - result[8] = '>'; - result[9] = '\0'; + strcpy(result, "<...>"); + // result[0] = '<'; + // int_to_dd(result + 1, n28, 7); + // result[8] = '>'; + // result[9] = '\0'; return 0; } @@ -181,7 +181,6 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha int unpack_text(const uint8_t *a71, char *text) { - // TODO: test uint8_t b71[9]; uint8_t carry = 0; @@ -272,11 +271,11 @@ int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *fie char call_3[15]; // should replace with hash12(n12, call_3); - // strcpy(call_3, "<...>"); - call_3[0] = '<'; - int_to_dd(call_3 + 1, n12, 4); - call_3[5] = '>'; - call_3[6] = '\0'; + strcpy(call_3, "<...>"); + // call_3[0] = '<'; + // int_to_dd(call_3 + 1, n12, 4); + // call_3[5] = '>'; + // call_3[6] = '\0'; char * call_1 = (iflip) ? c11 : call_3; char * call_2 = (iflip) ? call_3 : c11; @@ -370,4 +369,4 @@ int unpack77(const uint8_t *a77, char *message) { return 0; } -} // namespace +} // namespace ft8 diff --git a/gen_ft8.cpp b/gen_ft8.cpp index f030731..9c7fb46 100644 --- a/gen_ft8.cpp +++ b/gen_ft8.cpp @@ -4,12 +4,74 @@ #include #include "common/wave.h" +#include "common/debug.h" //#include "ft8/v1/pack.h" //#include "ft8/v1/encode.h" #include "ft8/pack.h" #include "ft8/encode.h" #include "ft8/constants.h" +#define LOG_LEVEL LOG_INFO + +void gfsk_pulse(int n_spsym, float b, float *pulse) { + const float c = M_PI * sqrtf(2 / logf(2)); + + for (int i = 0; i < 3*n_spsym; ++i) { + float t = i/(float)n_spsym - 1.5f; + pulse[i] = (erff(c * b * (t + 0.5f)) - erff(c * b * (t - 0.5f))) / 2; + } +} + +// Same as synth_fsk, but uses GFSK phase shaping +void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int signal_rate, float *signal) +{ + LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym); + int n_wave = n_sym * n_spsym; + float hmod = 1.0f; + + // Compute the smoothed frequency waveform. + // Length = (nsym+2)*nsps samples, first and last symbols extended + float dphi_peak = 2 * M_PI * hmod / n_spsym; + float dphi[n_wave + 2*n_spsym]; + + // Shift frequency up by f0 + for (int i = 0; i < n_wave + 2*n_spsym; ++i) { + dphi[i] = 2 * M_PI * f0 / signal_rate; + } + + float pulse[3 * n_spsym]; + gfsk_pulse(n_spsym, 2.0f, pulse); + + for (int i = 0; i < n_sym; ++i) { + int ib = i * n_spsym; + for (int j = 0; j < 3*n_spsym; ++j) { + dphi[j + ib] += dphi_peak*symbols[i]*pulse[j]; + } + } + + // Add dummy symbols at beginning and end with tone values equal to 1st and last symbol, respectively + for (int j = 0; j < 2*n_spsym; ++j) { + dphi[j] += dphi_peak*pulse[j + n_spsym]*symbols[0]; + dphi[j + n_sym * n_spsym] += dphi_peak*pulse[j]*symbols[n_sym - 1]; + } + + // Calculate and insert the audio waveform + float phi = 0; + for (int k = 0; k < n_wave; ++k) { // Don't include dummy symbols + signal[k] = sinf(phi); + phi = fmodf(phi + dphi[k + n_spsym], 2*M_PI); + } + + // Apply envelope shaping to the first and last symbols + int n_ramp = n_spsym / 8; + for (int i = 0; i < n_ramp; ++i) { + float env = (1 - cosf(2 * M_PI * i / (2 * n_ramp))) / 2; + signal[i] *= env; + signal[n_wave - 1 - i] *= env; + } +} + + // Convert a sequence of symbols (tones) into a sinewave of continuous phase (FSK). // Symbol 0 gets encoded as a sine of frequency f0, the others are spaced in increasing // fashion. @@ -23,8 +85,8 @@ void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing, int i = 0; while (j < num_symbols) { float f = f0 + symbols[j] * spacing; - phase += 2 * M_PI * f / signal_rate; - signal[i] = sin(phase); + phase = fmodf(phase + 2 * M_PI * f / signal_rate, 2 * M_PI); + signal[i] = sinf(phase); t += dt; if (t >= dt_sym) { // Move to the next symbol @@ -97,7 +159,8 @@ int main(int argc, char **argv) { signal[i] = 0; } - synth_fsk(tones, ft8::NN, frequency, symbol_rate, symbol_rate, sample_rate, signal + num_silence); + // synth_fsk(tones, ft8::NN, frequency, symbol_rate, symbol_rate, sample_rate, signal + num_silence); + synth_gfsk(tones, ft8::NN, frequency, sample_rate / symbol_rate, sample_rate, signal + num_silence); save_wav(signal, num_silence + num_samples + num_silence, sample_rate, wav_path); return 0; diff --git a/wsjtx2/ft2/portaudio.h b/wsjtx2/ft2/portaudio.h new file mode 100644 index 0000000..250fba0 --- /dev/null +++ b/wsjtx2/ft2/portaudio.h @@ -0,0 +1,1123 @@ + +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id: portaudio.h,v 1.1 2005/11/29 21:27:24 joe Exp $ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief The PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve the release number of the currently running PortAudio build, + eg 1900. +*/ +int Pa_GetVersion( void ); + + +/** Retrieve a textual description of the current PortAudio build, + eg "PortAudio V19-devel 13 October 2002". +*/ +const char* Pa_GetVersionText( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, /**< @todo review error code name */ + paCanNotWriteToACallbackStream, /**< @todo review error code name */ + paCanNotReadFromAnOutputOnlyStream, /**< @todo review error code name */ + paCanNotWriteToAnInputOnlyStream, /**< @todo review error code name */ + paIncompatibleStreamHostApi +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initialises internal data structures and prepares underlying + host APIs for use. This function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initializied by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_DeviceCount-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_DeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The the default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The the default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asyncronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constaining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the defualt host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +
+ set PA_RECOMMENDED_OUTPUT_DEVICE=1
+
+ The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds that can be used + for syncronisation. The type is used for the outTime argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that a multichannel buffer is passed + as a set of non-interleaved pointers. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000)/**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /* note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /* Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /* Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + shouldround the suggestedLatency up to the next practial value - ie to + provide an equal or higher latency than suggestedLatency whereever possibe. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; + PaTime currentTime; + PaTime outputBufferDacTime; +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, + paComplete=1, + paAbort=2 +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + @param input and @param output are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo The time in seconds when the first sample of the input + buffer was received at the audio input, the time in seconds when the first + sample of the output buffer will begin being played at the audio output, and + the time in seconds when the stream callback was called. + See also Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilise this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissable to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will recieve an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behaviour of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output dvice. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Determine the current time for the stream according to the same clock used + to generate buffer timestamps. This time may be used for syncronising other + events to the audio stream, for example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurrs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been consumed - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */