Package org.vocvark.jAudioTools

Source Code of org.vocvark.jAudioTools.AudioSamples

/*
* @(#)AudioSamples.java  1.02  April 27, 2005.
*
* Cory McKay
* McGill Univarsity
*/

package org.vocvark.jAudioTools;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.File;
import java.io.IOException;


/**
* A class for holding audio samples and associated audio formatting information.
* Samples are stored as arrays of doubles, generally for use in analysis and
* signal processing. Samples are stored maintaining any channel segregation as
* well as mixed down to a single channel. Values can vary between -1 and +1.
* <p/>
* <p>Each object of this class is assigned a string at instantiation that
* can be used externally as a reference. Although not used internally by the
* methods of this class, it will generally be best to assign a unique value
* to this string, such as a filename.
* <p/>
* <p>Includes constructors for generating and storing the samples from
* AudioInputStreams, audio files or existing arrays of sample values.
* <p/>
* <p>Includes methods for accessing samples as a whole or as segments, and also
* for returning the samples after having been broken into equally sized windows
* of specified sizes. A method is also present for saving the samples to a File.
* Methods are also available for checking if any sample values fall out of the
* allowable ranges (i.e. between -1 and +1) and for normalizing samples so that
* the maximum amplitude will have an absolute value of 1. Methods are also
* available for getting copies of objects of this class as well as for getting
* formatted information about them. The samples can also be changed externally.
*
* @author Cory McKay
*/
public class AudioSamples {
    /* FIELDS ******************************************************************/


    /**
     * A unique identifier that external objects can use to identify each individual
     * object of this class. Not used internally by objects of this class.
     */
    protected String unique_ID;


    /**
     * Audio samles, with a minimum value of -1 and a maximum value of +1. If
     * audio is multi-channel, all channels are mixed down into this one channel.
     */
    protected double[] samples;


    /**
     * Audio samles, with a minimum value of -1 and a maximum value of +1. Is
     * set to null if only one channel of audio is present. First indice
     * corresponds to channel and second indice corresponds to sample number.
     */
    protected double[][] channel_samples;


    /**
     * The AudioFormat used to encode the samples field. Will always involve
     * big-endian signed linear PCM encoding and a bit depth of eith 8 or 16 bits.
     */
    protected AudioFormat audio_format;


    /* CONSTRUCTORS ************************************************************/


    /**
     * Store the given audio file as samples and the corresponding
     * AudioFormat.
     * <p/>
     * <p><b>IMPORTANT:</b> Note that, regardless of the AudioFormat in the given file,
     * it will be converted and stored using big-endian signed linear PCM encoding.
     * Sampling rate and number of channels is maintained, but bit depth will be
     * changed to 16 bits if it is not either 8 or 16 bits.
     *
     * @param    audio_file                A reference to an audio file from which to extract
     * and store samples as double values.
     * @param    unique_identifier        The string that will be used by external
     * objects to uniquely identify the instantiated
     * AudioSamples object.
     * @param    normalize_if_clipped    If set to true, then normalizes audio so the
     * absolute value of the highest amplitude sample is
     * 1. Does this if and only if one or more of the samples
     * is outside the allowable range of sample values
     * (-1 to 1). If set to false, then does not
     * normalize, regardless of sample values.
     * @throws Exception                Throws an informative exception if the samples
     * cannot be extracted from the file.
     */
    public AudioSamples(File audio_file,
                        String unique_identifier,
                        boolean normalize_if_clipped)
            throws Exception {
        if (!audio_file.exists())
            throw new Exception("File " + audio_file.getName() + " does not exist.");
        if (audio_file.isDirectory())
            throw new Exception("File " + audio_file.getName() + " is a directory.");

        AudioInputStream audio_input_stream = null;

        try {
            audio_input_stream = AudioSystem.getAudioInputStream(audio_file);
        } catch (UnsupportedAudioFileException ex) {
            throw new Exception("File " + audio_file.getName() + " has an unsupported audio format.");
        } catch (IOException ex) {
            throw new Exception("File " + audio_file.getName() + " is not readable.");
        }

        AudioInputStream converted_audio = org.vocvark.jAudioTools.AudioMethods.getConvertedAudioStream(audio_input_stream);

        channel_samples = org.vocvark.jAudioTools.AudioMethods.extractSampleValues(converted_audio);

        samples = DSPMethods.getSamplesMixedDownIntoOneChannel(channel_samples);

        if (channel_samples.length == 1)
            channel_samples = null;

        audio_format = converted_audio.getFormat();

        unique_ID = unique_identifier;

        if (normalize_if_clipped)
            normalizeIfClipped();

        converted_audio.close();
    }


