// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

package net.avadeaux.klipspringer.codec;

import java.nio.ByteOrder;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;

/** Audio format specification that extends {@link AudioFormat} with some convenience functions and
  * restrictions. The sample size (frame size divided by number of channels) must be 1 or 2 for bit
  * depth 8 or smaller, 2 for bit depth 9 to 16, 3 or 4 for bit depth 17 to 24, and 4 for bit depth
  * 25 to 32. Bit depth larger than 32 is not allowed. Attempting to construct a PCM format that
  * does not conform to these rules results in exception. The additional field for significant bits
  * per sample should normally be equal to bits per sample, but may be smaller if the bit depth has
  * been scaled up by left shifting, in which case the significant number of bits should be
  * displayed as the actual quality.
  */
public class PcmFormat extends AudioFormat {
    /** Interface for selecting a quality-compatible audio format. */
    public interface Selector {
        /** Gets a supported format that matches the parameter in quality (sample rate, bit depth,
          * and number of channels). If possible, frame size, signedness and endianness of the
          * parameter are also matched.
          *
          * @throws UnsupportedFormatException if no quality-matching format is supported.
          */
        PcmFormat selectFormat(AudioFormat format) throws UnsupportedFormatException;
    }

    /** Maximum accepted frame size. */
    public final static int MAX_FS = 8 * 4;

    /** The number of significant bytes for quick access. */
    final int byps;

    /** The number of bytes allocated per buffer, for quick access. */
    final int ss;

    /** The number of significant bits per sample. */
    final int significantBips;

    /** Constructor that specifies all fields. */
    public PcmFormat(int rate, int bips, int channels, boolean signed, int fs, ByteOrder order, int significantBips) {
        super(signed ? AudioFormat.Encoding.PCM_SIGNED : AudioFormat.Encoding.PCM_UNSIGNED, rate, bips, channels, fs, rate, order == ByteOrder.BIG_ENDIAN);
        if (fs > MAX_FS) { throw new IllegalArgumentException("Frame size too large: "+fs); }
        byps = (bips+7)/8;
        ss = fs / channels;
        this.significantBips = significantBips;

        if (bips < 1 || bips > 32 || byps != ss && !(byps == 1 && ss == 2 || byps == 3 && ss == 4 || significantBips < bips)) {
            throw new IllegalArgumentException("Inconsistent PCM format ("+bips+"/"+fs+" "+channels+" channels)"
                                               + (significantBips == bips ? "" : " ["+significantBips+"]"));
        }
    }

    /** Constructor that specifies sound quality and sets other parameters to standard values. */
    public PcmFormat(int rate, int bips, int channels) {
        this(rate, bips, channels, true, (bips+7)/8 * channels, ByteOrder.nativeOrder(), bips);
    }

    /** Same value as {@link #getSampleRate()} but cast to int. */
    public int rate() { return (int) sampleRate; }

    /** Bits per sample, alias for {@link #getSampleSizeInBits()}. */
    public int bips() { return sampleSizeInBits; }

    /** Significant bits per sample. Returns the same value as {@link #bips()} unless the sample
      * values have been scaled up from a smaller bit depth, in which case this is the original bit
      * depth.
      */
    public int significantBips() { return significantBips; }

    /** Bytes per sample, ceiling of {@link #bips()}/8. */
    public int byps() { return byps; }

    /** Desired byte-alignment for data of this format. The lowest set bit of {@link #byps()}. */
    public int alignment() { return byps & -byps; }

    /** Number of channels, alias for {@link #getChannels()}. */
    public int channels() { return channels; }

    /** Boolean alternative to {@link #getEncoding()}. */
    public boolean signed() { return encoding == AudioFormat.Encoding.PCM_SIGNED; }

    /** Number of bytes allocated per sample, fs divided by channels. */
    public int ss() { return ss; }

    /** Frame size, alias for {@link #getFrameSize()}. */
    public int fs() { return frameSize; }

    /** Is big-endian, alias for {@link #isBigEndian()}. */
    public boolean bigend() { return bigEndian; }

    /** {@link ByteOrder} alternative to {@link #bigend()}. */
    public ByteOrder order() { return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; }

    /** Checks if formats match in sample rate, bits per channel, and number of channels. */
    public boolean matchesQuality(PcmFormat that) {
        return sampleRate == that.sampleRate
            && sampleSizeInBits == that.sampleSizeInBits
            && channels == that.channels;
    }

    /** Gets a PCM format specified by standard {@link AudioFormat}, with specified sample rate in
      * case it is unspecified in the given format.
      */
    public static PcmFormat of(AudioFormat format, int rate) throws UnsupportedFormatException {
        if (format instanceof PcmFormat && format.getSampleRate() == rate) { return (PcmFormat) format; }
        AudioFormat.Encoding enc = format.getEncoding();
        if (enc != AudioFormat.Encoding.PCM_SIGNED && enc != AudioFormat.Encoding.PCM_UNSIGNED) {
            throw new UnsupportedFormatException("Non-PCM format not supported: "+enc);
        }
        if (format.getSampleRate() != AudioSystem.NOT_SPECIFIED && format.getSampleRate() != rate) {
            throw new IllegalArgumentException("Sample rate mismatch: "+format.getSampleRate()+", "+rate);
        }
        return new PcmFormat(rate,
                             format.getSampleSizeInBits(),
                             format.getChannels(),
                             enc == AudioFormat.Encoding.PCM_SIGNED,
                             format.getFrameSize(),
                             format.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN,
                             format instanceof PcmFormat ? ((PcmFormat) format).significantBips() : format.getSampleSizeInBits());
    }

    /** Gets a PCM format specified by standard {@link AudioFormat} */
    public static PcmFormat of(AudioFormat format) throws UnsupportedFormatException {
        return of(format, (int) format.getSampleRate());
    }
}

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home