This example records audio and generates the lower sideband signal of it using the Weaver method, and saves it to a .wav file.

The Weaver method 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 a Butterworth filter. It’s cutFreq is 10kHz because it is not very steep, but in an ideal case, it should be samplerate/4. I left the lines for FFT filtering in the code (commented out).

Example resulting signal:

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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
public class SSBWeaver implements Runnable {
    private final static int SAMPLERATE = 44100;
    private final static int BUFFERSIZE = SAMPLERATE * 2;
    private final static int IIR_NZEROS = 10;
    private final static int IIR_NPOLES = 10;
    private final static double IIR_GAIN = 3.452507086e+02;

    // these are used for the two lowpass filters
    private double xv_H[], yv_H[];
    private double xv_L[], yv_L[];

    private TargetDataLine tdl;
    //private DoubleFFT_1D fft;

    public SSBWeaver(TargetDataLine tdl) {
        this.tdl = tdl;
    }

    // IIR Butterworth lowpass filter 1
    // see http://www-users.cs.york.ac.uk/~fisher/mkfilter/
    private double lowPassFilterH(double in) {
        xv_H[0] = xv_H[1];
        xv_H[1] = xv_H[2];
        xv_H[2] = xv_H[3];
        xv_H[3] = xv_H[4];
        xv_H[4] = xv_H[5];
        xv_H[5] = xv_H[6];
        xv_H[6] = xv_H[7];
        xv_H[7] = xv_H[8];
        xv_H[8] = xv_H[9];
        xv_H[9] = xv_H[10];
        xv_H[10] = in / IIR_GAIN;
        yv_H[0] = yv_H[1];
        yv_H[1] = yv_H[2];
        yv_H[2] = yv_H[3];
        yv_H[3] = yv_H[4];
        yv_H[4] = yv_H[5];
        yv_H[5] = yv_H[6];
        yv_H[6] = yv_H[7];
        yv_H[7] = yv_H[8];
        yv_H[8] = yv_H[9];
        yv_H[9] = yv_H[10];
        yv_H[10] = (xv_H[0] + xv_H[10]) + 10 * (xv_H[1] + xv_H[9]) + 45 * (xv_H[2] + xv_H[8]) + 120 * (xv_H[3] + xv_H[7]) + 210 * (xv_H[4] + xv_H[6]) + 252 * xv_H[5] + (-0.0000357500 * yv_H[0]) + (0.0006094904 * yv_H[1]) + (-0.0061672448 * yv_H[2]) + (0.0298428117 * yv_H[3]) + (-0.1344215690 * yv_H[4]) + (0.3160380661 * yv_H[5]) + (-0.8283557825 * yv_H[6]) + (1.0214778432 * yv_H[7]) + (-1.7026748886 * yv_H[8]) + (0.9259874207 * yv_H[9]);
        return yv_H[10];
    }

    // IIR Butterworth lowpass filter 2
    private double lowPassFilterL(double in) {
        xv_L[0] = xv_L[1];
        xv_L[1] = xv_L[2];
        xv_L[2] = xv_L[3];
        xv_L[3] = xv_L[4];
        xv_L[4] = xv_L[5];
        xv_L[5] = xv_L[6];
        xv_L[6] = xv_L[7];
        xv_L[7] = xv_L[8];
        xv_L[8] = xv_L[9];
        xv_L[9] = xv_L[10];
        xv_L[10] = in / IIR_GAIN;
        yv_L[0] = yv_L[1];
        yv_L[1] = yv_L[2];
        yv_L[2] = yv_L[3];
        yv_L[3] = yv_L[4];
        yv_L[4] = yv_L[5];
        yv_L[5] = yv_L[6];
        yv_L[6] = yv_L[7];
        yv_L[7] = yv_L[8];
        yv_L[8] = yv_L[9];
        yv_L[9] = yv_L[10];
        yv_L[10] = (xv_L[0] + xv_L[10]) + 10 * (xv_L[1] + xv_L[9]) + 45 * (xv_L[2] + xv_L[8]) + 120 * (xv_L[3] + xv_L[7]) + 210 * (xv_L[4] + xv_L[6]) + 252 * xv_L[5] + (-0.0000357500 * yv_L[0]) + (0.0006094904 * yv_L[1]) + (-0.0061672448 * yv_L[2]) + (0.0298428117 * yv_L[3]) + (-0.1344215690 * yv_L[4]) + (0.3160380661 * yv_L[5]) + (-0.8283557825 * yv_L[6]) + (1.0214778432 * yv_L[7]) + (-1.7026748886 * yv_L[8]) + (0.9259874207 * yv_L[9]);
        return yv_L[10];
    }
/*
    private void lowPassFilterFFT(double[] audioData, final int storedSamples, final double cutFreq) {
        // we need to initialize a buffer where we store our samples as complex numbers. first value is the real part, second is the imaginary.
        double[] fftData = new double[audioData.length * 2];
        for (int i = 0; i < storedSamples; i++) {
            // copying audio data to the fft data buffer, imaginary part is 0
            fftData[2 * i] = audioData[i];
            fftData[2 * i + 1] = 0;
        }

        // calculating the fft of the data, so we will have spectral power of each frequency component
        fft.complexForward(fftData);

        for (int i = 0; i < fftData.length; i += 2) {
            // lowpass
            if (i > ((cutFreq * (fftData.length/2)) / SAMPLERATE)*2)
                fftData[i] = fftData[i + 1] = 0;
        }

        // built-in scaling hangs the thread, so we don't use it
        fft.complexInverse(fftData, false);

        for (int i = 0; i < storedSamples; i++) {
            audioData[i] = fftData[2 * i] / (SAMPLERATE / 4.0); // applying scaling
        }
    }
*/