    /**
     * Store the given AudioInputStream as samples and the corresponding
     * AudioFormat.
     * <p/>
     * <p><b>IMPORTANT:</b> Note that the AudioFormat in the AudioInputStream will
     * be converted and stored as big-endian signed linear PCM encoding with. Sampling
     * rate and number of channels is maintained, but bit depth will be changed
     * to 16 bits if it is not either 8 or 16 bits.
     *
     * @param    audio_input_stream        An AudioInputStream from which to extract
     * and store samples as double values.
     * @param    unique_identifier        The string that will be used by external
     * objects to uniquely identify the instantiated
     * AudioSamples object.
     * @param    normalize_if_clipped    If set to true, then normalizes audio so the
     * absolute value of the highest amplitude sample is
     * 1. Does this if and only if one or more of the samples
     * is outside the allowable range of sample values
     * (-1 to 1). If set to false, then does not
     * normalize, regardless of sample values.
     * @throws Exception                Throws an informative exception if the samples
     * cannot be extracted from the AudioInputStream.
     */
    public AudioSamples(AudioInputStream audio_input_stream,
                        String unique_identifier,
                        boolean normalize_if_clipped)
            throws Exception {
        if (audio_input_stream == null)
            throw new Exception("Given AudioInputStream is empty.");

        unique_ID = unique_identifier;

        AudioInputStream converted_audio = org.vocvark.jAudioTools.AudioMethods.getConvertedAudioStream(audio_input_stream);

        channel_samples = org.vocvark.jAudioTools.AudioMethods.extractSampleValues(converted_audio);

        samples = DSPMethods.getSamplesMixedDownIntoOneChannel(channel_samples);

        if (channel_samples.length == 1)
            channel_samples = null;

        audio_format = converted_audio.getFormat();

        if (normalize_if_clipped)
            normalizeIfClipped();

        converted_audio.close();
    }


    /**
     * Store the given samples with the associated AudioFormat.
     * <p/>
     * <p><b>IMPORTANT:</b> Note that, regardless of the given AudioFormat, it will be
     * converted and samples will be stored as big-endian signed linear PCM encoding.
     * Sampling rate and number of channels is maintained, but bit depth will be changed
     * to 16 bits if it is not either 8 or 16 bits.
     *
     * @param    audio_samples            Audio samles to store, with a minimum value of
     * -1 and a maximum value of +1. The first indice
     * corresponds to the channel and the second indice
     * corresponds to the sample number.
     * @param    audio_format            The AudioFormat to use for interpereting the
     * given samples.
     * @param    unique_identifier        The string that will be used by external
     * objects to uniquely identify the instantiated
     * AudioSamples object.
     * @param    normalize_if_clipped    If set to true, then normalizes audio so the
     * absolute value of the highest amplitude sample is
     * 1. Does this if and only if one or more of the samples
     * is outside the allowable range of sample values
     * (-1 to 1). If set to false, then does not
     * normalize, regardless of sample values.
     * @throws Exception                Throws an informative exception if the samples
     * cannot be extracted from the audio_samples.
     */
    public AudioSamples(double[][] audio_samples,
                        AudioFormat audio_format,
                        String unique_identifier,
                        boolean normalize_if_clipped)
            throws Exception {
        if (audio_samples == null)
            throw new Exception("Given audio samples array is empty.");
        for (int chan = 0; chan < audio_samples.length; chan++)
            if (audio_samples[chan] == null)
                throw new Exception("One or more channels of given audio samples array is empty.");
        int number_samples = audio_samples[0].length;
        for (int chan = 0; chan < audio_samples.length; chan++)
            if (audio_samples[chan].length != number_samples)
                throw new Exception("Different channels of given audio samples array have a\n" +
                        "different number of samples.");
        if (audio_format == null)
            throw new Exception("Null audio format specified for samples.");
        if (audio_format.getChannels() != audio_samples.length)
            throw new Exception("The specified samples have " + audio_samples.length + " channels but\n" +
                    "the specified audio format has " + audio_format.getChannels() + " channels.\n" +
                    "These must be the same.");

        unique_ID = unique_identifier;

        samples = DSPMethods.getSamplesMixedDownIntoOneChannel(audio_samples);

        if (audio_samples.length == 1)
            channel_samples = null;
        else
            channel_samples = DSPMethods.getCopyOfSamples(audio_samples);

        this.audio_format = org.vocvark.jAudioTools.AudioMethods.getConvertedAudioFormat(audio_format);

        if (normalize_if_clipped)
            normalizeIfClipped();
    }


    /**
     * Store the given samples with the associated sampling rate.
     * A default AudioFormat is generated.
     *
     * @param    audio_samples            Audio samles to store, with a minimum value of
     * -1 and a maximum value of +1. The first indice
     * corresponds to the channel and the second indice
     * corresponds to the sample number.
     * @param    sampling_rate            The sampling rate to associate with the given
     * samples.
     * @param    unique_identifier        The string that will be used by external
     * objects to uniquely identify the instantiated
     * AudioSamples object.
     * @param    normalize_if_clipped    If set to true, then normalizes audio so the
     * absolute value of the highest amplitude sample is
     * 1. Does this if and only if one or more of the samples
     * is outside the allowable range of sample values
     * (-1 to 1). If set to false, then does not
     * normalize, regardless of sample values.
     * @throws Exception                Throws an informative exception if the samples
     * cannot be extracted from the audio_samples.
     */
    public AudioSamples(double[][] audio_samples,
                        float sampling_rate,
                        String unique_identifier,
                        boolean normalize_if_clipped)
            throws Exception {
        if (audio_samples == null)
            throw new Exception("Given audio samples array is empty.");
        for (int chan = 0; chan < audio_samples.length; chan++)
            if (audio_samples[chan] == null)
                throw new Exception("One or more channels of given audio samples array is empty.");
        int number_samples = audio_samples[0].length;
        for (int chan = 0; chan < audio_samples.length; chan++)
            if (audio_samples[chan].length != number_samples)
                throw new Exception("Different channels of given audio samples array have a\n" +
                        "different number of samples.");

        unique_ID = unique_identifier;

        samples = DSPMethods.getSamplesMixedDownIntoOneChannel(audio_samples);

        if (audio_samples.length == 1)
            channel_samples = null;
        else
            channel_samples = DSPMethods.getCopyOfSamples(audio_samples);

        audio_format = getDefaultAudioFormat(sampling_rate);

        if (normalize_if_clipped)
            normalizeIfClipped();
    }


