DSP tutorial: SSB modulation using the Hartley method
Last modified: November 16th, 2011This example records audio and generates a lower sideband signal using the Hartley method, and saves it to a .wav file.
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.
You can see an output example (LSB signal, 10kHz carrier, 4kHz modulator bandwidth) here:
More info:
- http://www2.ece.ohio-state.edu/~potter/ECE508/Ch4_Oct1.pdf
- http://local.eleceng.uct.ac.za/courses/EEE3086F/notes/508-AM_SSB_2up.pdf
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 | public class SSBHartley implements Runnable { private final static int SAMPLERATE = 44100; private final static int BUFFERSIZE = SAMPLERATE * 2; private final static int HILBERT_NZEROS = 512; private final static double HILBERT_GAIN = 1.570483967e+00; private double[] hilbert_xv; private final double hilbert_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, }; private TargetDataLine tdl; SSBHartley(TargetDataLine tdl) { this.tdl = tdl; hilbert_xv = new double[HILBERT_NZEROS + 1]; } private double hilbertTransform(double in) { for (int j = 0; j < HILBERT_NZEROS; j++) hilbert_xv[j] = hilbert_xv[j + 1]; hilbert_xv[HILBERT_NZEROS] = in / HILBERT_GAIN; double sum = 0; for (int j = 0; j <= HILBERT_NZEROS; j++) sum += hilbert_xcoeffs[j] * hilbert_xv[j]; return sum; } // converts float array to byte array private byte[] getBytesFromDoubles(final double[] audioData, final int storedSamples) { byte[] audioDataBytes = new byte[storedSamples * 2]; for (int i = 0; i < storedSamples; i++) { // saturation audioData[i] = Math.min(1.0, Math.max(-1.0, audioData[i])); // scaling and conversion to integer int sample = (int) Math.round((audioData[i] + 1.0) * 32767.5) - 32768; byte high = (byte) ((sample >> 8) & 0xFF); byte low = (byte) (sample & 0xFF); audioDataBytes[i * 2] = low; audioDataBytes[i * 2 + 1] = high; } return audioDataBytes; } // saves the audio data given in audioDataBytes to a .wav file private void writeWavFile(final byte[] audioDataBytes, final int storedSamples, final String fileName) { AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, SAMPLERATE, 16, 1, 2, SAMPLERATE, false); AudioInputStream audioInputStream = new AudioInputStream(new ByteArrayInputStream(audioDataBytes), audioFormat, storedSamples); try { FileOutputStream fileOutputStream = new FileOutputStream(fileName); AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, fileOutputStream); audioInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { byte[] abBuffer = new byte[tdl.getBufferSize()]; double[] abBufferDouble = new double[abBuffer.length / 2]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); // this will store sound data double oscPhase = 0; double carrierFreq = 10000; double[] delay = new double[HILBERT_NZEROS / 2 + 1]; tdl.start(); try { while (!Thread.interrupted()) { // waiting for the buffer to get filled while (tdl.available() < tdl.getBufferSize() * 0.5) Thread.sleep(0, 1); // without this, the audio will be choppy int bytesRead = tdl.read(abBuffer, 0, tdl.available()); // converting frames stored as bytes to double values int samplesRead = bytesRead / tdl.getFormat().getFrameSize(); for (int i = 0; i < samplesRead; i++) abBufferDouble[i] = ((abBuffer[i * 2] & 0xFF) | (abBuffer[i * 2 + 1] << 8)) / 32768.0; for (int samplePos = 0; samplePos < samplesRead; samplePos++) { oscPhase += (2 * Math.PI * carrierFreq) / SAMPLERATE; // delay line for (int j = 0; j < HILBERT_NZEROS / 2; j++) delay[j] = delay[j + 1]; delay[HILBERT_NZEROS / 2] = abBufferDouble[samplePos]; abBufferDouble[samplePos] = delay[0] * Math.cos(oscPhase) + hilbertTransform(abBufferDouble[samplePos]) * Math.sin(oscPhase); if (oscPhase >= 2 * Math.PI) oscPhase -= 2 * Math.PI; } // demodulating (multiplying samples with the carrier again) /*oscPhase = 0; for (int samplePos = 0; samplePos < samplesRead; samplePos++) { oscPhase += (2 * Math.PI * carrierFreq) / SAMPLERATE; abBufferDouble[samplePos] *= Math.sin(oscPhase); if (oscPhase >= 2 * Math.PI) oscPhase -= 2 * Math.PI; }*/ baos.write(getBytesFromDoubles(abBufferDouble, samplesRead), 0, samplesRead * 2); } } catch (InterruptedException e) { } tdl.stop(); tdl.close(); writeWavFile(baos.toByteArray(), baos.size() / 2, "output.wav"); } public static void main(String[] args) { AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, SAMPLERATE, 16, 1, 2, SAMPLERATE, false); DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat, BUFFERSIZE); TargetDataLine targetDataLine = null; try { targetDataLine = (TargetDataLine) AudioSystem.getLine(info); targetDataLine.open(audioFormat, BUFFERSIZE); System.out.println("Buffer size: " + targetDataLine.getBufferSize()); } catch (LineUnavailableException e1) { e1.printStackTrace(); } // creating the recorder thread from this class' instance SSBHartley ssbHartley = new SSBHartley(targetDataLine); Thread ssbHartleyThread = new Thread(ssbHartley); // we use this to read line from the standard input BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); ssbHartleyThread.setPriority(Thread.MAX_PRIORITY); ssbHartleyThread.start(); System.out.println("Recording... press ENTER to stop recording!"); try { br.readLine(); } catch (IOException e) { e.printStackTrace(); } ssbHartleyThread.interrupt(); try { // waiting for the recorder thread to stop ssbHartleyThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Recording stopped."); } } |
About me
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with.
... »
Trackback URL
No comments yet.
Trackback responses to this post