SSB modulation in software

NOTE: this post is outdated. See my ham DSP tutorial!

Single-sideband is one of the most popular modulation modes in analog voice radio communication today. SSB avoids the bandwidth doubling and unnecessary carrier emitting of amplitude modulation, thus concentrating radiated power only on the signal that carries the important information (for ex. voice).

If we take a 2KHz sine wave (modulator wave, this is the information we want to transport, so after demodulation, we would like to get this 2KHz beep) and modulate a 10KHz sine wave (carrier wave, this will carry our modulator wave) with it (you simply multiply the two sines), you get this:

You can see the two sidebands: LSB – lower sideband, the 8KHz sine, USB – upper sideband, the 12KHz sine. If you do this 2 sine multiplication in analog electronics, you will also get the 10KHz carrier wave.

Now this means if you want to transmit this 2KHz sine wave over radio on (for example) 3.791MHz, in theory you multiply the 2KHz sine with a 3.791MHz sine and transmit that resulting signal. In this case you will have to transmit both sidebands – if you transmit with 100 watts, your output power will distribute between the two sidebands, so this is not good, you only want to transmit one of the sidebands with full power.


A 4KHz wide LSB modulated voice signal

I played with three methods of generating an SSB modulated signal in software.

The first method is a very straight approach and only can be realized in software with no real benefit: let’s multiply our modulator wave with the carrier and then lowpass (or highpass) the sideband we want to keep.

This needs very sharp filtering. In the example I used FFT on the output, zeroed out all the unnecessary bins and then made an inverse FFT and got the result.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void CssbtestDlg::FillBuffer( float* pBuffer, int nBufferLength )
{
    int fft_i = 0;
    for( int i = 0; i < nBufferLength; i++ )
    {
        pBuffer[i] = (float)(0.5 * sin( 2*M_PI * 200 * i / (float)SAMPLESPERSECOND ) ) +
            + (float)(0.5 * sin( 2*M_PI * 500 * i / (float)SAMPLESPERSECOND ) ) +
            (float)(0.5 * sin( 2*M_PI * 1000 * i / (float)SAMPLESPERSECOND ) ) +
            (float)(0.5 * sin( 2*M_PI * 2000 * i / (float)SAMPLESPERSECOND ) );

        pBuffer[i] *= (float)sin( 2*M_PI * (10000) * i / (float)SAMPLESPERSECOND ); // osc1

        // filter
        if( fft_i++ == FFT_RES-1 )
        {
            for( int j=0; j < FFT_RES; j++ )
            {
                fft_in[j][0] = pBuffer[i-(FFT_RES-1)+j];
                fft_in[j][1] = 0;
            }

            fftw_execute(fft_pf);

            for( int j=10000; j < FFT_RES; j++ )
                fft_out[j][0] = fft_out[j][1] = 0;

            fftw_execute(fft_pb);

            for( int j=0; j < FFT_RES; j++ )
                pBuffer[i-(FFT_RES-1)+j] = fft_in[j][0]/FFT_RES;

            fft_i = 0;
        }
    }

    // demod
    //for( int i = 0; i < nBufferLength; i++ )
    //  pBuffer[i] *= (float)sin( 2*M_PI * 10000 * i / (float)SAMPLESPERSECOND );
}

Our buffer is exactly 44100 bytes long (this is the samplerate I used) and I set FFT_RES to this too, so the FFT library’s (libfftw) output is exactly linear, the 10000th bin means 10KHz and so on. In the example my carrier wave is a 10KHz sine, so I zeroed out all frequency components above that to get the lower sideband.

For demodulation of an SSB signal you can multiply it with a sine which has the frequency of the carrier wave (in this example 10KHz).

The second method is called the Hartley method. This method can be simulated very efficiently and easily in software. To generate an SSB signal with this method, two versions of the original signal are generated, mutually 90° out of phase. Each one of these signals is then mixed with carrier waves that are also 90° out of phase with each other. By either adding or subtracting the resulting signals, a lower or upper sideband signal results.

90 degrees phase delay can be realized with a Hilbert transformer. You can design one using this great page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#define NZEROS 512
#define GAIN   1.570483967e+00

static float xv[NZEROS+1] = {0};
static float delay[NZEROS+1] = {0};

