10

I a doing a weird project, and looking to convert some short, simple datagrams to audio - send them over a (physical) radio - then to receive and decode them on another device (think - embedded devices with audio out jack and GSM/GPRS-type radios).

(I have to use a physical, existing external radio).

Does anyone know of a good, simple software modem library good for such a project? I'm not so concerned about data rate, and would prefer simplicity over functionality. Even something akin to a basic 1200 baud modem would be fantastic.

Looking at this more of a learning experience and potential building block, rather than anything horribly practical.

Brad
  • 11,262
  • 8
  • 55
  • 74
  • Seems like starting with any basic tone generator for the sending side would work. On the receiving side, you'd probably want an FFT based frequency detector. From there, you could learn about means of error correction and avoidance through various custom protocols. – bbum May 22 '12 at 00:57
  • @bbum: FFT isn't necessary if you know the frequency. I think a relatively short filter (FIR) and some logic will do just fine. – Alexey Frunze May 22 '12 at 12:31
  • @Alex Thanks! I'm the first to admit my signal filtering skills are entirely lacking! I'm hoping someone will flesh out a nice answer on this question as I'd be interested in learning (and it sounds like a neat project!) – bbum May 22 '12 at 16:02
  • You're right Alex - I did a project on this a few years ago. I remember just doing a convolution/corolation against the sample and a sine wave of the carrier frequency - and against another 90 degrees out of phase. The amplitudes of the two gave me the relative phase of the sample wave, and the magnitude gave me a measure of confidence of a good carrier. An FFT could do the same, much this way was MUCH simpler and took MUCH less CPU. It was a "home-brewed" algorithm - FIR would probably have been much cleaner. – Brad May 22 '12 at 18:06
  • I patched the @arms22 library. You can found here. https://github.com/ezefranca/FSK-Arduino-iOS7 – Ezequiel Santos Nov 18 '14 at 15:08
  • I am looking at the FSK code inside this project - which looks very promising: http://www.soft-switch.org/downloads/spandsp/ – Brad May 24 '12 at 03:09

3 Answers3

13

As an exercise I've implemented a simple V.23-like modem using FSK modulation and supporting a data rate of 1200 bits/second (960 bits/second effective because of the start and stop bits).

I'm curious to see if it works with your radio. Noise, signal reflection and imperfect demodulation can all affect the performance of the modem.

Prior to trying to integrate this code into your project, first see if it works with audio recorded from your radio.

Code:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979324
#endif

typedef unsigned char uchar, uint8;
typedef signed char schar, int8;
typedef unsigned short ushort, uint16;
typedef short int16;
typedef unsigned int uint;
typedef unsigned long ulong;
#if UINT_MAX >= 0xFFFFFFFF
typedef int int32;
typedef unsigned int uint32;
#else
typedef long int32;
typedef unsigned long uint32;
#endif
typedef long long int64;
typedef unsigned long long uint64;

typedef struct
{
  double x, y;
} tComplex;

tComplex complexAdd(const tComplex* a, const tComplex* b)
{
  tComplex c;
  c.x = a->x + b->x;
  c.y = a->y + b->y;
  return c;
}

tComplex complexMul(const tComplex* a, const tComplex* b)
{
  tComplex c;
  c.x = a->x * b->x - a->y * b->y;
  c.y = a->x * b->y + a->y * b->x;
  return c;
}

void dft(tComplex out[], const tComplex in[], size_t n, int direction)
{
  size_t k, i;
  for (k = 0; k < n; k++)
  {
    tComplex r = { 0, 0 }, e;
    for (i = 0; i < n; i++)
    {
      e.x = cos(-2 * direction * M_PI / n * ((double)k - n / 2) * ((double)i - n / 2));
      e.y = sin(-2 * direction * M_PI / n * ((double)k - n / 2) * ((double)i - n / 2));
      e = complexMul(&e, &in[i]);
      r = complexAdd(&r, &e);
    }
    out[k] = r;
  }
}

#define FILTER_LENGTH 64