    // 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 osc1Phase = 0, osc2Phase = 0;
        double carrierFreq = 10000;
        double osc1Freq = SAMPLERATE/4;
        double osc2Freq = osc1Freq - carrierFreq; // pitch shift is given by the difference between the two oscillators
        double demodPhase = 0;

        //fft = new DoubleFFT_1D(abBufferDouble.length);
        double[] lp_H = new double[abBufferDouble.length];
        double[] lp_L = new double[abBufferDouble.length];

        xv_H = new double[IIR_NZEROS + 1];
        yv_H = new double[IIR_NPOLES + 1];
        xv_L = new double[IIR_NZEROS + 1];
        yv_L = new double[IIR_NPOLES + 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++) {
                    osc1Phase += (2 * Math.PI * osc1Freq) / SAMPLERATE;

                    lp_H[samplePos] = abBufferDouble[samplePos] * Math.sin(osc1Phase);
                    lp_L[samplePos] = abBufferDouble[samplePos] * Math.cos(osc1Phase);

                    if (osc1Phase >= 2 * Math.PI)
                        osc1Phase -= 2 * Math.PI;
                }

                // filtering can be done using FFT also
                //lowPassFilterFFT(l1, samplesRead, osc1Freq);
                //lowPassFilterFFT(l2, samplesRead, osc1Freq);
                for (int samplePos = 0; samplePos < samplesRead; samplePos++) {
                    lp_H[samplePos] = lowPassFilterH(lp_H[samplePos]);
                    lp_L[samplePos] = lowPassFilterL(lp_L[samplePos]);
                }

                // compensating the delay of the IIR filter by delaying osc2
                for (int i = 0; i < IIR_NZEROS; i++) {
                    osc2Phase += (2 * Math.PI * osc2Freq) / SAMPLERATE;
                    if (osc2Phase >= 2 * Math.PI)
                        osc2Phase -= 2 * Math.PI;
                }

                for (int samplePos = 0; samplePos < samplesRead; samplePos++) {
                    osc2Phase += (2 * Math.PI * osc2Freq) / SAMPLERATE;

                    lp_H[samplePos] *= Math.sin(osc2Phase);
                    lp_L[samplePos] *= Math.cos(osc2Phase);

                    abBufferDouble[samplePos] = lp_H[samplePos] + lp_L[samplePos];

                    if (osc2Phase >= 2 * Math.PI)
                        osc2Phase -= 2 * Math.PI;
                }

