Design a modulator/demodulator pair to accurately transmit data as quickly as possible over simulated plain old telephone service (POTS).


  1. Generate some random (/dev/random or the like) data that will take 3-4 seconds to transmit
  2. Modulate the data with your modulator to produce an audio file
  3. Pass the audio file through the POTS simulator. If you don't have Python/Scipy you can upload a file with the form, or do a JSON API request.
  4. Demodulate the audio file back to binary data
  5. Validate that the input and output are equal-ish* (limit 1 out of every 1000 bits can be corrupted)
  6. Score is number of bits transmitted divided by the length of the audio file (bits/second)


  • Input file must be 3-4 seconds, 44.1 kHz, mono.
  • Run the simulator with an SNR of 30 dB (it's default)
  • The demodulator must reconstruct the transmitted data with a bit error rate of no more than 10-3 (1 per thousand bits).
  • No digital compression is allowed (i.e. zipping the data. It's outside the scope of the challenge.)
  • No attempting to shove data into frequencies above 4 kHz. (My filters aren't perfect, but they're reasonably POTS-like with a relatively small number of taps.)
  • If your modem protocol requires a short preamble (no more than 1 second) to synchronize/calibrate the receiver, it isn't penalized.
  • If possible, please host the audio file somewhere accessible so we can listen to a cacophony of beeps and boops.


Here's an example notebook that demonstrates the modulation/demodulation with simple "on-off keying" (audio samples included!).

It would score 100 (bits/second). Note that it's transmitting with a much worse 5 dB SNR.

  • 2
    \$\begingroup\$ Is this different than an ordinary "compress this binary data" challenge? If so, could you clarify how precisely it differs? \$\endgroup\$
    – Doorknob
    Commented Oct 1, 2015 at 1:32
  • 1
    \$\begingroup\$ Here you're modulating data (turning it into something analog) then the reverse. One could maybe call it "analog compression" \$\endgroup\$
    – Nick T
    Commented Oct 1, 2015 at 1:37
  • \$\begingroup\$ Sorry, I'm not sure I understand how this challenge works. The word "modulate" doesn't even appear in the Wikipedia article you linked. Could you include more background information, or clarify the spec? \$\endgroup\$
    – Doorknob
    Commented Oct 1, 2015 at 1:38
  • 4
    \$\begingroup\$ wget wikipedia.org/Special:Random | grep title | texttospeech audio.wav speechtotext POTSaudio.wav | wget wikipedia/wiki/$text \$\endgroup\$ Commented Oct 1, 2015 at 2:53
  • 1
    \$\begingroup\$ This is an awesome challenge, I will try to find the time to submit an answer! \$\endgroup\$ Commented Oct 1, 2015 at 12:57

1 Answer 1


MATLAB, 1960 bps

Here is my updated attempt:

fs = 44100; %44.1kHz audio rate
fc = 2450;  %2.45kHz carrier - nice fraction of fs!
fsym = fc/5; %symbol rate

tmax = 4; %about 4 seconds worth

preamblesyms = 6;

t = 1/fs:1/fs:(tmax+preamblesyms/fsym);

symbols = preamblesyms+fsym*tmax;
symbollength = length(t)/symbols;
bits = symbols*3;
bitstream = [zeros(1,preamblesyms*3),rand(1,bits-preamblesyms*3)>0.5]; %Add a little preamble of 18 bits
data = bin2dec(char(reshape(bitstream,3,symbols)'+'0'))';

greycode = [0 1 3 2 6 7 5 4];

%Encode the symbols using QAM8 - we use effectively grey code so that
%adjacent symbols in the constellation have only one bit difference
%(minimises error rate)
encoded = zeros(2,symbols);
encoded(1,data==1) = 1/sqrt(2);
encoded(1,data==3) = 1;
encoded(1,data==2) = 1/sqrt(2);
encoded(1,data==7) = -1/sqrt(2);
encoded(1,data==5) = -1;
encoded(1,data==4) = -1/sqrt(2);
encoded(2,data==0) = 1;
encoded(2,data==1) = 1/sqrt(2);
encoded(2,data==2) = -1/sqrt(2);
encoded(2,data==6) = -1;
encoded(2,data==7) = -1/sqrt(2);
encoded(2,data==4) = 1/sqrt(2);

%Modulate onto carrier
carrier = [sin(2*pi*fc*t);cos(2*pi*fc*t)];
signal = reshape(repmat(encoded(1,:)',1,symbollength)',1,[]);
signal(2,:) = reshape(repmat(encoded(2,:)',1,symbollength)',1,[]);
modulated = sum(signal.*carrier)';

%Write out an audio file

%Wait for the user to run through the POTS simulator

%Read in the filtered data

%Recover the two carrier signals
preamblecos = filtered(symbollength+1:symbollength*2);
preamblesin = filtered(symbollength+1+round(symbollength*3/4):symbollength*2+round(symbollength*3/4));

%Replicated the recovered carriers for all symbols
carrierfiltered = [repmat(preamblesin,1,symbols);repmat(preamblecos,1,symbols)];

%Generate a demodulation filter (pass up to 0.66*fc, stop at 1.33*fc
%(really we just need to kill everything around 2*fc where the alias ends up)
Hd = design(d,'equiripple');

%Demodulate the incoming stream
demodulated = carrierfiltered .* [filtered;filtered];

%Split signal up into bit periods
recovereddemodulated(1,:,:) = reshape(demodulated(1,:),symbollength,symbols);
recovereddemodulated(2,:,:) = reshape(demodulated(2,:),symbollength,symbols);

%Extract the average level for each bit period. Only look at the second
%half to account for slow rise times in the signal due to filtering

%Convert the recovered signal into a complex number.
recoveredsignal=recoveredsignal(2,:) + 1j*recoveredsignal(1,:);

%Determine the magnitude and angle of the symbol. The phase is normalised
%to pi/4 as that is the angle between the symbols. Rounding this to the
%nearest integer will tell us which of the 8 phases it is closest to
recoveredphase = round(angle(recoveredsignal)/(pi/4));
recoveredphase = mod(recoveredphase+8,8)+1; %Remap to an index in the grey code vector.

%Determine the symbol in the QAM8 constellation
recoveredencoded(1:preamblesyms)=0; %Assume the preamble is correct for comparison

%Turn it back in to a bit stream
bitstreamRecovered = reshape(dec2bin(recoveredencoded)'-'0',1,[]);

%And check if they are all correct...
    disp(['Woop, ' num2str(fsym*4) 'bps']);
    error('Its corrupt Jim.');

Since my first attempt, I have played around a bit. There is now a small preamble at the beginning (18 bit periods, but could be shorter) which contains just a cosine wave. I extract this and replicated it to create correctly phased sine and cosine carriers for demodulation - as it is a very short preamble, I haven't counted it in the bit rate as per your instructions.

Also since the first attempt I am now using a QAM8 constellation to achieve 3 bits per symbol instead of 2. This effectively doubles the transfer rate. So with a ~2.4kHz carrier I am now achieving 1960bps.

I've also improved the symbol detection so that the averaging doesn't get affected by slow rise times caused by the filtering - basically only the second half of each bit period is averaged to remove the impact of rise times.

Still nowhere near the 40kbps theoretical channel bandwidth from the Shannon-Hartley theory (assuming the 30dB SNR)

Just for those that like horrible sounds, this is the new entry:

And in case anyone is interested, this is the previous 960bps entry

  • \$\begingroup\$ Scoring is just the transfer rate, so keep your code clear. I added a suggestion to host your audio file somewhere if it's easy for funsies :D \$\endgroup\$
    – Nick T
    Commented Oct 1, 2015 at 3:57
  • \$\begingroup\$ I'll upload the audio to my site. It sounds rather eery! \$\endgroup\$ Commented Oct 1, 2015 at 3:58
  • \$\begingroup\$ @NickT audio file uploaded - see the link at the bottom of the post. \$\endgroup\$ Commented Oct 1, 2015 at 4:02
  • \$\begingroup\$ If you have a SoundCloud account you can upload your audio and post a link and it will be playable in your post. (Example) \$\endgroup\$ Commented Oct 1, 2015 at 5:18
  • \$\begingroup\$ @NickT thanks. I've created a soundcloud account and uploaded it. I've also made an updated version with double the data rate :) \$\endgroup\$ Commented Oct 1, 2015 at 15:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.