    /* PUBLIC METHODS ***********************************************************/


    /**
     * Returns a copy of this AudioSamples object. All fields are copies, not
     * references to the original fields, so no changes made to the copies
     * will change the original
     *
     * @return A copy of this object.
     * @throws Exception    Throws an informative exception if the copy cannot
     * be made.
     */
    public AudioSamples getCopyOfAudioSamples()
            throws Exception {
        String new_unique_ID = null;
        if (new_unique_ID != null)
            new_unique_ID = new String(unique_ID);

        double[][] new_channel_samples = null;
        if (channel_samples != null) {
            new_channel_samples = new double[channel_samples.length][];
            for (int i = 0; i < new_channel_samples.length; i++) {
                new_channel_samples[i] = new double[channel_samples[i].length];
                for (int j = 0; j < new_channel_samples[i].length; j++)
                    new_channel_samples[i][j] = channel_samples[i][j];
            }
        } else {
            new_channel_samples = new double[1][samples.length];
            for (int i = 0; i < samples.length; i++)
                new_channel_samples[0][i] = samples[i];
        }

        AudioFormat new_audio_format = null;
        if (audio_format != null) {
            new_audio_format = new AudioFormat(audio_format.getEncoding(),
                    audio_format.getSampleRate(),
                    audio_format.getSampleSizeInBits(),
                    audio_format.getChannels(),
                    audio_format.getFrameSize(),
                    audio_format.getFrameRate(),
                    audio_format.isBigEndian());
        }

        return new AudioSamples(new_channel_samples, new_audio_format, new_unique_ID, false);
    }


    /**
     * Returns a formatted description of the AudioFormat of the samples
     * as well as the number of samples per channel, the duration in
     * seconds of the recording and the maximum sample amplitude.
     *
     * @return A formatted description of the recording.
     */
    public String getRecordingInfo() {
        String return_string = org.vocvark.jAudioTools.AudioMethods.getAudioFormatData(audio_format);

        String number_samples = getNumberSamplesPerChannel() + " samples\n";
        String duration = getDuration() + " seconds\n";
        String max_sample_value = getMaximumAmplitude() + "\n";

        return_string += new String("SAMPLES PER CHANNEL: " + number_samples);
        return_string += new String("DURATION: " + duration);
        return_string += new String("MAX SIGNAL AMPLITUDE: " + max_sample_value);
        return return_string;
    }


    /**
     * Returns the identifier assigned to an object of this class at instantiation.
     *
     * @return The identifier of this object.
     */
    public String getUniqueIdentifier() {
        return unique_ID;
    }


    /**
     * Returns the AudioFormat associated with the stored samples.
     *
     * @return The AudioFormat associated with the stored samples.
     */
    public AudioFormat getAudioFormat() {
        return audio_format;
    }


    /**
     * Returns the sampling rate that is associated with the stored samples.
     *
     * @return The sampling rate associated with the stored samples in the
     * form of a float.
     */
    public float getSamplingRate() {
        return audio_format.getSampleRate();
    }


    /**
     * Returns the sampling rate that is associated with the stored samples.
     *
     * @return The sampling rate associated with the stored samples in the
     * form of a double.
     */
    public double getSamplingRateAsDouble() {
        return (new Float(audio_format.getSampleRate())).doubleValue();
    }


    /**
     * Returns the total number of samples per channel in the stored audio.
     *
     * @return The total number of samples per channel in the stored audio.
     */
    public int getNumberSamplesPerChannel() {
        return samples.length;
    }


    /**
     * Returns the total length of the stored audio in seconds.
     *
     * @return The length of the stored audio in seconds.
     */
    public double getDuration() {
        return convertSampleIndexToTime(samples.length - 1);
    }


    /**
     * Returns the number of channels of stored audio.
     *
     * @return The number of channels of stored audio.
     */
    public int getNumberChannels() {
        if (channel_samples == null)
            return 1;
        else
            return channel_samples.length;
    }


    /**
     * Returns the stored audio samples. If the audio data originally consisted
     * of multiple channels, then the returned samples represent the audio
     * after mixing down into a single channel.
     *
     * @return The audio samples stored in this object. These have a minimum
     * value of -1 and a maximum value of +1.
     */
    public double[] getSamplesMixedDown() {
        return samples;
    }


    /**
     * Returns the stored audio samples between the given sample indices.
     * If the audio data originally consisted of multiple channels, then
     * the returned samples represent the audio after mixing down into a
     * single channel.
     *
     * @param    start_sample    The sample indice of the first sample to return.
     * Must be 0 or higher and must be less than
     * end_sample.
     * @param    end_sample        The sample indice of the last sample to return.
     * Must be less than the total number of samples.
     * @return The audio samples stored in this object. These
     * have a minimum  value of -1 and a maximum value
     * of +1. Only the samples within (and including)
     * the given sample indices are returned. Only one
     * (potentially combined) channel of audio is
     * returned.
     * @throws Exception        Throws an informative exception if the given
     * start sample or end sample are outside acceptable
     * ranges.
     */
    public double[] getSamplesMixedDown(int start_sample, int end_sample)
            throws Exception {
        if (start_sample < 0)
            throw new Exception("Requested audio starting at sample " + start_sample +
                    "\nStart sample indice must be 0 or greater.");
        if (end_sample >= samples.length)
            throw new Exception("Requested audio ending at sample " + end_sample +
                    "\nA total of " + samples.length + "samples are present." +
                    "\nRequested ending sample indice must be less than this.");
        if (start_sample >= end_sample)
            throw new Exception("Requested audio starting at sample " + start_sample +
                    " and ending at sample " + end_sample + ".\n" +
                    "Requested start sample indice must be less than requested" +
                    "\nend sample indice.");

        int number_samples = end_sample - start_sample;
        double[] sample_segment = new double[number_samples];
        for (int samp = start_sample; samp <= end_sample; samp++)
            sample_segment[samp - start_sample] = samples[samp];
        return sample_segment;
    }