typedef struct tTx
{
  enum
  {
    stSendingOnes,
    stSendingData
  } State;

  uint SampleRate;
  uint OnesFreq;
  uint ZeroesFreq;
  uint BitRate;

  uint32 SampleCnt;
  uint BitSampleCnt;
  uint Data;
  uint DataLeft;

  double Phase;
  double PhaseIncrement;

  uint (*pTxGetDataCallBack)(struct tTx*, uint8*);
} tTx;

void TxInit(tTx* pTx,
            uint SampleRate,
            uint (*pTxGetDataCallBack)(tTx*, uint8*))
{
  memset(pTx, 0, sizeof(*pTx));
  pTx->State = stSendingOnes;
  pTx->SampleRate = SampleRate;
  pTx->OnesFreq = 1300;
  pTx->ZeroesFreq = 2100;
  pTx->BitRate = 1200;
  pTx->pTxGetDataCallBack = pTxGetDataCallBack;

  pTx->SampleCnt = 0;
  pTx->BitSampleCnt = pTx->SampleRate;
  pTx->Data = 0;
  pTx->DataLeft = 0;
  pTx->Phase = 0.0;
  pTx->PhaseIncrement = 2 * M_PI * pTx->OnesFreq / pTx->SampleRate;
}

int16 TxGetSample(tTx* pTx)
{
  int16 sample;

  if (pTx->State == stSendingOnes &&
      pTx->SampleCnt >= pTx->SampleRate)
  {
    // Sent 1 second worth of 1's, can now send data
    pTx->State = stSendingData;
  }

  if (pTx->State == stSendingData &&
      pTx->BitSampleCnt >= pTx->SampleRate)
  {
    // Another data bit can now be sent
    uint8 d;

    pTx->BitSampleCnt -= pTx->SampleRate;

    if (!pTx->DataLeft)
    {
      // Get the next data byte (if any)
      if (pTx->pTxGetDataCallBack(pTx, &d) != 0)
      {
        pTx->Data = d & 0xFF;
        pTx->Data |= 1 << 8; // insert the stop bit
        pTx->Data <<= 1; // insert the start bit
        pTx->DataLeft = 10;
      }
      else
      {
        pTx->Data = 0x3FF; // no data, send 10 1's
        pTx->DataLeft = 10;
      }
    }

    // Extract the next data bit to send
    d = pTx->Data & 1;
    pTx->Data >>= 1;
    pTx->DataLeft--;

    // Choose the appropriate frequency for 0 and 1
    if (d)
    {
      pTx->PhaseIncrement = 2 * M_PI * pTx->OnesFreq / pTx->SampleRate;
    }
    else
    {
      pTx->PhaseIncrement = 2 * M_PI * pTx->ZeroesFreq / pTx->SampleRate;
    }
  }

  // Generate the next sample, advance the generator's phase
  sample = (int16)(16000 * cos(pTx->Phase));
  pTx->Phase += pTx->PhaseIncrement;
  if (pTx->Phase >= 2 * M_PI)
  {
    pTx->Phase -= 2 * M_PI;
  }

  if (pTx->State == stSendingData)
  {
    pTx->BitSampleCnt += pTx->BitRate;
  }

  pTx->SampleCnt++;

  return sample;
}

typedef struct tRx
{
  enum
  {
    stCarrierLost,
    stCarrierDetected,
    stReceivingData
  } State;

  uint SampleRate;
  uint OnesFreq;
  uint ZeroesFreq;
  uint MidFreq;
  uint BitRate;

  uint32 SampleCnt;
  uint BitSampleCnt;
  uint Data;

  double Phase;
  double PhaseIncrement;

  tComplex Filter[FILTER_LENGTH];
  double Delay[FILTER_LENGTH];

  double LastAngle;
  int LastDelta;
  int32 Deltas;

  int32 CarrierAngle;
  int32 CarrierCnt;

  double LongAvgPower;
  double ShortAvgPower;

  void (*pRxGetDataCallBack)(struct tRx*, uint8);
} tRx;