                // demodulating (multiplying samples with the carrier again)
                /*for (int samplePos = 0; samplePos < samplesRead; samplePos++) {
                    demodPhase += (2 * Math.PI * carrierFreq) / SAMPLERATE;
                    abBufferDouble[samplePos] *= Math.sin(demodPhase);

                    if (demodPhase >= 2 * Math.PI)
                        demodPhase -= 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
        SSBWeaver ssbWeaver = new SSBWeaver(targetDataLine);
        Thread ssbWeaverThread = new Thread(ssbWeaver);

        // we use this to read line from the standard input
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        ssbWeaverThread.setPriority(Thread.MAX_PRIORITY);
        ssbWeaverThread.start();

        System.out.println("Recording... press ENTER to stop recording!");
        try {
            br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ssbWeaverThread.interrupt();

        try {
            // waiting for the recorder thread to stop
            ssbWeaverThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Recording stopped.");
    }

}

download (973.6 kb)

Walter 2013-07-20 17:05:26

Hi Nonoo,
I’ve been playing with your SSBWeaver.java program. I downloaded it and compiled it (after commenting out: import edu.emory.mathcs.jtransforms.fft.DoubleFFT_ID:). Then I tried recording some piano notes (e.g., A3, C4, etc.). When I played the .wav file, I expected to hear a pitch shift (along with some SSB distortion). But there was no pitch shift. I also tried changing the carrierFreq value from 10000 to 440. Still no pitch shift. Do you have any idea what I am doing wrong, or am I expecting the wrong response? Thanks in advance… I’m working on a software defined radio (SDR) project and wanted to add SSB reception. The current project is on SourceForge.net (enter: fun2audio in its Search window).

Nonoo 2013-07-21 09:07:03

Maybe the problem is that you commented out that DoubleFFT line.

Walter 2013-07-21 17:24:40

Hi again,
Since all of the other fft code had been commented out, I assumed that that line was not needed.

I will keep looking…

Walter 2013-07-21 19:41:46

OK, now the results make sense! I used the Audacity audio program to run the output.wav file. I used its Analyze | Plot Spectrun… tool to look at its spectrum. For an input tone of 261.63 (middle C on a piano(,the output peaks at about 9000 Hz, which is well above my hearing range! Impressive code! Thanks for sharing it…

 
 
 
 
Walter 2013-09-01 16:58:26

Hi again Nonoo
I ran your filter design at:
http://www-users.cs.york.ac.uk/~fisher/mkfilter/ with the same parameters as I assume you used: Butterworth, Lowpass, order=10, samplerate=44100, corner=10000. Everything was the same except the GAIN that was generated was: 7.432679795e+02. in your code the IIR_GAIN is: 3.452507086e+02. Do you remember where the difference came from? I’m trying to re-implement your program in C.

Thanks in advance…

Nonoo 2013-09-01 17:31:36

Huh I can’t remember, but if you’re finished with the C conversion, please upload it somewhere and post the link to it here. :)

 
 
SAM 2013-10-03 06:06:08

Hello,

I just downloaded a plugin for Frequency shifter for V Machine but I don’t have a V Machine physically. I just downloaded VFX software and installed on my Win XP and also the plugin Frequency Shifter (32 bit) was added.

I can be able to see the VST editor and all that stuff at the right panel side of VFX application. In the options I have configured the DirectSound under Audio system option.

My question is I’m trying to change (shift) the FREQUENCY of a normal audio file .wav or .mp3 with 44100 khz sample rate with 8 or 16 bit per sample 128 or 320 kbps file from its normal frequency range, say – 8 – 10 khz and shift it to the range between 18- 20 KHZ. Is this possible with the above setup or do I require a V Machine? If it is possible to do it without V Machine then I would love to hear it from you using SSB modulation etc., and I will be obliged.

The statistics are given below –

Wavosaur statistics and information
————————————
Statistics:

RMS power L: 7.28% (-22.75 dB)

RMS power R: 7.27% (-22.76 dB)

Average value L (DC offset): -0.00% (-94.74 dB)

Average value R (DC offset): 0.00% (-89.95 dB)

Min value L: -98.11% (-0.17 dB)

Min value R: -100.13% (0.01 dB)

Max value L: 91.92% (-0.73 dB)

Max value R: 89.03% (-1.01 dB)

General information:

Sample name: D:\Ex\sam.mp3

Channel number: 2

Sample number: 1166976

Total duration: 00:00:26:462

Frequency: 44100Hz

Bits per sample: 16

Format: PCM

Selection:

Selection start: 0

Selection end: 1166975

Delta: 1166976

No loop points

No marker points

No automation points

Thanks in advance for your valuable help.

Warm Regards,
SAM

Nonoo 2013-10-03 08:43:54

Hello, this is not a plugin, and I don’t know what V machine is. You’re missing something.

SAM 2013-10-04 04:35:03

Ok,
Let me make it simple.

I have an audio file whose specifications and details are given in my previous post here.

My sole purpose is to know if it is possible to change (SHIFT) the frequency of a file like above from 8-10 KHZ (for instance) to 18-20 KHZ and REVERSE of that. For instance a high frequency file of 18-20 KHZ and shift its frequency to 8-10 KHZ for whatever normal human hearing range is?

Is this possible using any wave editor program like Audacity or any other application in Win XP?

Thanks in advance for your valuable help.

SAM

Nonoo 2013-10-04 09:46:52

Yes it’s possible, for example Sound Forge has a tool called “pitch shifter”. I think Audacity has this feature too.

SAM 2013-10-05 06:43:56

Thanks, I will give it a try. But I was wondering what does SSB modulation exactly do?

SAM

 
 
 
 
 
 
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