I don't regard this as a practical solution, but it is a fun one. Why simulate a null modem, when you can simulate a real modem. minimodem is available as a package in some distributions and converts data into modem audio tones, and vice versa. To try it out, just listen to this audio at baud rate 10 and 0.1 volume:
$ echo 'the quick brown fox' | minimodem -v 0.1 --tx 10
This takes about 20 seconds to run. If you direct the output to a file, it is instantaneous, but you can then "play" the file with sox
and have the tones interpreted back to data:
$ echo 'the quick brown fox' | minimodem -v 0.1 --tx 10 -f out.wav
$ file out.wav
out.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 48000 Hz
$ sox out.wav -t wav - | minimodem -f - --rx 10
### CARRIER 10.00 @ 1590.0 Hz ###
the quick brown fox
### NOCARRIER ndata=20 confidence=33.775 ampl=0.064 bps=10.00 (rate perfect) ###
This is still instantaneous, but shows that it works.
To play the tones in the wav file at the right speed, it needs to be played through an audio device. But we can use software loopback devices to do that:
$ sudo modprobe snd-aloop
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC662 rev1 Analog [ALC662 rev1 Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 3: HDMI 0 [HDMI 0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: Loopback [Loopback], device 0: Loopback PCM [Loopback PCM]
Subdevices: 7/8
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
Subdevice #4: subdevice #4
Subdevice #5: subdevice #5
Subdevice #6: subdevice #6
Subdevice #7: subdevice #7
card 1: Loopback [Loopback], device 1: Loopback PCM [Loopback PCM]
Subdevices: 8/8
Subdevice #0: subdevice #0
Subdevice #1: subdevice #1
Subdevice #2: subdevice #2
Subdevice #3: subdevice #3
Subdevice #4: subdevice #4
Subdevice #5: subdevice #5
Subdevice #6: subdevice #6
Subdevice #7: subdevice #7
We now have 8 loopback devices (the module has a parameter to ask for more). For example, we can now play sound on card 1, device 1, subdevice 2, and listen to it on card 1, device 0, subdevice 2.
To play, taking 20 seconds:
$ sox out.wav -t alsa hw:1,1,2
out.wav:
File Size: 1.96M Bit Rate: 768k
Encoding: Signed PCM
Channels: 1 @ 16-bit
Samplerate: 48000Hz
Replaygain: off
Duration: 00:00:20.40
In:47.7% 00:00:09.73 [00:00:10.67] Out:467k [ -=|=- ] Clip:0
To listen:
$ sox -q -t alsa hw:1,0,2 -t wav -c 1 - | minimodem --rx 10 -f -
### CARRIER 10.00 @ 1590.0 Hz ###
the quick brown fox
### NOCARRIER ndata=21 confidence=10.265 ampl=0.060 bps=10.00 (rate perfect) ###
The characters take 20 seconds to appear, one by one.