简体   繁体   中英

How can I make cancellation sound by using single frequency sound?

I haven't experience to use javascript. But I want to demonstrate sound reduction or cancellation to high school students by using single frequency sound in class. I've searched sound generator & detection code in website. Now I can find out frequency but I cannot make phase shifting sound to reduce sound. Could you help me advices to make phase shifting sound to reduce sound?

//Single frequency sound generator
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

public class MakeSound {
    public static void main(String[] args) throws LineUnavailableException {
        System.out.println("Generate Noise!");
        byte[] buf = new byte[2];
        int samplingsize = 44100;
        AudioFormat af = new AudioFormat((float) samplingsize, 16, 1, true, false);
        SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
        sdl.open();
        sdl.start();
        int duration = 500000; // noise generating duration [ms]
        int noise_frequency = 315; // noise frequency
        System.out.println("Noise Frequency:"+noise_frequency+"Hz");
        for (int i = 0; i < duration*(float) 44100/1000; i++) { 
            float numberOfSamplesToRepresentFullSin = (float) samplingsize / noise_frequency;
            double angle = i / (numberOfSamplesToRepresentFullSin/ 2.0) * Math.PI;
            short a = (short) (Math.sin(angle) * 32767); //32767 - max value for sample to take (-32767 to 32767)
            buf[0] = (byte) (a & 0xFF);
            buf[1] = (byte) (a >> 8);
            sdl.write(buf, 0, 2);
            }
        sdl.drain();
        sdl.stop();
        }
    }

//Frequency detection & phase shifting sound generator
package fft_1;
import java.nio.ByteBuffer;  
import java.nio.ByteOrder;  

import javax.sound.sampled.AudioFormat;  
import javax.sound.sampled.AudioSystem;  
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

import org.apache.commons.math3.complex.Complex;  
import org.apache.commons.math3.transform.DftNormalization;  
import org.apache.commons.math3.transform.FastFourierTransformer;  
import org.apache.commons.math3.transform.TransformType;  

@SuppressWarnings("unused")
public class AudioInput {  

    TargetDataLine  microphone;  

    final int       audioFrames= 8192;  //power ^ 2  

    final float     sampleRate= 8000.0f;  
    final int       bitsPerRecord= 16;  
    final int       channels= 1;  
    final boolean   bigEndian = true;  
    final boolean   signed= true;  

    byte            byteData[];     // length=audioFrames * 2  
    double          doubleData[];   // length=audioFrames only reals needed for apache lib.  
    AudioFormat     format;  
    FastFourierTransformer transformer;  

    public AudioInput () {  

        byteData= new byte[audioFrames * 2];  //two bytes per audio frame, 16 bits  

        doubleData= new double[audioFrames * 2];  // real & imaginary  
        doubleData= new double[audioFrames];  // only real for apache  

        transformer = new FastFourierTransformer(DftNormalization.STANDARD);  

        System.out.print("Microphone initialization\n");  
        format = new AudioFormat(sampleRate, bitsPerRecord, channels, signed, bigEndian);  
        DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format is an AudioFormat object  

        if (!AudioSystem.isLineSupported(info)) {  
            System.err.print("isLineSupported failed");  
            System.exit(1);  
        }  

        try {  
             microphone = (TargetDataLine) AudioSystem.getLine(info);  
             microphone.open(format);  
             System.out.print("Microphone opened with format: "+format.toString()+"\n");  
             microphone.start();  
        }
        catch(Exception ex){  
            System.out.println("Microphone failed: "+ex.getMessage());  
            System.exit(1);  
        }  

    }  

    public int readPcm(){  
        int numBytesRead=   
                microphone.read(byteData, 0, byteData.length);  
        if(numBytesRead!=byteData.length){  
            System.out.println("Warning: read less bytes than buffer size");  
            System.exit(1);  
        }  
        return numBytesRead;  
    }  


    @SuppressWarnings({ })
    public void byteToDouble(){  
        ByteBuffer buf= ByteBuffer.wrap(byteData);  
        buf.order(ByteOrder.BIG_ENDIAN);  
        int i=0;   

        while(buf.remaining()>2){  
            short s = buf.getShort();  
            doubleData[ i ] = (new Short(s)).doubleValue();  
            ++i;  
        }  
        System.out.println("Parsed "+i+" doubles from "+byteData.length+" bytes");  
    }  


