Search code examples
javaaudiojavafxwavamplitude

How to reduce amplitudes array size in Java?


Searching the web for weeks i came to the following code for calculating the amplitudes of a given .wav file in Java . Now the problem is it doesn't scale well at all with big audio files like let's say 30 minutes , the produces array is huge.

To draw it i am using JavaFX created this repository (ps the code might be different there due to i have not committed some days now).

So :

/**                                                                                                                             
 * Get Wav Amplitudes                                                                                                           
 *                                                                                                                              
 * @param file                                                                                                                  
 * @return                                                                                                                      
 * @throws UnsupportedAudioFileException                                                                                        
 * @throws IOException                                                                                                          
 */                                                                                                                             
private int[] getWavAmplitudes(File file) throws UnsupportedAudioFileException , IOException {                                  
    System.out.println("Calculting WAV amplitudes");                                                                            
    int[] amplitudes = null;                                                                                                    

    //Get Audio input stream                                                                                                    
    try (AudioInputStream input = AudioSystem.getAudioInputStream(file)) {                                                      
        AudioFormat baseFormat = input.getFormat();                                                                             

        Encoding encoding = AudioFormat.Encoding.PCM_UNSIGNED;                                                                  
        float sampleRate = baseFormat.getSampleRate();                                                                          
        int numChannels = baseFormat.getChannels();                                                                             

        AudioFormat decodedFormat = new AudioFormat(encoding, sampleRate, 16, numChannels, numChannels * 2, sampleRate, false); 
        int available = input.available();                                                                                      
        amplitudes = new int[available];                                                                                        

        //Get the PCM Decoded Audio Input Stream                                                                                
        try (AudioInputStream pcmDecodedInput = AudioSystem.getAudioInputStream(decodedFormat, input)) {                        
            final int BUFFER_SIZE = 4096; //this is actually bytes                                                              
            System.out.println(available);                                                                                      

            //Create a buffer                                                                                                   
            byte[] buffer = new byte[BUFFER_SIZE];                                                                              

            //Read all the available data on chunks                                                                             
            int counter = 0;                                                                                                    
            while (pcmDecodedInput.readNBytes(buffer, 0, BUFFER_SIZE) > 0)                                                      
                for (int i = 0; i < buffer.length - 1; i += 2, counter += 2) {                                                  
                    if (counter == available)                                                                                   
                        break;                                                                                                  
                    amplitudes[counter] = ( ( buffer[i + 1] << 8 ) | buffer[i] & 0xff ) << 16;                                  
                    amplitudes[counter] /= 32767;                                                                               
                    amplitudes[counter] *= WAVEFORM_HEIGHT_COEFFICIENT;                                                         
                }                                                                                                               
        } catch (Exception ex) {                                                                                                
            ex.printStackTrace();                                                                                               
        }                                                                                                                       
    } catch (Exception ex) {                                                                                                    
        ex.printStackTrace();                                                                                                   
    }                                                                                                                           

    //System.out.println("Finished Calculting amplitudes");                                                                     
    return amplitudes;                                                                                                          
}   

And then i process the amplitudes like this :

/**                                                                 
 * Process the amplitudes                                           
 *                                                                  
 * @param sourcePcmData                                             
 * @return An array with amplitudes                                 
 */                                                                 
private float[] processAmplitudes(int[] sourcePcmData) {            
    System.out.println("Processing WAV amplitudes");                

    //The width of the resulting waveform panel                     
    int width = waveVisualization.width;                            
    System.out.println("P Width :" + width);                        
    float[] waveData = new float[width];                            
    int samplesPerPixel = sourcePcmData.length / width;             

    //Calculate                                                     
    float nValue;                                                   
    for (int w = 0; w < width; w++) {                               
        //if (isCancelled())                                        
        //  break;                                                  

        //For performance keep it here                              
        int c = w * samplesPerPixel;                                
        nValue = 0.0f;                                              

        //Keep going                                                
        for (int s = 0; s < samplesPerPixel; s++) {                 
            //if (isCancelled())                                    
            //  break;                                              
            nValue += ( Math.abs(sourcePcmData[c + s]) / 65536.0f );
        }                                                           

        //Set WaveData                                              
        waveData[w] = nValue / samplesPerPixel;                     
    }                                                               

    System.out.println("Finished Processing amplitudes");           
    return waveData;                                                
}     