static double xcoeffs[] =
  { +0.0000000000, +0.0003138613, +0.0000000000, +0.0003174376,
    +0.0000000000, +0.0003221740, +0.0000000000, +0.0003280972,
    +0.0000000000, +0.0003352340, +0.0000000000, +0.0003436114,
    +0.0000000000, +0.0003532566, +0.0000000000, +0.0003641970,
    +0.0000000000, +0.0003764603, +0.0000000000, +0.0003900744,
    +0.0000000000, +0.0004050673, +0.0000000000, +0.0004214676,
    +0.0000000000, +0.0004393040, +0.0000000000, +0.0004586054,
    +0.0000000000, +0.0004794011, +0.0000000000, +0.0005017209,
    +0.0000000000, +0.0005255947, +0.0000000000, +0.0005510531,
    +0.0000000000, +0.0005781267, +0.0000000000, +0.0006068469,
    +0.0000000000, +0.0006372454, +0.0000000000, +0.0006693544,
    +0.0000000000, +0.0007032067, +0.0000000000, +0.0007388356,
    +0.0000000000, +0.0007762750, +0.0000000000, +0.0008155595,
    +0.0000000000, +0.0008567243, +0.0000000000, +0.0008998053,
    +0.0000000000, +0.0009448395, +0.0000000000, +0.0009918643,
    +0.0000000000, +0.0010409181, +0.0000000000, +0.0010920405,
    +0.0000000000, +0.0011452719, +0.0000000000, +0.0012006537,
    +0.0000000000, +0.0012582286, +0.0000000000, +0.0013180405,
    +0.0000000000, +0.0013801346, +0.0000000000, +0.0014445576,
    +0.0000000000, +0.0015113575, +0.0000000000, +0.0015805840,
    +0.0000000000, +0.0016522886, +0.0000000000, +0.0017265245,
    +0.0000000000, +0.0018033470, +0.0000000000, +0.0018828134,
    +0.0000000000, +0.0019649832, +0.0000000000, +0.0020499184,
    +0.0000000000, +0.0021376834, +0.0000000000, +0.0022283457,
    +0.0000000000, +0.0023219754, +0.0000000000, +0.0024186459,
    +0.0000000000, +0.0025184340, +0.0000000000, +0.0026214200,
    +0.0000000000, +0.0027276884, +0.0000000000, +0.0028373275,
    +0.0000000000, +0.0029504304, +0.0000000000, +0.0030670949,
    +0.0000000000, +0.0031874241, +0.0000000000, +0.0033115266,
    +0.0000000000, +0.0034395171, +0.0000000000, +0.0035715167,
    +0.0000000000, +0.0037076536, +0.0000000000, +0.0038480636,
    +0.0000000000, +0.0039928907, +0.0000000000, +0.0041422875,
    +0.0000000000, +0.0042964166, +0.0000000000, +0.0044554506,
    +0.0000000000, +0.0046195735, +0.0000000000, +0.0047889815,
    +0.0000000000, +0.0049638842, +0.0000000000, +0.0051445053,
    +0.0000000000, +0.0053310847, +0.0000000000, +0.0055238792,
    +0.0000000000, +0.0057231643, +0.0000000000, +0.0059292361,
    +0.0000000000, +0.0061424131, +0.0000000000, +0.0063630386,
    +0.0000000000, +0.0065914829, +0.0000000000, +0.0068281459,
    +0.0000000000, +0.0070734605, +0.0000000000, +0.0073278960,
    +0.0000000000, +0.0075919615, +0.0000000000, +0.0078662107,
    +0.0000000000, +0.0081512469, +0.0000000000, +0.0084477282,
    +0.0000000000, +0.0087563740, +0.0000000000, +0.0090779724,
    +0.0000000000, +0.0094133886, +0.0000000000, +0.0097635740,
    +0.0000000000, +0.0101295776, +0.0000000000, +0.0105125586,
    +0.0000000000, +0.0109138011, +0.0000000000, +0.0113347310,
    +0.0000000000, +0.0117769368, +0.0000000000, +0.0122421921,
    +0.0000000000, +0.0127324845, +0.0000000000, +0.0132500473,
    +0.0000000000, +0.0137973997, +0.0000000000, +0.0143773929,
    +0.0000000000, +0.0149932668, +0.0000000000, +0.0156487183,
    +0.0000000000, +0.0163479841, +0.0000000000, +0.0170959429,
    +0.0000000000, +0.0178982414, +0.0000000000, +0.0187614506,
    +0.0000000000, +0.0196932631, +0.0000000000, +0.0207027420,
    +0.0000000000, +0.0218006399, +0.0000000000, +0.0229998108,
    +0.0000000000, +0.0243157494, +0.0000000000, +0.0257673042,
    +0.0000000000, +0.0273776363, +0.0000000000, +0.0291755264,
    +0.0000000000, +0.0311971870, +0.0000000000, +0.0334888257,
    +0.0000000000, +0.0361103470, +0.0000000000, +0.0391408311,
    +0.0000000000, +0.0426868713, +0.0000000000, +0.0468956751,
    +0.0000000000, +0.0519764409, +0.0000000000, +0.0582368230,
    +0.0000000000, +0.0661485683, +0.0000000000, +0.0764737425,
    +0.0000000000, +0.0905286557, +0.0000000000, +0.1107996896,
    +0.0000000000, +0.1426148288, +0.0000000000, +0.1998268664,
    +0.0000000000, +0.3332294323, +0.0000000000, +0.9999653629,
    +0.0000000000, -0.9999653629, -0.0000000000, -0.3332294323,
    -0.0000000000, -0.1998268664, -0.0000000000, -0.1426148288,
    -0.0000000000, -0.1107996896, -0.0000000000, -0.0905286557,
    -0.0000000000, -0.0764737425, -0.0000000000, -0.0661485683,
    -0.0000000000, -0.0582368230, -0.0000000000, -0.0519764409,
    -0.0000000000, -0.0468956751, -0.0000000000, -0.0426868713,
    -0.0000000000, -0.0391408311, -0.0000000000, -0.0361103470,
    -0.0000000000, -0.0334888257, -0.0000000000, -0.0311971870,
    -0.0000000000, -0.0291755264, -0.0000000000, -0.0273776363,
    -0.0000000000, -0.0257673042, -0.0000000000, -0.0243157494,
    -0.0000000000, -0.0229998108, -0.0000000000, -0.0218006399,
    -0.0000000000, -0.0207027420, -0.0000000000, -0.0196932631,
    -0.0000000000, -0.0187614506, -0.0000000000, -0.0178982414,
    -0.0000000000, -0.0170959429, -0.0000000000, -0.0163479841,
    -0.0000000000, -0.0156487183, -0.0000000000, -0.0149932668,
    -0.0000000000, -0.0143773929, -0.0000000000, -0.0137973997,
    -0.0000000000, -0.0132500473, -0.0000000000, -0.0127324845,
    -0.0000000000, -0.0122421921, -0.0000000000, -0.0117769368,
    -0.0000000000, -0.0113347310, -0.0000000000, -0.0109138011,
    -0.0000000000, -0.0105125586, -0.0000000000, -0.0101295776,
    -0.0000000000, -0.0097635740, -0.0000000000, -0.0094133886,
    -0.0000000000, -0.0090779724, -0.0000000000, -0.0087563740,
    -0.0000000000, -0.0084477282, -0.0000000000, -0.0081512469,
    -0.0000000000, -0.0078662107, -0.0000000000, -0.0075919615,
    -0.0000000000, -0.0073278960, -0.0000000000, -0.0070734605,
    -0.0000000000, -0.0068281459, -0.0000000000, -0.0065914829,
    -0.0000000000, -0.0063630386, -0.0000000000, -0.0061424131,
    -0.0000000000, -0.0059292361, -0.0000000000, -0.0057231643,
    -0.0000000000, -0.0055238792, -0.0000000000, -0.0053310847,
    -0.0000000000, -0.0051445053, -0.0000000000, -0.0049638842,
    -0.0000000000, -0.0047889815, -0.0000000000, -0.0046195735,
    -0.0000000000, -0.0044554506, -0.0000000000, -0.0042964166,
    -0.0000000000, -0.0041422875, -0.0000000000, -0.0039928907,
    -0.0000000000, -0.0038480636, -0.0000000000, -0.0037076536,
    -0.0000000000, -0.0035715167, -0.0000000000, -0.0034395171,
    -0.0000000000, -0.0033115266, -0.0000000000, -0.0031874241,
    -0.0000000000, -0.0030670949, -0.0000000000, -0.0029504304,
    -0.0000000000, -0.0028373275, -0.0000000000, -0.0027276884,
    -0.0000000000, -0.0026214200, -0.0000000000, -0.0025184340,
    -0.0000000000, -0.0024186459, -0.0000000000, -0.0023219754,
    -0.0000000000, -0.0022283457, -0.0000000000, -0.0021376834,
    -0.0000000000, -0.0020499184, -0.0000000000, -0.0019649832,
    -0.0000000000, -0.0018828134, -0.0000000000, -0.0018033470,
    -0.0000000000, -0.0017265245, -0.0000000000, -0.0016522886,
    -0.0000000000, -0.0015805840, -0.0000000000, -0.0015113575,
    -0.0000000000, -0.0014445576, -0.0000000000, -0.0013801346,
    -0.0000000000, -0.0013180405, -0.0000000000, -0.0012582286,
    -0.0000000000, -0.0012006537, -0.0000000000, -0.0011452719,
    -0.0000000000, -0.0010920405, -0.0000000000, -0.0010409181,
    -0.0000000000, -0.0009918643, -0.0000000000, -0.0009448395,
    -0.0000000000, -0.0008998053, -0.0000000000, -0.0008567243,
    -0.0000000000, -0.0008155595, -0.0000000000, -0.0007762750,
    -0.0000000000, -0.0007388356, -0.0000000000, -0.0007032067,
    -0.0000000000, -0.0006693544, -0.0000000000, -0.0006372454,
    -0.0000000000, -0.0006068469, -0.0000000000, -0.0005781267,
    -0.0000000000, -0.0005510531, -0.0000000000, -0.0005255947,
    -0.0000000000, -0.0005017209, -0.0000000000, -0.0004794011,
    -0.0000000000, -0.0004586054, -0.0000000000, -0.0004393040,
    -0.0000000000, -0.0004214676, -0.0000000000, -0.0004050673,
    -0.0000000000, -0.0003900744, -0.0000000000, -0.0003764603,
    -0.0000000000, -0.0003641970, -0.0000000000, -0.0003532566,
    -0.0000000000, -0.0003436114, -0.0000000000, -0.0003352340,
    -0.0000000000, -0.0003280972, -0.0000000000, -0.0003221740,
    -0.0000000000, -0.0003174376, -0.0000000000, -0.0003138613,
    -0.0000000000,
  };