    /**
     * Returns the stored audio samples between the given times.
     * If the audio data originally consisted of multiple channels, then
     * the returned samples represent the audio after mixing down into a
     * single channel.
     *
     * @param    start_time        The time, in seconds, of the first sample to
     * return. Must be 0 or higher and must be less than
     * end_time.
     * @param    end_time        The time, in seconds, of the last sample to return.
     * return. Must be less than or equal to the total
     * duration.
     * @return The audio samples stored in this object. These
     * have a minimum  value of -1 and a maximum value
     * of +1. Only the samples within (and including)
     * the given times are returned. Only one (potentially
     * combined) channel of audio is returned.
     * @throws Exception        Throws an informative exception if the given
     * times are outside acceptable ranges.
     */
    public double[] getSamplesMixedDown(double start_time, double end_time)
            throws Exception {
        int start_sample = convertTimeToSampleIndex(start_time);
        int end_sample = convertTimeToSampleIndex(end_time);
        return getSamplesMixedDown(start_sample, end_sample);
    }


    /**
     * Returns the stored audio samples divided into windows of equal lengths.
     * The last window is extended past the length of the stored samples if
     * necessary and is zero padded. If the audio data originally consisted of
     * multiple channels, then the returned samples represent the audio after
     * mixing down into a single channel.
     *
     * @param    window_size        The length in samples of the windows that the
     * samples are to divided into.
     * @return The audio samples stored in this object after
     * being broken into equally sized windows of the
     * specified size. The last window is zero-padded
     * if necessary. The samples have a minimum value
     * of -1 and a maximum value of +1. Only one
     * (potentially combined) channel of audio is
     * returned. The first indice specifies the window
     * and the second indice specifies the sample index
     * within the window.
     * @throws Exception        Throws an exception if a negative or 0 window size
     * is specified.
     */
    public double[][] getSampleWindowsMixedDown(int window_size)
            throws Exception {
        if (window_size < 1)
            throw new Exception("Window size of " + window_size + " specified.\n" +
                    "This value must be above 0.");

        int number_windows = samples.length / window_size;
        if (samples.length % window_size != 0)
            number_windows++;

        double[][] windowed_samples = new double[number_windows][window_size];
        for (int win = 0; win < number_windows; win++) {
            if (win != number_windows - 1) {
                for (int samp = 0; samp < window_size; samp++)
                    windowed_samples[win][samp] = samples[win * window_size + samp];
            } else {
                for (int samp = 0; samp < window_size; samp++) {
                    if (win * window_size + samp < samples.length)
                        windowed_samples[win][samp] = samples[win * window_size + samp];
                    else
                        windowed_samples[win][samp] = 0;
                }
            }
        }

        return windowed_samples;
    }


    /**
     * Returns the stored audio samples divided into windows of equal lengths.
     * The last window is extended past the length of the stored samples if
     * necessary and is zero padded. If the audio data originally consisted of
     * multiple channels, then the returned samples represent the audio after
     * mixing down into a single channel.
     *
     * @param    window_duration    The duration in seconds of the windows that the
     * samples are divided into.
     * @return The audio samples stored in this object after
     * being broken into equally sized windows of the
     * specified size. The last window is zero-padded
     * if necessary. The samples have a minimum value
     * of -1 and a maximum value of +1. Only one
     * (potentially combined) channel of audio is
     * returned. The first indice specifies the window
     * and the second indice specifies the sample index
     * within the window.
     * @throws Exception        Throws an exception if a negative or 0 window size
     * is specified.
     */
    public double[][] getSampleWindowsMixedDown(double window_duration)
            throws Exception {
        int window_size = convertTimeToSampleIndex(window_duration);
        return getSampleWindowsMixedDown(window_size);
    }


    /**
     * Returns the stored audio samples in the form of an AudioInputStream.
     * If the audio data originally consisted of multiple channels, then the
     * returned samples represent the audio after mixing down into a single
     * channel.
     *
     * @return The entire set of samples mixed down into one channel.
     * Returned in the form of an AudioInputStream.
     * @throws Exception        Throws an exception if a a problem occurs
     * during conversion.
     */
    public AudioInputStream getAudioInputStreamMixedDown()
            throws Exception {
        // Specify that only one channel is used
        AudioFormat mixed_down_audio_format = new AudioFormat(audio_format.getSampleRate(),
                audio_format.getSampleSizeInBits(),
                1,
                true,
                audio_format.isBigEndian());

        // Convert samples to 2-D array
        double[][] samples_to_convert = new double[1][];
        samples_to_convert[0] = samples;

        // Convert to an AudioInputStream
        AudioInputStream audio_input_stream
                = org.vocvark.jAudioTools.AudioMethods.convertToAudioInputStream(samples_to_convert, mixed_down_audio_format);

        // Return the resuls
        return audio_input_stream;
    }