The output is this :

enter image description here


Solution

  • Found a really good solution , though i am not sure what the final array maximum size should be , after some experiments 100.000 seems a good number.

    All the code is in this github repository .

    So the method getWavAmplitudes becomes :

    /**                                                                                                                                                
     * Get Wav Amplitudes                                                                                                                              
     *                                                                                                                                                 
     * @param file                                                                                                                                     
     * @return                                                                                                                                         
     * @throws UnsupportedAudioFileException                                                                                                           
     * @throws IOException                                                                                                                             
     */                                                                                                                                                
    private int[] getWavAmplitudes(File file) throws UnsupportedAudioFileException , IOException {                                                     
    
        //Get Audio input stream                                                                                                                       
        try (AudioInputStream input = AudioSystem.getAudioInputStream(file)) {                                                                         
            AudioFormat baseFormat = input.getFormat();                                                                                                
    
            //Encoding                                                                                                                                 
            Encoding encoding = AudioFormat.Encoding.PCM_UNSIGNED;                                                                                     
            float sampleRate = baseFormat.getSampleRate();                                                                                             
            int numChannels = baseFormat.getChannels();                                                                                                
    
            AudioFormat decodedFormat = new AudioFormat(encoding, sampleRate, 16, numChannels, numChannels * 2, sampleRate, false);                    
            int available = input.available();                                                                                                         
    
            //Get the PCM Decoded Audio Input Stream                                                                                                   
            try (AudioInputStream pcmDecodedInput = AudioSystem.getAudioInputStream(decodedFormat, input)) {                                           
                final int BUFFER_SIZE = 4096; //this is actually bytes                                                                                 
    
                //Create a buffer                                                                                                                      
                byte[] buffer = new byte[BUFFER_SIZE];                                                                                                 
    
                //Now get the average to a smaller array                                                                                               
                int maximumArrayLength = 100000;                                                                                                       
                int[] finalAmplitudes = new int[maximumArrayLength];                                                                                   
                int samplesPerPixel = available / maximumArrayLength;                                                                                  
    
                //Variables to calculate finalAmplitudes array                                                                                         
                int currentSampleCounter = 0;                                                                                                          
                int arrayCellPosition = 0;                                                                                                             
                float currentCellValue = 0.0f;                                                                                                         
    
                //Variables for the loop                                                                                                               
                int arrayCellValue = 0;                                                                                                                
    
                //Read all the available data on chunks                                                                                                
                while (pcmDecodedInput.readNBytes(buffer, 0, BUFFER_SIZE) > 0)                                                                         
                    for (int i = 0; i < buffer.length - 1; i += 2) {                                                                                   
    
                        //Calculate the value                                                                                                          
                        arrayCellValue = (int) ( ( ( ( ( buffer[i + 1] << 8 ) | buffer[i] & 0xff ) << 16 ) / 32767 ) * WAVEFORM_HEIGHT_COEFFICIENT );  
    
                        //Every time you him [currentSampleCounter=samplesPerPixel]                                                                    
                        if (currentSampleCounter != samplesPerPixel) {                                                                                 
                            ++currentSampleCounter;                                                                                                    
                            currentCellValue += Math.abs(arrayCellValue);                                                                              
                        } else {                                                                                                                       
                            //Avoid ArrayIndexOutOfBoundsException                                                                                     
                            if (arrayCellPosition != maximumArrayLength)                                                                               
                                finalAmplitudes[arrayCellPosition] = finalAmplitudes[arrayCellPosition + 1] = (int) currentCellValue / samplesPerPixel;
    
                            //Fix the variables                                                                                                        
                            currentSampleCounter = 0;                                                                                                  
                            currentCellValue = 0;                                                                                                      
                            arrayCellPosition += 2;                                                                                                    
                        }                                                                                                                              
                    }                                                                                                                                  
    
                return finalAmplitudes;                                                                                                                
            } catch (Exception ex) {                                                                                                                   
                ex.printStackTrace();                                                                                                                  
            }                                                                                                                                          
        } catch (Exception ex) {                                                                                                                       
            ex.printStackTrace();                                                                                                                      
    
        }                                                                                                                                              
    
        //You don't want this to reach here...                                                                                                         
        return new int[1];                                                                                                                             
    }  
    

    Any recommendation and improvements very welcome .