void CssbtestDlg::FillBuffer( float* pBuffer, int nBufferLength )
{
    for( int i = 0; i < nBufferLength; i++ )
    {
        float sum;
        int j;
        float out;

        out = voice[voicep];
        //out = (float)(0.2 * sin( 2*M_PI * 200 * i / (float)SAMPLESPERSECOND ) ); // SINE
        //out += (float)(0.2 * (float)sin( 2*M_PI * 500 * i / (float)SAMPLESPERSECOND ) ); // SINE
        //out += (float)(0.2 * (float)sin( 2*M_PI * 800 * i / (float)SAMPLESPERSECOND ) ); // SINE
        for( j = 0; j < NZEROS; j++ )
            xv[j] = xv[j+1];
            xv[NZEROS] = (float)(out / GAIN);
            sum = 0.0;
        for( j = 0; j <= NZEROS; j++)
            sum += (float)( xcoeffs[j] * xv[j] );

        for( j = 0; j < NZEROS/2; j++ )
            delay[j] = delay[j+1];
            delay[NZEROS/2] = out;

        pBuffer[i] = delay[0]   *   cos( 2*M_PI * 10000 * i / (float)SAMPLESPERSECOND ) +
            sum   *   sin( 2*M_PI * 10000 * i / (float)SAMPLESPERSECOND );

        // demod
        //pBuffer[i] *= (float)sin( 2*M_PI * 10000 * i / (float)SAMPLESPERSECOND );

        voicep++;
        if( voicep == 120040 )
            voicep = 0;
    }
}