void RxInit(tRx* pRx,
            uint SampleRate,
            void (*pRxGetDataCallBack)(struct tRx*, uint8))
{
  tComplex tmp[FILTER_LENGTH];
  uint i;

  memset(pRx, 0, sizeof(*pRx));
  pRx->State = stCarrierLost;
  pRx->SampleRate = SampleRate;
  pRx->OnesFreq = 1300;
  pRx->ZeroesFreq = 2100;
  pRx->MidFreq = (pRx->OnesFreq + pRx->ZeroesFreq) / 2;
  pRx->BitRate = 1200;
  pRx->pRxGetDataCallBack = pRxGetDataCallBack;

  pRx->SampleCnt = 0;
  pRx->BitSampleCnt = 0;
  pRx->Data = 0x3FF;
  pRx->Phase = 0.0;
  pRx->PhaseIncrement = 2 * M_PI * pRx->MidFreq / pRx->SampleRate;
  pRx->LastAngle = 0.0;
  pRx->LastDelta = 0;
  pRx->Deltas = 0;
  pRx->CarrierAngle = 0;
  pRx->CarrierCnt = 0;
  pRx->LongAvgPower = 0.0;
  pRx->ShortAvgPower = 0.0;

  for (i = 0; i < FILTER_LENGTH; i++)
  {
    pRx->Delay[i] = 0.0;
  }

  for (i = 0; i < FILTER_LENGTH; i++)
  {
    if (i == 0) // w < 0 (min w)
    {
      pRx->Filter[i].x = 0;
      pRx->Filter[i].y = 0;
    }
    else if (i < FILTER_LENGTH / 2) // w < 0
    {
      pRx->Filter[i].x = 0;
      pRx->Filter[i].y = 0;
    }
    else if (i == FILTER_LENGTH / 2) // w = 0
    {
      pRx->Filter[i].x = 0;
      pRx->Filter[i].y = 0;
    }
    else if (i > FILTER_LENGTH / 2) // w > 0
    {
      pRx->Filter[i].x = 0;
      pRx->Filter[i].y = -1;

      // Extra filter to combat channel noise
      if (i - FILTER_LENGTH / 2 < 875UL * FILTER_LENGTH / pRx->SampleRate ||
          i - FILTER_LENGTH / 2 > (2350UL * FILTER_LENGTH + pRx->SampleRate - 1) / pRx->SampleRate)
      {
        pRx->Filter[i].y = 0;
      }
    }
  }

  memcpy(tmp, pRx->Filter, sizeof(tmp));
  dft(pRx->Filter, tmp, FILTER_LENGTH, -1);
}