    /**
     * Returns the stored audio samples between the given samples in the form of an
     * AudioInputStream. If the audio data originally consisted of multiple channels,
     * then the returned samples represent the audio after mixing down into a single
     * channel.
     *
     * @param    start_sample    The sample indice of the first sample to return.
     * Must be 0 or higher and must be less than
     * end_sample.
     * @param    end_sample        The sample indice of the last sample to return.
     * Must be less than the total number of samples.
     * @return The specified set of samples mixed down into one channel.
     * Returned in the form of an AudioInputStream.
     * @throws Exception        Throws an informative exception if the given
     * start sample or end sample are outside acceptable
     * ranges or if error occurs during conversion.
     */
    public AudioInputStream getAudioInputStreamMixedDown(int start_sample, int end_sample)
            throws Exception {
        // Specify that only one channel is used
        AudioFormat mixed_down_audio_format = new AudioFormat(audio_format.getSampleRate(),
                audio_format.getSampleSizeInBits(),
                1,
                true,
                audio_format.isBigEndian());

        // Find the particular samples to convert
        double[] sample_portion = getSamplesMixedDown(start_sample, end_sample);

        // Convert the samples to 2-D array
        double[][] samples_to_convert = new double[1][];
        samples_to_convert[0] = sample_portion;

        // Convert to an AudioInputStream
        AudioInputStream audio_input_stream
                = org.vocvark.jAudioTools.AudioMethods.convertToAudioInputStream(samples_to_convert, mixed_down_audio_format);

        // Return the resuls
        return audio_input_stream;
    }


    /**
     * Returns the stored audio samples  between the given times in the form of an
     * AudioInputStream. If the audio data originally consisted of multiple channels,
     * then the returned samples represent the audio after mixing down into a single
     * channel.
     *
     * @param    start_time        The time, in seconds, of the first sample to
     * return. Must be 0 or higher and must be less than
     * end_time.
     * @param    end_time        The time, in seconds, of the last sample to return.
     * return. Must be less than or equal to the total
     * duration.
     * @return The specified set of samples mixed down into one channel.
     * Returned in the form of an AudioInputStream.
     * @throws Exception        Throws an informative exception if the given
     * start time or end time are outside acceptable
     * ranges or if error occurs during conversion.
     */
    public AudioInputStream getAudioInputStreamMixedDown(double start_time, double end_time)
            throws Exception {
        // Specify that only one channel is used
        AudioFormat mixed_down_audio_format = new AudioFormat(audio_format.getSampleRate(),
                audio_format.getSampleSizeInBits(),
                1,
                true,
                audio_format.isBigEndian());

        // Find the particular samples to convert
        double[] sample_portion = getSamplesMixedDown(start_time, end_time);

        // Convert the samples to 2-D array
        double[][] samples_to_convert = new double[1][];
        samples_to_convert[0] = sample_portion;

        // Convert to an AudioInputStream
        AudioInputStream audio_input_stream
                = org.vocvark.jAudioTools.AudioMethods.convertToAudioInputStream(samples_to_convert, mixed_down_audio_format);

        // Return the resuls
        return audio_input_stream;
    }


    /**
     * Returns the stored audio samples.
     *
     * @return The audio samples stored in this object. These have a minimum
     * value of -1 and a maximum value of +1. The first indice corresponds
     * to the channel and the second indice corresponds to the sample
     * number.
     */
    public double[][] getSamplesChannelSegregated() {
        if (channel_samples == null) {
            double[][] formatted_samples = new double[1][];
            formatted_samples[0] = samples;
            return formatted_samples;
        } else
            return channel_samples;
    }


    /**
     * Returns the stored audio samples between the given sample indices.
     *
     * @param    start_sample    The sample indice of the first sample to return.
     * Must be 0 or higher and must be less than
     * end_sample.
     * @param    end_sample        The sample indice of the last sample to return.
     * Must be less than the total number of samples.
     * @return The audio samples stored in this object. These
     * have a minimum  value of -1 and a maximum value
     * of +1. The first indice corresponds to the channel
     * and the second indice corresponds to the sample
     * number.
     * @throws Exception        Throws an informative exception if the given
     * start sample or end sample are outside acceptable
     * ranges.
     */
    public double[][] getSamplesChannelSegregated(int start_sample, int end_sample)
            throws Exception {
        if (start_sample < 0)
            throw new Exception("Requested audio starting at sample " + start_sample +
                    "\nStart sample indice must be 0 or greater.");
        if (end_sample >= samples.length)
            throw new Exception("Requested audio ending at sample " + end_sample +
                    "\nA total of " + samples.length + "samples are present." +
                    "\nRequested ending sample indice must be less than this.");
        if (start_sample >= end_sample)
            throw new Exception("Requested audio starting at sample " + start_sample +
                    " and ending at sample " + end_sample + ".\n" +
                    "Requested start sample indice must be less than requested" +
                    "\nend sample indice.");

        int number_samples = end_sample - start_sample + 1;

        // Case where only one channel of audio is present
        if (channel_samples == null) {
            double[][] sample_segment = new double[1][number_samples];
            for (int samp = start_sample; samp <= end_sample; samp++)
                sample_segment[0][samp - start_sample] = samples[samp];
            return sample_segment;
        }

        // Case where multiple channels of audio are present
        else {
            double[][] sample_segment = new double[channel_samples.length][number_samples];
            for (int chan = 0; chan < channel_samples.length; chan++)
                for (int samp = start_sample; samp <= end_sample; samp++)
                    sample_segment[chan][samp - start_sample] = channel_samples[chan][samp];
            return sample_segment;
        }
    }