    public void findFrequency() throws LineUnavailableException{
        
        float frequency;  
        Complex[] cmplx= transformer.transform(doubleData, TransformType.FORWARD);  
        double real = 0;  
        double im = 0;  
        double mag[] = new double[cmplx.length];  
        
        byte[] buf = new byte[2];
        int samplingsize = 44100;
        AudioFormat af = new AudioFormat((float) samplingsize, 16, 1, true, false);
        SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
        sdl.open();
        sdl.start();
        

        for(int i = 0; i < cmplx.length; i++){  
            real = cmplx[i].getReal();  
            im = cmplx[i].getImaginary();  
            mag[i] = Math.sqrt((real * real) + (im*im));  
        }  

        double peak = -1.0;  
        int index=-1;  
        for(int i = 0; i < cmplx.length; i++){  
            if(peak < mag[i]){  
                index=i;  
                peak= mag[i];  
            }  
        }  
        frequency = (sampleRate * index) / audioFrames;  
        System.out.print("Index: "+index+", Frequency: "+frequency+"\n"); 
        
        int duration = 3000; // duration millisecond
        int beatpersec = (int) Math.round(frequency);
        
        for (int i = 0; i < frequency/2 ; i++) {    
             System.out.println("i"+i); 
            
        }
        for (int i = 0; i < duration*(float) 44100/1000; i++) { 
            float numberOfSamplesToRepresentFullSin = (float) samplingsize / beatpersec;
            double angle = i / (numberOfSamplesToRepresentFullSin/ 2.0) * Math.PI;
            short a = (short) (Math.sin(angle) * 32767); //32767 - max value for sample to take (-32767 to 32767)
            buf[0] = (byte) (a & 0xFF);
            buf[1] = (byte) (a >> 8);
            sdl.write(buf, 0, 2);
            }
        sdl.drain();
        sdl.stop();
        }
    
    
    public void printFreqs(){  
        for (int i=0; i<audioFrames/4; i++){  
             //System.out.println("bin "+i+", freq: "+(sampleRate*i)/audioFrames);  
            System.out.println("End");
        }  
    }  

    public static void main(String[] args) throws LineUnavailableException {  
        AudioInput ai= new AudioInput();  
        int turns=1;  
        while(turns-- > 0){  
            ai.readPcm();  
            ai.byteToDouble();  
            ai.findFrequency();  
        }  


ai.printFreqs();  
    }  
}

I'm just looking at the MakeSound class. I'm assuming if we had a controlled way of altering its phase, that would be sufficient for your needs.

First off, include a slider control in the project. It's output should go from 0 to one full period, depending on your "angle" units. If it's degrees, it could be 0 to 359.

Put the sdl.write method in its own thread, inside a while loop and keep it running continuously.

Make a class or function that provides the "next" block of sine data on demand. If you need an array size for the write, something like 4K might be a good starting guess. In my experience, anything from 1k to 8k works fine. The while loop holding the sdl calls this function once per each write operation.

Now, your angle value in your data on-demand function needs to be determined by adding two parts: (1) the part that cycles on pitch continously (similar to what you are already doing), (2) a "phase" variable that holds an angle value that can range from 0 to one full period.

Have the slider tied to the "phase" variable. Probably some form of loose coupling would be good, to prevent the changes to the "phase" variable from blocking the sine-wave calculation.

A couple of cautions, though. For one, as you move the slider, you will likely create some clicks unless you build in a function to spread out the changes in the "phase" value over, say, 128 PCM values. Secondly, the volumes have to match for true cancellation, so a volume slider as well as the phase slider might be needed. The "volume" slider can range from 0 to 1, creating a factor that you multiply against the PCM values that you are holding in the short array.

The main thing, since there is a single starting point for this continuous signal, (thanks to running the sdl continuously in the while loop), there should be some point on the slide that best corresponds to the cancellation. It will be a different point on the slider each time, of course.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM