You will need to have a look at modulation techniques. The normal procedure is this:
- Making binary data redundant with some error correction code
- Modulation of the data to a discrete signal
- D/A converter
- Transfer over physical medium
- A/D converter Sampling
- Error correction
If you want to do this simpler, then you can skip the error correction part, but this bears the risk, that your whole data is corrupted under just slightly not optimal environment.
Let's have a quick look at the software parts of this.
Adding error correction codes
There are many codes to do this. A very simple one is just repeating every bit multiple times and in the error correction phase taking the average of all received bits.
You have a sequence of ones and zeros and want to convert it to a wave pattern. You do this by mapping them to different base signals. In an easy case those signals can just be sinus signals of different frequency, in general they can be any signals, but should be orthogonal to be statistically independant.
Then you need to specify how long one bit will be sent, that is called symbol length. The longer you send your signal for a bit, the easier it is to detect it, however you can sent less data per time. Keep in mind that we are creating a discrete signal, which then goes through some D/A converter (our sound card).
We want to sent the pattern 00110100 using a sine of 5000 Hz for a 0 and 10000 Hz for a 1. We choose our symbol length to be 1 ms, so it is a multiple of the period of both our base signals, which improves the shape.
So we send a sine of frequency 5000 Hz for 2 ms, then 10000 Hz for 2 ms, then 5000 Hz for 1 ms, 10000 Hz for 1ms and finally 5000 Hz for 2 ms.
To create the sampling points for this we have to choose an audio format. Let's use 44 kHz sampling frequency.
The code to do this is something similar to this:
for bit in data:
for i in range(0, sampling_frequency * symbol_length):
signal.append(sin(i * sample_length * symbol_frequency(bit)))
sampling_frequency would be something like 44 kHz, symbol_length is 1ms, sample_length is 1/sampling_frequency, symbol_frequency is 5000 Hz for a 0 and 10000 Hz for a 1.
This can be done by a correlation function. Basically you assume you have a symbol and then look how similar your received signal is to the signal generated by that symbol. The similarity is the sum over all samples of the product of the received sample and the theoretical sample. If your frequency matches the signs should be equal throughout the signal, so this ends up as a big value, for different frequencies the signs change at different points and all will end up somewhere around zero. For our simple case you can calculate the correlation function with an assumed one and an assumed zero and then use the bigger one as your received symbol.
To read and write your created audio to a file you can use the default python wave library: https://docs.python.org/2/library/wave.html