In this example I used a prerecorded voice with 4KHz bandwidth as the modulator. The carrier is at 10KHz. The resulting LSB signal can be seen here:

The third method is called the Weaver method. This is the most complicated one, it uses two quadrature oscillators and lowpass filters. A very nicely illustrated paper on this method can be downloaded here, read this if you want to learn the mechanisms of this method.

For lowpass filtering I used the previously described FFT – inverse FFT method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
void CssbtestDlg::FillBuffer( float* pBuffer, int nBufferLength )
{
    float pOut1[SAMPLESPERSECOND], pOut2[SAMPLESPERSECOND];

    int fft_i = 0;
    for( int i = 0; i < nBufferLength; i++ )
    {
        pOut1[i] = pOut2[i] = voice[voicep];
        /*pOut1[i] = pOut2[i] = (float)(0.5 * sin( 2*M_PI * 200 * i / (float)SAMPLESPERSECOND ) ) +
            + (float)(0.5 * sin( 2*M_PI * 500 * i / (float)SAMPLESPERSECOND ) ) +
            (float)(0.5 * sin( 2*M_PI * 1000 * i / (float)SAMPLESPERSECOND ) ) +
            (float)(0.5 * sin( 2*M_PI * 2000 * i / (float)SAMPLESPERSECOND ) );*/


        pOut1[i] *= (float)cos( 2*M_PI * (SAMPLESPERSECOND/4) * i / (float)SAMPLESPERSECOND ); // osc1
        pOut2[i] *= (float)sin( 2*M_PI * (SAMPLESPERSECOND/4) * i / (float)SAMPLESPERSECOND ); // osc1

        // filter
        if( fft_i++ == FFT_RES-1 )
        {
            // filter1
            for( int j=0; j < FFT_RES; j++ )
            {
                fft_in[j][0] = pOut1[i-(FFT_RES-1)+j];
                fft_in[j][1] = 0;
            }

            fftw_execute(fft_pf);

            for( int j=FFT_RES/4; j < FFT_RES; j++ )
                fft_out[j][0] = fft_out[j][1] = 0;

            fftw_execute(fft_pb);

            for( int j=0; j < FFT_RES; j++ )
            {
                pOut1[i-(FFT_RES-1)+j] = fft_in[j][0]/FFT_RES;
                pOut1[i-(FFT_RES-1)+j] *= (float)cos( 2*M_PI * (SAMPLESPERSECOND/4-10000) * (i-(FFT_RES-1)+j) / (float)SAMPLESPERSECOND ); // osc2
            }

            // filter2
            for( int j=0; j < FFT_RES; j++ )
            {
                fft_in[j][0] = pOut2[i-(FFT_RES-1)+j];
                fft_in[j][1] = 0;
            }

            fftw_execute(fft_pf);

            for( int j=FFT_RES/4; j < FFT_RES; j++ )
                fft_out[j][0] = fft_out[j][1] = 0;

            fftw_execute(fft_pb);

            for( int j=0; j < FFT_RES; j++ )
            {
                pOut2[i-(FFT_RES-1)+j] = fft_in[j][0]/FFT_RES;
                pOut2[i-(FFT_RES-1)+j] *= (float)sin( 2*M_PI * (SAMPLESPERSECOND/4-10000) * (i-(FFT_RES-1)+j) / (float)SAMPLESPERSECOND ); // osc2
            }

            fft_i = 0;
            for( int j=0; j < FFT_RES; j++ )
                pBuffer[i-(FFT_RES-1)+j] = pOut1[i-(FFT_RES-1)+j] + pOut2[i-(FFT_RES-1)+j];
        }

        voicep++;
        if( voicep == 120040 )
            voicep = 0;
    }

    // demod
    //for( int i = 0; i < nBufferLength; i++ )
    //  pBuffer[i] *= (float)sin( 2*M_PI * 10000 * i / (float)SAMPLESPERSECOND );
}

The resulting signal:

I hope you find this post useful. I had great time experimenting with all these stuff. More writings like this will come soon.

Marxy 2012-04-21 12:00:15

Wow!

Thanks so much for sharing your code.

I’m wondering if it is possible to generate ssb in real time on an arm processor such as the raspberry pi?

Marxy

Nonoo 2012-04-21 12:41:13

I’m sure you don’t need a Raspberry Pi, software SSB modulation will work fine on a much slower embedded PC or MCU.

 
 
DAVID K OSBURN 2013-09-03 18:17:20

Hi, I’m a real beginner, what code is your AM SSB and do you have any suggestions on how I can get started understanding and using the AM SSB up converter software.

Thanks.

David

Nonoo 2013-09-03 18:36:04

Hello, you should read my tutorials from the beginning ;)

 
 
Name (required)
E-mail (required - never shown publicly)
Webpage URL
Comment:
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> in your comment.

Trackback responses to this post

About me

Nonoo
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with. ... »

Twitter

Listening now

My favorite artists