#define RX_VERBOSE 0
void RxGetSample(tRx* pRx, int16 Sample)
{
  tComplex s = { 0.0, 0.0 }, ss;
  double angle;
  uint i;
  int delta;
  double pwr;

  // Insert the sample into the delay line
  memmove(&pRx->Delay[0], &pRx->Delay[1], sizeof(pRx->Delay) - sizeof(pRx->Delay[0]));
  pRx->Delay[FILTER_LENGTH - 1] = Sample;

  // Get the next analytic signal sample by applying Hilbert transform/filter
  for (i = 0; i < FILTER_LENGTH; i++)
  {
    s.x += pRx->Delay[i] * pRx->Filter[FILTER_LENGTH - 1 - i].x;
    s.y += pRx->Delay[i] * pRx->Filter[FILTER_LENGTH - 1 - i].y;
  }

  // Frequency shift by MidFreq down
  ss.x = cos(-pRx->Phase);
  ss.y = sin(-pRx->Phase);
  s = complexMul(&s, &ss);
  pRx->Phase += pRx->PhaseIncrement;
  if (pRx->Phase >= 2 * M_PI)
  {
    pRx->Phase -= 2 * M_PI;
  }

  // Calculate signal power
  pwr = (s.x * s.x + s.y * s.y) / 32768 / 32768;
  pRx->LongAvgPower *= 1 - pRx->BitRate / (pRx->SampleRate * 8.0 * 8);
  pRx->LongAvgPower += pwr;
  pRx->ShortAvgPower *= 1 - pRx->BitRate / (pRx->SampleRate * 8.0);
  pRx->ShortAvgPower += pwr;

#if 0
  printf("LongAvgPower:%f ShortAvgPower:%f\n", pRx->LongAvgPower, pRx->ShortAvgPower);
#endif

  // Disconnect if the signal power changes abruptly.
  if (pRx->State != stCarrierLost &&
      pRx->LongAvgPower > pRx->ShortAvgPower * 8 * 8)
  {
    // N.B. The receiver may have received a few extra (garbage) bytes
    // while demodulating the abruptly changed signal.
    // Prefixing data with its size or using a more advanced protocol
    // may be a good solution to this little problem.
    pRx->State = stCarrierLost;
    pRx->BitSampleCnt = 0;
    pRx->Data = 0x3FF;
    pRx->Phase = 0.0;
    pRx->LastAngle = 0.0;
    pRx->LastDelta = 0;
    pRx->Deltas = 0;
    pRx->CarrierAngle = 0;
    pRx->CarrierCnt = 0;
  }

  // Get the phase angle from the analytic signal sample
  angle = (fpclassify(s.x) == FP_ZERO && fpclassify(s.y) == FP_ZERO) ?
    0.0 : 180 / M_PI * atan2(s.y, s.x);
  // Calculate the phase angle change and force it to the -PI to +PI range
  delta = (int)(360.5 + angle - pRx->LastAngle) % 360;
  if (delta > 180) delta -= 360;

  if (pRx->State == stCarrierLost)
  {
    // Accumulate the phase angle change to see if we're receiving 1's
    pRx->CarrierAngle += delta;
    pRx->CarrierCnt++;

    // Check whether or not the phase corresponds to 1's
    if (delta < 0)
    {
      if (pRx->CarrierCnt >= pRx->SampleRate / pRx->OnesFreq * 8)
      {
        double ph = (double)pRx->CarrierAngle / pRx->CarrierCnt;
#if RX_VERBOSE
        printf("ca:%5ld, cc:%4ld, ca/cc:%4ld\n",
               (long)pRx->CarrierAngle,
               (long)pRx->CarrierCnt,
               (long)(pRx->CarrierAngle / pRx->CarrierCnt));
#endif
        // Frequency tolerance is +/-16 Hz per the V.23 spec
        if (ph < (pRx->OnesFreq - 17.0 - pRx->MidFreq) * 360.0 / pRx->SampleRate ||
            ph > (pRx->OnesFreq + 17.0 - pRx->MidFreq) * 360.0 / pRx->SampleRate)
        {
          goto BadCarrier;
        }
      }
    }
    else
    {
BadCarrier:
      // Phase doesn't correspond to 1's
      pRx->CarrierAngle = 0.0;
      pRx->CarrierCnt = 0;
    }

    if (pRx->CarrierCnt >= pRx->SampleRate / 2 + pRx->SampleRate / 4)
    {
      // 0.75 seconds worth of 1's have been detected, ready to receive data

      // Adjust MidFreq to compensate for the DAC and ADC sample rate difference
      double f1 = (double)pRx->CarrierAngle / pRx->CarrierCnt / 360 * pRx->SampleRate + pRx->MidFreq;
      pRx->MidFreq = (uint)(pRx->MidFreq * f1 / pRx->OnesFreq);
      pRx->PhaseIncrement = 2 * M_PI * pRx->MidFreq / pRx->SampleRate;
#if RX_VERBOSE
      printf("f1:%u, new MidFreq:%u\n", (uint)f1, pRx->MidFreq);
#endif
      pRx->State = stCarrierDetected;
    }
  }
  else
  {
    // Detect frequency changes (transitions between 0's and 1's)
    int freqChange = ((int32)pRx->LastDelta * delta < 0 || pRx->LastDelta && !delta);
    int reAddDelta = 0;

#if RX_VERBOSE
    printf("%6lu: delta:%4d freqChange:%d BitSampleCnt:%u\n",
           (ulong)pRx->SampleCnt,
           delta,
           freqChange,
           pRx->BitSampleCnt);
#endif

    // Synchronize with 1<->0 transitions
    if (freqChange)
    {
      if (pRx->BitSampleCnt >= pRx->SampleRate / 2)
      {
        pRx->BitSampleCnt = pRx->SampleRate;
        pRx->Deltas -= delta;
        reAddDelta = 1;
      }
      else
      {
        pRx->BitSampleCnt = 0;
        pRx->Deltas = 0;
      }
    }

    // Accumulate analytic signal phase angle changes
    // (positive for 0, negative for 1)
    pRx->Deltas += delta;

    if (pRx->BitSampleCnt >= pRx->SampleRate)
    {
      // Another data bit has accumulated
      pRx->BitSampleCnt -= pRx->SampleRate;

#if RX_VERBOSE
      printf("bit: %u\n", pRx->Deltas < 0);
#endif

      pRx->Data >>= 1;
      pRx->Data |= (pRx->Deltas < 0) << 9;
      pRx->Deltas = delta * reAddDelta;

      if ((pRx->Data & 0x201) == 0x200)
      {
        // Start and stop bits have been detected
        if (pRx->State == stCarrierDetected)
        {
          pRx->State = stReceivingData;
        }
        pRx->Data = (pRx->Data >> 1) & 0xFF;
        pRx->pRxGetDataCallBack(pRx, (uint8)pRx->Data);

#if RX_VERBOSE
        printf("byte: 0x%02X ('%c')\n",
               pRx->Data,
               (pRx->Data >= 0x20 && pRx->Data <= 0x7F) ? pRx->Data : '?');
#endif

        pRx->Data = 0x3FF;
      }
    }

    pRx->BitSampleCnt += pRx->BitRate;
  }

  pRx->LastAngle = angle;
  pRx->LastDelta = delta;
  pRx->SampleCnt++;
}

