Search code examples
javaaudiotimejavasoundpitch

To modify the pitch of an audio file at specific positions in the files using the codes in java below


I want to modify the pitch of the audio clip dynamically based on the user input at different time instants, say if user enters change the pitch of the audio after 10 seconds then how can I achieve the same?

I found this link that describes how to modify the pitch but i want to repeat this process at different time instants of the audio clip and for some short duration only. Can anybody guide me on this ?

Some Edits

Edit 1

I found this code as I mentioned this previously as well

//source file
final File file1 = new File(“Sample.mp3”);
//destination file
final File file2 = new File(“Sample_cat.wav”);
//audio stream of file1
final AudioInputStream in1 = getAudioInputStream(file1);
//get audio format for targetted sound
final AudioFormat inFormat = getOutFormat(in1.getFormat());
//change the frequency of Audio format
private AudioFormat getOutFormat(AudioFormat inFormat) {
        int ch = inFormat.getChannels();
        float rate = inFormat.getSampleRate();    
        return new AudioFormat(PCM_SIGNED, 72000, 16, ch, ch * 2, rate,
                inFormat.isBigEndian());
    }
//get the target file audio stream using file format
final AudioInputStream in2 = getAudioInputStream(inFormat, in1);
//write the audio file in targeted pitch file
AudioSystem.write(in2, AudioFileFormat.Type.WAVE, file2);

EDIT 2 I found another code which sets the position of audio file from where you want to start and stop the audio.

        File audioFile = new File(audioFilePath);


        AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);

        AudioFormat format = audioStream.getFormat();

        DataLine.Info info = new DataLine.Info(Clip.class, format);

        Clip audioClip = (Clip) AudioSystem.getLine(info); 
        audioClip.open(audioStream);
        audioClip.setLoopPoints(10_000, 500_000);
        audioClip.loop(1);

Now, How can I change the pitch for the duration set in Edit 2 i.e. 10 ms to 50 ms using the code in Edit 1

can anybody suggest me if I can do the same thing in any other ways except Java ? then suggestions are welcome... Please Help. I am new to this.

**Edit 3 **

One can refer the exact problem on this link : link

These are the values(in milliseconds) that I am referring :

public static void convertMsgToAudio(String msg){

        int len = msg.length();
        duration = new double[len];
        msg = msg.toUpperCase();
        System.out.println("Msg 2 : " + msg);

        int i;
        //char ch;
        for(i=0;i<msg.length();i++){

            if(msg.charAt(i) == 'A'){
                duration[i] = 50000;
            }
            else if (msg.charAt(i) == 'B'){
                duration[i] = 100000;
            }
            else if (msg.charAt(i) == 'C'){
                duration[i] = 150000;
            }
            else if (msg.charAt(i) == 'D'){
                duration[i] = 200000;               
            }
            else if (msg.charAt(i) == 'E'){
                duration[i] = 250000;
            }
            else if (msg.charAt(i) == 'F'){
                duration[i] = 300000;
            }
            else if (msg.charAt(i) == 'G'){
                duration[i] = 350000;
            }
            else if (msg.charAt(i) == 'H'){
                duration[i] = 400000;
            }
            else if (msg.charAt(i) == 'I'){
                duration[i] = 450000;
            }
            else if (msg.charAt(i) == 'J'){
                duration[i] = 500000;
            }
            else if (msg.charAt(i) == 'K'){
                duration[i] = 550000;
            }
            else if (msg.charAt(i) == 'L'){
                duration[i] = 600000;
            }
            else if (msg.charAt(i) == 'M'){
                duration[i] = 650000;
            }
            else if (msg.charAt(i) == 'N'){
                duration[i] = 700000;
            }
            else if (msg.charAt(i) == 'O'){
                duration[i] = 750000;
            }
            else if (msg.charAt(i) == 'P'){
                duration[i] = 800000;
            }
            else if (msg.charAt(i) == 'Q'){
                duration[i] = 850000;
            }
            else if (msg.charAt(i) == 'R'){
                duration[i] = 900000;
            }
            else if (msg.charAt(i) == 'S'){
                duration[i] = 950000;
            }
            else if (msg.charAt(i) == 'T'){
                duration[i] = 1000000;
            }
            else if (msg.charAt(i) == 'U'){
                duration[i] = 1100000;
            }
            else if (msg.charAt(i) == 'V'){
                duration[i] = 1200000;
            }
            else if (msg.charAt(i) == 'W'){
                duration[i] = 1300000;
            }
            else if (msg.charAt(i) == 'X'){
                duration[i] = 1400000;
            }
            else if (msg.charAt(i) == 'Y'){
                duration[i] = 1500000;
            }
            else if (msg.charAt(i) == 'Z'){
                duration[i] = 1600000;
            }

        }

    }

Solution

  • Java does not expose the data in a Clip for editing, as far as I know.

    I've never tried altering the pitch by messing with the sample rate. Maybe that is a good way to go. There is a section of the Java tutorials that covers changes in formatting of wav files: Using Files and Format Converters. Seems like this would be good background info and may even cover the solution you are attempting.

    Here is what I do, call it a VarispeedWavPlayer.

    (1) have a volatile instance float variable that is a speed factor (1 is same speed, 1.1 is 110%, 0.5 is half speed, etc.

    (2) have a float that will be a running 'tapehead'

    (3) start with normal code for reading in from an AudioInputStream and outputting to a SourceDataLine (good example in "Reading Sound Files" in the above Java Tutorial link.

    (4) in the area where there is the comment

    // Here, do something useful with the audio data that's 
    // now in the audioBytes array...
    

    (a) convert the incoming bytes to PCM data.

    Example of how to do this, with 16-bit encoding, stereo, little-endian ("CD quality"). This uses a read buffer size of 1024 bytes, which converts to 256 frames (remember, there are two tracks, left and right) of short data that ranges from -32767 to 32767 (or maybe 32768, I can't recall that detail at the moment).

    while((bytesRead = audioInputStream.read(rawByteBuffer, 0, 1024)) != -1)
    {
        for (int i = 0, n = bytesRead / 2); i < n; i ++)
        {
            pcmBuffer[i] =  ( rawByteBuffer[i * 2] & 0xff )
                            | ( rawByteBuffer[(i * 2) + 1)] << 8 ) ;
        }
    }   
    

    The above was edited to be clear, and can use some performance optimizations.

    (b) fetch the current "speedfactor" and write a loop that iterates through the PCM frame values (keeping in mind that with stereo, the "next" frame for that track is +2):

    tapehead += speedfactor;
    

    (c) this will usually land at a fractional value. Use linear interpolation to calculate a value at that intermediate spot. For example, if you land at tapehead = 10.25, and frame 10 is 0.5, and frame 11 is 0.6, you'd return 0.525.

    (d) convert the interpolated values back to bytes (reverse of step 4a)

    (e) accumulate the bytes and send them out via the SourceDataLine.

    I've left out some details in terms of managing the fact that the input and output bytebuffers will not match one-to-one. But if you grasp the basic concept, then I think you will be able to solve this.

    Note, this will only update the speed at the point when the "speedFactor" variable is consulted, once per input buffer. So you don't want the input buffer to be overly large.