    /**
     * Returns the stored audio samples between the given times in secondes.
     *
     * @param    start_time        The time, in seconds, of the first sample to
     * return. Must be 0 or higher and must be less than
     * end_time.
     * @param    end_time        The time, in seconds, of the last sample to return.
     * return. Must be less than or equal to the total
     * duration.
     * @return The audio samples stored in this object. These
     * have a minimum  value of -1 and a maximum value
     * of +1. The first indice corresponds to the channel
     * and the second indice corresponds to the sample
     * number. Only the samples within (and including)
     * the given times are returned.
     * @throws Exception        Throws an informative exception if the given
     * times are outside acceptable ranges.
     */
    public double[][] getSamplesChannelSegregated(double start_time, double end_time)
            throws Exception {
        int start_sample = convertTimeToSampleIndex(start_time);
        int end_sample = convertTimeToSampleIndex(end_time);
        return getSamplesChannelSegregated(start_sample, end_sample);
    }


    /**
     * Returns the stored audio samples divided into windows of equal lengths.
     * The last window is extended past the length of the stored samples if
     * necessary and is zero padded.
     *
     * @param    window_size        The length in samples of the windows that the
     * samples are to divided into.
     * @return The audio samples stored in this object after
     * being broken into equally sized windows of the
     * specified size. The last window is zero-padded
     * if necessary. The samples have a minimum value
     * of -1 and a maximum value of +1. The first indice
     * specifies the channel, the second indice specifies
     * the window and the third indice specifies the
     * sample index within the window.
     * @throws Exception        Throws an exception if a negative or 0 window size
     * is specified.
     */
    public double[][][] getSampleWindowsChannelSegregated(int window_size)
            throws Exception {
        if (channel_samples == null) {
            double[][][] windowed_samples = new double[1][][];
            windowed_samples[0] = getSampleWindowsMixedDown(window_size);
            return windowed_samples;
        }

        if (window_size < 1)
            throw new Exception("Window size of " + window_size + " specified.\n" +
                    "This value must be above 0.");

        int number_windows = samples.length / window_size;
        if (samples.length % window_size != 0)
            number_windows++;

        double[][][] windowed_samples = new double[channel_samples.length][number_windows][window_size];
        for (int chan = 0; chan < channel_samples.length; chan++)
            for (int win = 0; win < number_windows; win++) {
                if (win != number_windows - 1) {
                    for (int samp = 0; samp < window_size; samp++)
                        windowed_samples[chan][win][samp] = channel_samples[chan][win * window_size + samp];
                } else {
                    for (int samp = 0; samp < window_size; samp++) {
                        if (win * window_size + samp < samples.length)
                            windowed_samples[chan][win][samp] = channel_samples[chan][win * window_size + samp];
                        else
                            windowed_samples[chan][win][samp] = 0;
                    }
                }
            }

        return windowed_samples;
    }


    /**
     * Returns the stored audio samples divided into windows of equal lengths.
     * The last window is extended past the length of the stored samples if
     * necessary and is zero padded.
     *
     * @param    window_duration    The duration in seconds of the windows that the
     * samples are divided into.
     * @return The audio samples stored in this object after
     * being broken into equally sized windows of the
     * specified size. The last window is zero-padded
     * if necessary. The samples have a minimum value
     * of -1 and a maximum value of +1. The first indice
     * specifies the channel, the second indice specifies
     * the window and the third indice specifies the
     * sample index within the window.
     * @throws Exception        Throws an exception if a negative or 0 window size
     * is specified.
     */
    public double[][][] getSampleWindowsChannelSegregated(double window_duration)
            throws Exception {
        int window_size = convertTimeToSampleIndex(window_duration);
        return getSampleWindowsChannelSegregated(window_size);
    }


    /**
     * Returns the stored audio samples in the form of an AudioInputStream.
     *
     * @return The entire set of samples returned in the
     * form of an AudioInputStream.
     * @throws Exception        Throws an exception if a a problem occurs
     * during conversion.
     */
    public AudioInputStream getAudioInputStreamChannelSegregated()
            throws Exception {
        // Extract samples from samples field if only one channel present
        double[][] samples_to_convert = getSamplesChannelSegregated();

        // Convert to an AudioInputStream
        AudioInputStream audio_input_stream
                = org.vocvark.jAudioTools.AudioMethods.convertToAudioInputStream(samples_to_convert, audio_format);

        // Return the resuls
        return audio_input_stream;
    }


    /**
     * Returns the stored audio samples between the given samples in the form of
     * an AudioInputStream.
     *
     * @param    start_sample    The sample indice of the first sample to return.
     * Must be 0 or higher and must be less than
     * end_sample.
     * @param    end_sample        The sample indice of the last sample to return.
     * Must be less than the total number of samples.
     * @return The specified set of samples returned in the form of an
     * of an AudioInputStream.
     * @throws Exception        Throws an informative exception if the given
     * start sample or end sample are outside acceptable
     * ranges or if error occurs during conversion.
     */
    public AudioInputStream getAudioInputStreamChannelSegregated(int start_sample, int end_sample)
            throws Exception {
        // Extract samples from samples field if only one channel present
        double[][] samples_to_convert = getSamplesChannelSegregated(start_sample, end_sample);

        // Convert to an AudioInputStream
        AudioInputStream audio_input_stream
                = org.vocvark.jAudioTools.AudioMethods.convertToAudioInputStream(samples_to_convert, audio_format);

        // Return the resuls
        return audio_input_stream;
    }


    /**
     * Returns the stored audio samples between the given times in the form of
     * an AudioInputStream.
     *
     * @param    start_time        The time, in seconds, of the first sample to
     * return. Must be 0 or higher and must be less than
     * end_time.
     * @param    end_time        The time, in seconds, of the last sample to return.
     * return. Must be less than or equal to the total
     * duration.
     * @return The specified set of samples returned in the form of an
     * of an AudioInputStream.
     * @throws Exception        Throws an informative exception if the given
     * start time or end time are outside acceptable
     * ranges or if error occurs during conversion.
     */
    public AudioInputStream getAudioInputStreamChannelSegregated(double start_time, double end_time)
            throws Exception {
        // Extract samples from samples field if only one channel present
        double[][] samples_to_convert = getSamplesChannelSegregated(start_time, end_time);

        // Convert to an AudioInputStream
        AudioInputStream audio_input_stream
                = org.vocvark.jAudioTools.AudioMethods.convertToAudioInputStream(samples_to_convert, audio_format);

        // Return the resuls
        return audio_input_stream;
    }


    /**
     * Saves the currently stored samples to the specified file.
     * <p/>
     * <p><b>WARNING:</b> Will automatically overwrite given file if it already exists.
     *
     * @param    save_file                The File to save the audio samples to.
     * @param    multi_channel            If this is true, then any separate channels are
     * saved on separate channels. If this is false,
     * then saved file has only one channel, onto which
     * all samples have been mixed down to.
     * @param    save_file_type            The AudioFileFormat.Type to use for saving the audio.
     * A default value is used if this is null.
     * @param    normalize_if_clipped    If set to true, then normalizes audio so the
     * absolute value of the highest amplitude sample is 1.
     * Does this if and only if one or more of the samples to
     * save is outside the allowable range of sample values
     * (-1 to 1). If set to false, then does not
     * normalize, regardless of sample values. <b>WARNING:</b>
     * This will normalize the samples stored in memory as well.
     * @throws Exception                Throws an informative exception if the samples cannot
     * be succesfully saved to the file.
     */
    public void saveAudio(File save_file,
                          boolean multi_channel,
                          AudioFileFormat.Type save_file_type,
                          boolean normalize_if_clipped)
            throws Exception {
        // Verify that a file is specified
        if (save_file == null)
            throw new Exception("No file provided to save to.");

        // Normalize the file if requested and if necessary
        if (normalize_if_clipped)
            normalizeIfClipped();

        // Convert samples to an AudioInputStream
        AudioInputStream audio_input_stream = null;
        if (multi_channel)
            audio_input_stream = getAudioInputStreamChannelSegregated();
        else
            audio_input_stream = getAudioInputStreamMixedDown();

        // Save as a wav file if file type not specified
        if (save_file_type == null)
            save_file_type = AudioFileFormat.Type.WAVE;

        // Delete any pre-existing file
        if (save_file.exists())
            save_file.delete();

        // Write the samples to the file
        AudioSystem.write(audio_input_stream, save_file_type, save_file);
    }


    /**
     * Checks the mixed down and channel segregated samples separately to see if
     * either one has values outside of the permitted range (-1 to +1). If so,
     * normalizes the appropriate samples so that the absolute value of the
     * highest amplitude sample is 1.
     */
    public void normalizeIfClipped() {
        if (checkMixedDownSamplesForClipping() > 0.0)
            normalizeMixedDownSamples();
        if (checkChannelSegregatedSamplesForClipping() > 0.0)
            normalizeChannelSegretatedSamples();
    }


    /**
     * Returns the maximum amplitude value in all of the channels.
     *
     * @return The maximum amplitude (absolute) value in any one channel.
     */
    public double getMaximumAmplitude() {
        double max_amplitude = 0.0;
        if (channel_samples != null) {
            for (int chan = 0; chan < channel_samples.length; chan++)
                for (int samp = 0; samp < channel_samples[chan].length; samp++)
                    if (Math.abs(channel_samples[chan][samp]) > max_amplitude)
                        max_amplitude = Math.abs(channel_samples[chan][samp]);
        } else {
            for (int samp = 0; samp < samples.length; samp++)
                if (Math.abs(samples[samp]) > max_amplitude)
                    max_amplitude = Math.abs(samples[samp]);
        }
        return max_amplitude;
    }


    /**
     * Returns the maximum deviation in the stored mixed down sample values outside
     * the permissible range of -1 to +1. Returns -1.0 if all samples fall withing
     * the permissible range.
     *
     * @return The maximum deviation from the permissible sample values. -1 if
     * all sample values fall within the allowable range.
     */
    public double checkMixedDownSamplesForClipping() {
        double max_difference = -1.0;
        for (int samp = 0; samp < samples.length; samp++)
            if (Math.abs(samples[samp]) > 1.0) {
                double difference = Math.abs(samples[samp]) - 1.0;
                if (difference > max_difference)
                    max_difference = difference;
            }
        return max_difference;
    }


    /**
     * Returns the maximum deviation in the stored sample values outside the
     * permissible range of -1 to +1. Returns -1.0 if all samples fall withing
     * the permissible range. Does this dependantly over all channels, but not
     * over the mixed down channels (unless only one channel is present).
     *
     * @return The maximum deviation from the permissible sample values. -1 if
     * all sample values fall within the allowable range.
     */
    public double checkChannelSegregatedSamplesForClipping() {
        double max_difference = -1.0;
        if (channel_samples != null) {
            for (int chan = 0; chan < channel_samples.length; chan++)
                for (int samp = 0; samp < channel_samples[chan].length; samp++)
                    if (Math.abs(channel_samples[chan][samp]) > 1.0) {
                        double difference = Math.abs(channel_samples[chan][samp]) - 1.0;
                        if (difference > max_difference)
                            max_difference = difference;
                    }
        } else
            max_difference = checkMixedDownSamplesForClipping();
        return max_difference;
    }


    /**
     * Normalizes the samples mixed down into one channel so that the absolute value
     * of the highest sample amplitude is 1. Does nothing if all samples are 0.
     */
    public void normalizeMixedDownSamples() {
        samples = DSPMethods.normalizeSamples(samples);
    }


    /**
     * Normalizes the channel segregated samples so that the absolute value
     * of the highest sample amplitude is 1. Does nothing if all samples are 0.
     */
    public void normalizeChannelSegretatedSamples() {
        if (channel_samples != null)
            channel_samples = DSPMethods.normalizeSamples(channel_samples);
        else
            normalizeMixedDownSamples();
    }


    /**
     * Normalizes both the channel segregated samples and the samples mixed
     * down into one channel so that the absolute value of the highest sample
     * amplitude is 1. Does nothing if all samples are 0.
     */
    public void normalize() {
        normalizeChannelSegretatedSamples();
        if (channel_samples != null)
            normalizeMixedDownSamples();
    }


    /**
     * Updates the samples stored in an object of this class. The given new
     * samples must have the same number of channels as the original data.
     * The stored samples are a copy of the given samples, so changes to
     * the passed array will not affect the information stored here.
     *
     * @param    new_samples            Audio samles to store, usually with a minimum value
     * of -1 and a maximum value of +1. The first indice
     * corresponds to the channel and the second indice
     * corresponds to the sample number.
     * @throws Exception            Throws an exception if an invalid parameter is
     * passed (null entries, non-matching number of channels
     * or samples per channel.
     */
    public void setSamples(double[][] new_samples)
            throws Exception {
        // Verify that the given samples are valid
        if (new_samples == null)
            throw new Exception("An empty set of samples provided.");
        int number_samples = -1;
        for (int chan = 0; chan < new_samples.length; chan++) {
            if (new_samples[chan] == null)
                throw new Exception("Channel " + chan + " of the given samples is empty.");
            if (number_samples != -1)
                if (number_samples != new_samples[chan].length)
                    throw new Exception("Different channels have different numbers of samples.");
            number_samples = new_samples[chan].length;
        }

        // Update the samples and channel_samples fields
        if (channel_samples == null) {
            if (new_samples.length != 1) {
                throw new Exception("Given samples have " + new_samples.length + " channels.\n" +
                        "Only one channel should be present.");
            }
            samples = new double[number_samples];
            for (int samp = 0; samp < samples.length; samp++)
                samples[samp] = new_samples[0][samp];
        } else {
            if (new_samples.length != channel_samples.length) {
                throw new Exception("Given samples have " + new_samples.length + " channels.\n" +
                        channel_samples.length + " channel should be present.");
            }
            channel_samples = new double[new_samples.length][number_samples];
            for (int chan = 0; chan < channel_samples.length; chan++)
                for (int samp = 0; samp < channel_samples[chan].length; samp++)
                    channel_samples[chan][samp] = new_samples[chan][samp];
            samples = DSPMethods.getSamplesMixedDownIntoOneChannel(channel_samples);

        }
    }


    /* PRIVATE METHODS *********************************************************/


    /**
     * Returns a new <code>AudioFormat</code> with the given sampling rate. The
     * number of channels is automatically set based on the channel_samples field.
     * <p/>
     * Big-endian signed linear PCM encoding is used by default, and the default
     * bit-depth is set to 16 bits.
     *
     * @param    sampling_rate    The sampling rate, in Hz, to use for interpereting
     * samples.
     * @return The default AudioFormat with the specified sampling
     * rate.
     */
    private AudioFormat getDefaultAudioFormat(float sampling_rate) {
        int bit_depth = 16;
        boolean signed = true;
        boolean big_endian = true;

        int channels = 1;
        if (channel_samples == null)
            channels = channel_samples.length;

        return new AudioFormat(sampling_rate, bit_depth, channels, signed, big_endian);
    }


    /**
     * Returns the given sample index converted into a time in seconds based
     * on the sampling rate of the stored audio samples. If the given sample
     * index is negative, then time 0 is returned. If the given sample index
     * is greate thean the length of the audio, then the time of the last index
     * is returned.
     *
     * @param    sample_index    The sample index to convert to a time value.
     * @return The time in seconds corresponding to the given
     * sample index, or the nearest time if the
     * given sample index is outside the duration
     * of the audio.
     */
    private double convertSampleIndexToTime(int sample_index) {
        if (sample_index < 0)
            sample_index = 0;
        else if (sample_index >= samples.length)
            sample_index = samples.length - 1;
        float time = samples.length / audio_format.getSampleRate();
        return (new Float(time)).doubleValue();
    }


    /**
     * Returns the given time in seconds converted into a sample index based
     * on the sampling rate of the stored audio samples. If the given time
     * would correspond to a negative sample, then the index of the first
     * sample is returned. If the given time would correspond to a sample
     * greater than the length of the audio, then the last index is returned.
     *
     * @param    time    The time in seconds to convert to a sample.
     * @return The sample corresponding to the given time, or the
     * nearest sample if the given time is outside the duration
     * of the audio.
     */
    private int convertTimeToSampleIndex(double time) {
        int sample_index = (int) (time * audio_format.getSampleRate());
        if (sample_index < 0)
            return 0;
        else if (sample_index >= samples.length)
            return samples.length - 1;
        return sample_index;
    }
}
TOP

Related Classes of org.vocvark.jAudioTools.AudioSamples

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.