typedef struct
{
  tTx Tx;
  FILE* DataFile;
  int CountDown;
} tTxTest;

uint TxGetDataCallBack(tTx* pTx, uint8* pTxData)
{
  tTxTest* pTxTest = (tTxTest*)pTx;
  uchar c;

  if (pTxTest->CountDown)
  {
    pTxTest->CountDown--;
    return 0;
  }

  if (fread(&c, 1, 1, pTxTest->DataFile) != 1)
  {
    pTxTest->CountDown = 20;
    return 0;
  }

  *pTxData = c;
  return 1;
}

int testTx(uint SampleRate,
           double NoiseLevel,
           const char* DataFileName,
           const char* AudioFileName)
{
  FILE *fData = NULL, *fAudio = NULL;
  int err = EXIT_FAILURE;
  tTxTest txTest;

  if ((fData = fopen(DataFileName, "rb")) == NULL)
  {
    printf("Can't open file \"%s\"\n", DataFileName);
    goto Exit;
  }

  if ((fAudio = fopen(AudioFileName, "wb")) == NULL)
  {
    printf("Can't create file \"%s\"\n", AudioFileName);
    goto Exit;
  }

  txTest.DataFile = fData;
  txTest.CountDown = 0;

  TxInit(&txTest.Tx,
         SampleRate,
         &TxGetDataCallBack);

  do
  {
    int16 sample = TxGetSample(&txTest.Tx);
    if (txTest.CountDown > 1 && txTest.CountDown <= 10)
    {
#if 0 // Enable this code to test disconnecting.
      // Finish with silence.
      sample = 0;
#endif
    }
    sample += (rand() - (int)RAND_MAX / 2) * NoiseLevel * 16000 / (RAND_MAX / 2);
    fwrite(&sample, 1, sizeof(sample), fAudio);
  } while (txTest.CountDown != 1); // Drain all data-containing samples

  err = EXIT_SUCCESS;

Exit:

  if (fData != NULL) fclose(fData);
  if (fAudio != NULL) fclose(fAudio);

  return err;
}

typedef struct
{
  tRx Rx;
  FILE* DataFile;
} tRxTest;

void RxGetDataCallBack(tRx* pRx, uint8 RxData)
{
  tRxTest* pRxTest = (tRxTest*)pRx;
  uchar c = RxData;
  fwrite(&c, 1, 1, pRxTest->DataFile);
}

