├── LICENSE ├── README.md ├── minidemod-wfm-atan.c └── minidemod-wfm.c /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andras Retzler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | minidemod 2 | ========= 3 | 4 | The purpose of `minidemod-wfm.c` is to have a few lines of code that can actually demodulate WFM from an I/Q input. 5 | 6 | The whole DSP chain is built by OS pipes, just like this: 7 | 8 | rtl_sdr -s 240000 -f 89500000 -g 20 - | tcc -lm -run minidemod-wfm.c \ 9 | | sox -t raw -r 240000 -e unsigned -b 8 -c 1 - -t raw - rate 48000 \ 10 | | mplayer -quiet -rawaudio samplesize=1:channels=1:rate=48000 -demuxer rawaudio - 11 | 12 | Read like this: 13 | 14 | rtl_sdr (acquires samples) | minidemod-wfm (demodulates) \ 15 | | sox (low pass filter + decimation) | mplayer (audio output) ` 16 | 17 | [For systems with aplay, you can use this instead of mplayer: 18 | aplay -r 48000 -B1000000 19 | ] 20 | 21 | To run it, you will need a Linux box with `rtl_sdr tcc sox mplayer` installed. 22 | 23 | I've also added `minidemod-wfm-atan.c` with a detailed explanation of the demodulation process. It does sound better, but uses more CPU (still not more than 10% on my box). 24 | 25 | ## Follow-up 26 | 27 | At SDRA-2018 I gave a talk on writing a simple AM/FM/SSB receiver in C: 28 | 29 | https://www.youtube.com/watch?v=-QERqK1XAy0 30 | 31 | The code (which actually fits on 2 sheets of A4 paper) can be found here: 32 | 33 | https://github.com/ha7ilm/smallrx 34 | 35 | If you need a fully fledged command-line DSP tool for SDR, see my CSDR project here: 36 | 37 | https://github.com/simonyiszk/csdr 38 | 39 | ## How this tool can be used as a demo on SDR 40 | 41 | I used this tool to introduce some fellow students to SDR, after a short explanation on SDR and modulations in general. 42 | 43 | I showed them the code, but previously deleted the formula that does the actual calculation, turning this into a fun exercise: they had to figure out that one line themselves. 44 | 45 | If they entered the correct formula, the success was immediate: audio was playing through the speakers. 46 | 47 | Authors 48 | ------- 49 | 50 | András Retzler 51 | 52 | Also tnx [dnet](https://github.com/dnet) for the small fixes. 53 | -------------------------------------------------------------------------------- /minidemod-wfm-atan.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2014 Andras Retzler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ----------------------------------------------------------------------------- 24 | 25 | To play with mplayer: 26 | 27 | rtl_sdr -s 240000 -f 89500000 -g 20 - | tcc -lm -run minidemod-wfm-atan.c | sox -t raw -r 240000 -e signed -b 16 -c 1 - -t raw - rate 48000 | mplayer -quiet -rawaudio samplesize=2:channels=1:rate=48000 -demuxer rawaudio - 28 | 29 | Some parameters of rtl_sdr - e.g. frequency and gain - should be acquired by experimentation with a correct SDR software (gqrx or SDR#). 30 | 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #define PI M_PI 37 | 38 | double get_phase(double i, double q) 39 | { 40 | double phi=atan(q/i); //It is correct in quadrant I. 41 | if(i<0&&q>0) phi += PI; // Correction for quadrant II. 42 | else if(i<0&&q<0) phi += PI; // Correction for quadrant III. 43 | else if(i>0&&q<0) phi +=2*PI; // Correction for quadrant IV. 44 | else if(i==0&&q>0) phi = PI/2; // 90 degrees 45 | else if(i==0&&q<0) phi = 3*(PI/2); // 270 degrees 46 | else if(i<0&&q==0) phi = PI; // 180 degrees 47 | else if(i>0&&q==0) phi = 0; // 0 degrees 48 | return phi; // between 0 and 2*PI. 49 | } 50 | 51 | int main() 52 | { 53 | double i1=0, i2, q1=0, q2; 54 | short s; 55 | for(;;) 56 | { 57 | i2=((unsigned char)getchar()-127); q2=((unsigned char)getchar()-127); 58 | 59 | //Frequency deviation is actually the time derivative of phase. s(t)=dPhi / dt 60 | double phi1=get_phase(i1,q1); 61 | double phi2=get_phase(i2,q2); 62 | /* 63 | If we calculated output by just substracting phases from each other, jumping from 359° to 1° would lead to 64 | faulty behaviour (indicating 358° phase change, while it is really 2° in the other direction...) 65 | 66 | In fact, by interpreting the Nyquist theorem on complex signals, one can find out that signal phase change 67 | should be less than 180 degrees while stepping one single sample. So we do this: 68 | */ 69 | 70 | double dphi=phi2-phi1; 71 | if(dphi<-PI) dphi+=2*PI; //dphi below -180°? Let's interpret it in the other direction! 72 | if(dphi>PI) dphi-=2*PI; //dphi above 180°? Let's interpret it in the other direction! 73 | 74 | // Now let's use signed 16 bit output rather than 8 bit unsigned. 75 | s=((SHRT_MAX-1)/PI)*dphi; //Okay so let's get the actual sample. dphi should be between -PI and +PI. 76 | 77 | //Note that you can't do this whole thing on CPU / DSP architectures that can't do floating point operations. 78 | //Hence the easy formula in minidemod-wfm.c 79 | 80 | fwrite(&s, sizeof(short), 1, stdout); 81 | i1=i2; q1=q2; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /minidemod-wfm.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2014 Andras Retzler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ----------------------------------------------------------------------------- 24 | 25 | To play with mplayer (recommended): 26 | 27 | rtl_sdr -s 240000 -f 89500000 -g 20 - | tcc -lm -run minidemod-wfm.c | sox -t raw -r 240000 -e unsigned -b 8 -c 1 - -t raw - rate 48000 | mplayer -quiet -rawaudio samplesize=1:channels=1:rate=48000 -demuxer rawaudio - 28 | 29 | To play with ALSA: 30 | 31 | rtl_sdr -s 240000 -f 89500000 -g 20 - | tcc -lm -run minidemod-wfm.c | sox -t raw -r 240000 -e unsigned -b 8 -c 1 - -t raw - rate 48000 | aplay -f U8 -c1 -r 48000 --buffer-size=200000 32 | 33 | Some parameters of rtl_sdr - e.g. frequency and gain - should be acquired by experimentation with a correct SDR software (gqrx or SDR#). 34 | 35 | */ 36 | 37 | #include 38 | #include 39 | 40 | int main() 41 | { 42 | double i1=0, i2, q1=0, q2, s; 43 | for(;;) 44 | { 45 | i2=((unsigned char)getchar()-127); q2=((unsigned char)getchar()-127); 46 | 47 | //Formula 1: http://www.dsprelated.com/showmessage/142012/1.php 48 | s=70*((i2*(q2-q1)) - (q2*(i2-i1)))/(i2*i2+q2*q2); 49 | 50 | //Formula 2: freq. deviation is actually the time derivative of phase (more precise way in minidemod-wfm-atan.c) 51 | //s=100*(atan(i2/q2)-atan(i1/q1)); 52 | 53 | putchar((unsigned char)(s+127)); 54 | i1=i2; q1=q2; 55 | } 56 | } 57 | 58 | --------------------------------------------------------------------------------