// 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.io.IOException;
import java.io.Closeable;
import java.nio.ByteBuffer;
import static java.nio.ByteOrder.*;
import static net.avadeaux.klipspringer.codec.Device.BitLayout.*;
/** Intermediary for passing PCM data to a writer with matching sample rate, bit depth and number of
* channels, but whose coding may differ in signedness, endianness, or padding (see <a
* href="https://klipspringer.avadeaux.net/ten-standard-ways-of-representing-binary-numbers/">blog
* post</a> about various formats). Wraps a byte buffer to provide additional operations on it,
* without duplicating the operations that work on the wrapped buffer directly. Operates on
* <em>direct</em> byte buffers (located in external memory), to make bulk data transfer efficient
* from decoders and device readers implemented using external libraries, and from files read using
* <a href="https://docs.oracle.com/en/java/javase/16/core/java-nio.html">NIO</a>. It is less
* efficient when input comes via a {@link javax.sound.sampled.TargetDataLine}, {@link
* javax.sound.sampled.AudioInputStream}, or plain {@link java.io.InputStream}, which all operate
* on JVM-internal byte arrays.
*/
public abstract class PcmBuffer implements Closeable {
static { Library.init(); }
final ByteBuffer buf;
final PcmFormat format;
final int sign; // 1 in sign bit position only
final int flip; // sign if format unsigned, zero otherwise
final int shift; // shifted positions (zero unless MSB)
final int mask; // 1 in significant bit positions; 0 in higher if LSB, 1 otherwise
final long handle; // native struct address
private boolean closed = false;
private PcmBuffer(ByteBuffer buf, PcmFormat format, Device.BitLayout lout, int ssbits) {
this.buf = buf;
buf.order(format.order());
this.format = format;
sign = 1 << format.bips()-1;
flip = format.signed() ? 0 : sign;
shift = lout == MSB ? ssbits-format.bips() : 0;
mask = -1 >>> (lout == LSB ? 32-format.bips() : 0);
handle = buf.isDirect()
? create(buf, format.channels(), format.signed(), format.bigend(), sign, lout == LSBX ? sign : 0, shift)
: 0;
}
/** Gets the output format, used in writing to the wrapped buffer. */
public PcmFormat format() { return format; }
/** See {@link Closeable#close()}. */
public void close() {
if (!closed) {
closed = true;
if (handle != 0) { free(handle); }
}
}
// Overridden by native methods in subclasses.
abstract void put(long handle, ByteBuffer data, int dataPos, boolean dataSigned, int dataSs, boolean dataBigend, int bufPos, int samples);
/** Gets the underlying byte buffer. */
public final ByteBuffer byteBuffer() { return buf; }
/** Reads a sample as a signed integer at the current position, and advances position. */
public final int getSample() {
int p = buf.position();
buf.position(p+format.ss());
return getSample(p);
}
/** Reads a sample as a signed integer given the position of its first byte. */
public abstract int getSample(int index);
/** Writes a signed integer sample value at the current position, and advances position. */
public final void putSample(int value) {
int p = buf.position();
buf.position(p+format.ss());
putSample(p, value);
}
long sa = 0;
/** Writes a signed integer sample value given the position of its first byte. */
public abstract void putSample(int index, int value);
/** Transfers data from a byte buffer into this buffer. The formats must match in quality, but
* may differ in signedness, endianness and frame size. If the data buffer is not direct, this
* method copies its contents to a temporary direct buffer before transferring to the internal
* buffer.
*/
public final void put(ByteBuffer data, PcmFormat dataFormat) throws IOException {
if (!buf.isDirect()) { throw new UnsupportedOperationException("put unsupported for nondirect buffer"); }
if (!data.isDirect()) {
ByteTransfer.writeDirect(data, new PcmWriter() {
public boolean write(ByteBuffer data) throws IOException {
put(data, dataFormat);
return true;
}
public void close() { }
}, dataFormat.fs());
return;
}
if (data.order() != dataFormat.order()) {
throw new IllegalArgumentException("Byte order mismatch between buffer and format");
}
if (!dataFormat.matchesQuality(format)) {
throw new IllegalArgumentException("Format "+dataFormat+" not compaticle with "+format);
}
int dataPos = data.position(), bufPos = buf.position();
int n = data.remaining(), dss = dataFormat.ss();
data.position(dataPos + n);
buf.position(bufPos + n/dss*format.ss());
put(handle, data, dataPos, dataFormat.signed(), dss, dataFormat.bigend(), bufPos, n/dss);
}
/** Mixes the samples in a segment of the buffer so that all channels get the same value (the
* arithmetic mean of the original values.
*/
public final void monomix(int pos, int frames) {
if (format.channels() > 1) {
monomix(handle, pos, frames);
}
}
abstract void monomix(long handle, int pos, int frames);
/** Constructor that wraps a byte buffer as a PCM buffer for the given output format and
* layout. Sets the byte order of the byte buffer to match the format.
*/
public static PcmBuffer of(ByteBuffer buf, PcmFormat format, Device.BitLayout lout) {
if (lout == LSBX && !format.signed()) {
throw new IllegalArgumentException("Unisgned format cannot have extended sign");
}
switch (format.ss()) {
case 1:
return new PcmBuffer1(buf, format, lout);
case 2:
return new PcmBuffer2(buf, format, lout);
case 3:
return new PcmBuffer3(buf, format, lout);
case 4:
return new PcmBuffer4(buf, format, lout);
default:
throw new IllegalArgumentException("Illegal sample size in bytes: "+format.ss());
}
}
private native long create(ByteBuffer buf, int channels, boolean signed, boolean bigend, int sign, int xsign, int shift);
private native void free(long handle);
// 1 -------------------------------------------------------------------------------------------
private final static class PcmBuffer1 extends PcmBuffer {
PcmBuffer1(ByteBuffer buf, PcmFormat format, Device.BitLayout lout) { super(buf, format, lout, 8); }
public int getSample(int index) {
int v = (buf.get(index) & 0xff) >>> shift ^ flip;
return v | -(v & sign);
}
public void putSample(int index, int value) {
buf.put(index, (byte) ((value ^ flip) << shift & mask));
}
native void put(long handle, ByteBuffer data, int dataPos, boolean dataSigned, int dataSs, boolean dataBigend, int bufPos, int samples);
native void monomix(long handle, int pos, int frames);
}
// 2 -------------------------------------------------------------------------------------------
private final static class PcmBuffer2 extends PcmBuffer {
PcmBuffer2(ByteBuffer buf, PcmFormat format, Device.BitLayout lout) { super(buf, format, lout, 16); }
public int getSample(int index) {
int v = (buf.getShort(index) & 0xffff) >>> shift ^ flip;
return v | -(v & sign);
}
public void putSample(int index, int value) {
buf.putShort(index, (short) ((value ^ flip) << shift & mask));
}
native void put(long handle, ByteBuffer data, int dataPos, boolean dataSigned, int dataSs, boolean dataBigend, int bufPos, int samples);
native void monomix(long handle, int pos, int frames);
}
// 3 -------------------------------------------------------------------------------------------
private final static class PcmBuffer3 extends PcmBuffer {
final int b0, b2;
PcmBuffer3(ByteBuffer buf, PcmFormat format, Device.BitLayout lout) {
super(buf, format, lout, 24);
if (format.bigend()) {
b0 = 16;
b2 = 0;
} else {
b0 = 0;
b2 = 16;
}
}
public int getSample(int index) {
int v = ((buf.get(index) & 0xff) << b0 | (buf.get(index+1) & 0xff) << 8 | (buf.get(index+2) & 0xff) << b2) >>> shift ^ flip;
return v | -(v & sign);
}
public void putSample(int index, int value) {
int v = (value ^ flip) << shift & mask;
buf.put(index, (byte) (v >>> b0)); buf.put(index+1, (byte) (v >>> 8)); buf.put(index+2, (byte) (v >>> b2));
}
native void put(long handle, ByteBuffer data, int dataPos, boolean dataSigned, int dataSs, boolean dataBigend, int bufPos, int samples);
native void monomix(long handle, int pos, int frames);
}
// 4 -------------------------------------------------------------------------------------------
private final static class PcmBuffer4 extends PcmBuffer {
PcmBuffer4(ByteBuffer buf, PcmFormat format, Device.BitLayout lout) { super(buf, format, lout, 32); }
public int getSample(int index) {
int v = buf.getInt(index) >>> shift ^ flip;
return v | -(v & sign);
}
public void putSample(int index, int value) {
buf.putInt(index, (value ^ flip) << shift & mask);
}
native void put(long handle, ByteBuffer data, int dataPos, boolean dataSigned, int dataSs, boolean dataBigend, int bufPos, int samples);
native void monomix(long handle, int pos, int frames);
}
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home