int testRx(uint SampleRate,
           const char* AudioFileName,
           const char* DataFileName)
{
  uint lastState;
  FILE *fAudio = NULL, *fData = NULL;
  int err = EXIT_FAILURE;
  tRxTest rxTest;

  if ((fAudio = fopen(AudioFileName, "rb")) == NULL)
  {
    printf("Can't open file \"%s\"\n", AudioFileName);
    goto Exit;
  }

  if ((fData = fopen(DataFileName, "wb")) == NULL)
  {
    printf("Can't create file \"%s\"\n", DataFileName);
    goto Exit;
  }

  rxTest.DataFile = fData;

  RxInit(&rxTest.Rx,
         SampleRate,
         &RxGetDataCallBack);

  for (;;)
  {
    int16 sample;

    if (fread(&sample, 1, sizeof(sample), fAudio) != sizeof(sample))
    {
      if (rxTest.Rx.State != stCarrierLost) goto NoCarrier;
      break;
    }

    lastState = rxTest.Rx.State;
    RxGetSample(&rxTest.Rx, sample);

    if (rxTest.Rx.State != lastState && rxTest.Rx.State == stCarrierDetected)
    {
      printf("\nCONNECT %u\n\n", rxTest.Rx.BitRate);
    }

    if (rxTest.Rx.State != lastState && rxTest.Rx.State == stCarrierLost)
    {
NoCarrier:
      printf("\n\nNO CARRIER\n");
      break;
    }
  }

  err = EXIT_SUCCESS;

Exit:

  if (fAudio != NULL) fclose(fAudio);
  if (fData != NULL) fclose(fData);

  return err;
}

int main(int argc, char* argv[])
{
  uint sampleRate;
  double noiseLevel;

  if (argc < 2 ||
      !stricmp(argv[1], "-help") ||
      !stricmp(argv[1], "/help") ||
      !stricmp(argv[1], "-?") ||
      !stricmp(argv[1], "/?"))
  {
Usage:
    printf("Usage:\n\n"
           "  %s tx <sample rate> <noise level> <data input file> <PCM output file>\n"
           "  %s rx <sample rate> <PCM input file> <data output file>\n",
           argv[0],
           argv[0]);
    return 0;
  }

  if (!stricmp(argv[1], "tx") &&
      argc == 6 &&
      sscanf(argv[2], "%u", &sampleRate) == 1 &&
      sscanf(argv[3], "%lf", &noiseLevel) == 1)
  {
    return testTx(sampleRate, noiseLevel, argv[4], argv[5]);
  }
  else if (!stricmp(argv[1], "rx") &&
           argc == 5 &&
           sscanf(argv[2], "%u", &sampleRate) == 1)
  {
    return testRx(sampleRate, argv[3], argv[4]);
  }
  else
  {
    goto Usage;
  }
}

Typical usage:

modem.exe tx 8000 0.2 testin.txt test8000.pcm
modem.exe rx 8000 test8000.pcm testout.txt

The resulting testout.txt should be identical to testin.txt.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • Wow! Looking forward to trying this out!! :-) – Brad Jun 15 '12 at 19:13
  • Having trouble trying to export code out of the web page. Can I download as a file somewhere? – Brad Jul 10 '12 at 18:22
  • 1
    Perhaps it would be a little easier to get it in the editing more? Start editing the answer. – Alexey Frunze Jul 10 '12 at 19:29
  • Working with this - I assume I could change the bitrate by changing the "BitRate" params. But there are also "OnesFreq" and "ZeroesFreq". What are these? (I could summize the obvious) - or rather, do they depend on BitRate, or can they be tuned independently? I am asking because - my data is very terse. Would I be better off on a noisy channel using a lower bitrate with more pronounced (zero and one) levels? – Brad Sep 04 '12 at 19:17
  • In FSK, symbols (here, bits 0 and 1) are represented by tones of different frequencies (ZeroesFreq and OnesFreq here). For these symbols/tones to be detectable, their duration has to be more than one full sinewave period (IOW, 1/ZeroesFreq < 1/BitRate and 1/OnesFreq < 1/BitRate OR ZeroesFreq > BitRate and OnesFreq > BitRate). The bandwidth of such an FSK-modulated signal is from OnesFreq-BitRate/2 (1300-1200/2=700 Hz here) to ZeroesFreq+BitRate/2 (2100+1200/2=2700 Hz here) and the upper frequency (2700 Hz here) must be lower than SampleRate/2 (8000/2=4000 Hz here). – Alexey Frunze Sep 08 '12 at 06:46
  • There's a band-pass filter superimposed on the Hilbert filter that's calculated in RxInit(). It's needed to improve performance in noisy conditions. This band-pass filter should pass the signal from OnesFreq-BitRate/2 to ZeroesFreq+BitRate/2. See [this related question](http://dsp.stackexchange.com/q/2781/1408). In my original code the pass band of the filter is somewhat tighter because I used experimental values. It looks like there's some signal redundancy, which lets me get away with a tighter pass band. – Alexey Frunze Sep 08 '12 at 06:57
  • Using the presented relationship between the four values, you may be able to bump up the data rate. If the noise permits, of course. You should probably also keep BitRate=(OnesFreq+ZeroesFreq)/2. I'm not a DSP pro, just happen to know some basics. – Alexey Frunze Sep 08 '12 at 07:00
  • I did notice the band-pass filter. I am learning ALOT from your code and doing conciderable work with it. One thing I noticed, is that as soon as the (random) noise level is appx. greater than the signal level, signal is immediateley lost. Some tuning I did of the frequency parameters made this a BIT better, but not by much. I had done an implementation a few years back which seemed to be MUCH more resilient to noise - to the point that I could audibly not hear ANY signal, but the code was able to easily pluck it out. – Brad Sep 08 '12 at 21:59
  • I know MUCH less about DSP than you do - my old code was VERY different (and must more expensive) than your code. I think I used an FFT to detect, and determine the phase of the carrier signal. From there, I filtered out the carrier. I also used a different kind of modulation, so I everything was based on a single carrier frequency which made this easier. I just bought a book on DSP that I hope will clarify things for me! The thing about the signal cutting off so quickly, has me concerned with this implementaiton. – Brad Sep 08 '12 at 22:01
  • I don't understand the reciever code much at all. I figured out the "filter" and the "DFT" was implementing some sort of a band-pass filter, and the two constants were high and low frequencies - but that was about it. I don't understand the "high" and "low" power-levels, etc - so I'm a bit blind with all it - but still learning! I have made some frequency adjustments to better adhere to Bell V22 standard - and beyond that - I chose some other (odd) frequencies to better fit sampling frequency/window. Other than that - having fun learning! Thanks again! – Brad Sep 08 '12 at 22:03
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/16449/discussion-between-alexey-frunze-and-brad) – Alexey Frunze Sep 08 '12 at 22:48
  • how do i use .pcm file to generate wave sound? – Taranfx Jan 31 '13 at 07:33
  • @geeknizer I don't understand the question and I don't see how it relates to the question here. Could you create a new, specific question on SO? – Alexey Frunze Jan 31 '13 at 07:35
  • Is this pcm out file audio or something else? – Taranfx Jan 31 '13 at 07:57
  • @geeknizer PCM also stands for Pulse Code Modulation and that's what's meant here, raw samples of data. There's no special file format or anything. – Alexey Frunze Jan 31 '13 at 07:59
2

A web search will turn up lots of amateur radio BPSK and RTTY/FSK solutions. Much of this code was written for older slower CPUs, so should run just fine on an iPhone. You can use the Audio Queue API or the RemoteIO Audio Unit for iOS audio IO to the codec.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
1

If you're still looking for a soft modem, you might consider libquiet or Quiet.js. These offer a low power GMSK mode which is fairly capable as well as higher bitrate modes if you're using an audio cable. Quiet uses an existing SDR library to perform its modulation so you'll get something pretty full-featured.

Brian Armstrong
  • 326
  • 2
  • 14