From 7abfde3229a43668c0c417bda0cf05f180464024 Mon Sep 17 00:00:00 2001 From: dennisppaul Date: Wed, 19 Jun 2024 12:15:27 +0200 Subject: [PATCH] fixed bytes to float conversion --- lib/wellen.jar | Bin 1178415 -> 1179826 bytes src/wellen/Tone.java | 20 ++++- src/wellen/WAVConverter.java | 99 ++++++++++++--------- src/wellen/Wellen.java | 28 ++++-- src/wellen/dsp/Sampler.java | 100 +++++++++++----------- src/wellen/tests/TestMultiChannelDSP.java | 50 +++++++++++ 6 files changed, 198 insertions(+), 99 deletions(-) create mode 100644 src/wellen/tests/TestMultiChannelDSP.java diff --git a/lib/wellen.jar b/lib/wellen.jar index 26ad763433b2d28b0bc83979c135484a2723f452..dc15d09385fae49b086bbe364de997be72ff1275 100644 GIT binary patch delta 27140 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 AX#fBK delta 26121 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 diff --git a/src/wellen/Tone.java b/src/wellen/Tone.java index 5579faa..55c4d08 100644 --- a/src/wellen/Tone.java +++ b/src/wellen/Tone.java @@ -49,7 +49,7 @@ public static T create_instrument(Class instrument_cla // c = instrument_class.getDeclaredConstructor(Minim.class, int.class); // mInstrument = c.newInstance((Minim) ((ToneEngineMinim) instance()).minim(), ID); // } else { - c = instrument_class.getDeclaredConstructor(int.class); + c = instrument_class.getDeclaredConstructor(int.class); mInstrument = c.newInstance(ID); // } } catch (Exception ex) { @@ -85,6 +85,20 @@ public static float[] get_buffer() { return instance().get_buffer(); } + /** + * @return reference to left output buffer + */ + public static float[] get_output_buffer_left() { + return get_buffer_left(); + } + + /** + * @return reference to right output buffer + */ + public static float[] get_output_buffer_right() { + return get_buffer_right(); + } + public static float[] get_buffer_left() { return instance().get_buffer_left(); } @@ -251,7 +265,7 @@ public static void stop() { private static void printAlreadyStartedWarning() { System.err.println("+++ WARNING @" + Tone.class.getSimpleName() + ".start" + " / tone engine already " + - "initialized. make sure that `start` is the first call to `Ton`. " + "use " + - "`set_engine(ToneEngine)` to switch tone engines."); + "initialized. make sure that `start` is the first call to `Ton`. " + "use " + + "`set_engine(ToneEngine)` to switch tone engines."); } } diff --git a/src/wellen/WAVConverter.java b/src/wellen/WAVConverter.java index ed2a988..13512e2 100644 --- a/src/wellen/WAVConverter.java +++ b/src/wellen/WAVConverter.java @@ -12,29 +12,29 @@ public class WAVConverter { // @TODO(write header could also support `WAVE_FORMAT_PCM_32BIT_FLOAT`) // @TODO(currently fixed to little endianness) - public static boolean VERBOSE = false; - private static final String WAV_CHUNK_DATA = "data"; - private static final String WAV_CHUNK_FMT_ = "fmt "; - private static final String WAV_CHUNK_RIFF = "RIFF"; - private static final String WAV_CHUNK_WAVE = "WAVE"; - private final int mBitsPerSample; - private final int mChannels; - private final int mCompressionFormat; - private final ArrayList mData; - private final ArrayList mHeader; - private final int mSampleRate; + public static boolean VERBOSE = false; + private static final String WAV_CHUNK_DATA = "data"; + private static final String WAV_CHUNK_FMT_ = "fmt "; + private static final String WAV_CHUNK_RIFF = "RIFF"; + private static final String WAV_CHUNK_WAVE = "WAVE"; + private final int mBitsPerSample; + private final int mChannels; + private final int mCompressionFormat; + private final ArrayList mData; + private final ArrayList mHeader; + private final int mSampleRate; public WAVConverter(Info pInfo) { this(pInfo.channels, pInfo.bits_per_sample, pInfo.sample_rate, pInfo.format); } public WAVConverter(int pChannels, int pBitsPerSample, int pSampleRate, int pCompressionFormat) { - mChannels = pChannels; - mBitsPerSample = pBitsPerSample; - mSampleRate = pSampleRate; + mChannels = pChannels; + mBitsPerSample = pBitsPerSample; + mSampleRate = pSampleRate; mCompressionFormat = pCompressionFormat; - mData = new ArrayList<>(); - mHeader = new ArrayList<>(); + mData = new ArrayList<>(); + mHeader = new ArrayList<>(); } public static Info convert_bytes_to_samples(byte[] pHeader) { @@ -42,7 +42,7 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { // see http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html // see https://sites.google.com/site/musicgapi/technical-documents/wav-file-format /* RIFF Chunk */ - int mOffset = 0x00; + int mOffset = 0x00; final String mRIFFChunkName = WAVConverter.read_string(pHeader, mOffset + 0x00); if (!mRIFFChunkName.equalsIgnoreCase(WAV_CHUNK_RIFF)) { System.err.println("+++ WARNING @" + WAVConverter.class.getSimpleName() + " / expected `" + WAV_CHUNK_RIFF + "`" + " in header."); @@ -65,14 +65,14 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { System.err.println("+++ WARNING @" + WAVConverter.class.getSimpleName() + " / expected `" + WAV_CHUNK_FMT_ + "` " + "in header."); } final int mFormatChunkSize = WAVConverter.read__int32(pHeader, mOffset + 0x04); - mWAVStruct.format = WAVConverter.read__int16(pHeader, mOffset + 0x08); - mWAVStruct.channels = WAVConverter.read__int16(pHeader, mOffset + 0x0A); - mWAVStruct.sample_rate = WAVConverter.read__int32(pHeader, mOffset + 0x0C); + mWAVStruct.format = WAVConverter.read__int16(pHeader, mOffset + 0x08); + mWAVStruct.channels = WAVConverter.read__int16(pHeader, mOffset + 0x0A); + mWAVStruct.sample_rate = WAVConverter.read__int32(pHeader, mOffset + 0x0C); mWAVStruct.bits_per_sample = WAVConverter.read__int16(pHeader, mOffset + 0x16); if (VERBOSE) { System.out.println("+++ CHUNK: " + mFormatChunkName); System.out.println(" chunk size : " + mFormatChunkSize); - System.out.println(" format code: " + mWAVStruct.format); + System.out.println(" format code: " + mWAVStruct.format + " (" + getFormatString(mWAVStruct.format) + ")"); System.out.println(" channels : " + mWAVStruct.channels); System.out.println(" sample rate: " + mWAVStruct.sample_rate); System.out.println(" byte/sec : " + WAVConverter.read__int32(pHeader, mOffset + 0x10)); @@ -81,7 +81,7 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { } if (mWAVStruct.format != Wellen.WAV_FORMAT_PCM && mWAVStruct.format != Wellen.WAV_FORMAT_IEEE_FLOAT_32BIT) { System.err.println("+++ WARNING @" + WAVConverter.class.getSimpleName() + " / format not " + "supported. " - + "currently only `WAV_FORMAT_PCM` + `WAV_FORMAT_IEEE_FLOAT_32BIT` " + "works" + "." + " " + "(" + mWAVStruct.format + ")"); + + "currently only `WAV_FORMAT_PCM` + `WAV_FORMAT_IEEE_FLOAT_32BIT` " + "works" + "." + " " + "(" + mWAVStruct.format + ")"); } /* data chunk */ @@ -89,7 +89,7 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { if (WAVConverter.read_string(pHeader, mOffset + 0x00).equalsIgnoreCase("fact")) { // @TODO(hack! skipping `fact` chunk … handle this a bit more elegantly) final int mFactChunkSize = WAVConverter.read__int32(pHeader, mOffset + 0x04); - final int mPeakOffset = WAVConverter.read__int32(pHeader, mOffset + 0x10); + final int mPeakOffset = WAVConverter.read__int32(pHeader, mOffset + 0x10); if (VERBOSE) { System.out.println("+++ skipping `fact` chunk"); System.out.println("+++ CHUNK: " + WAVConverter.read_string(pHeader, mOffset + 0x00)); @@ -105,9 +105,9 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { if (!mDataChunkName.equalsIgnoreCase(WAV_CHUNK_DATA)) { System.err.println("+++ WARNING @" + WAVConverter.class.getSimpleName() + " / expected `" + WAV_CHUNK_DATA + "`" + " in header."); } - final int mDataChunkSize = WAVConverter.read__int32(pHeader, mOffset + 0x04); - byte[] mInterlacedByteBuffer = WAVConverter.read__bytes(pHeader, mOffset + 0x08, mDataChunkSize); - int mDataSize = mInterlacedByteBuffer.length / mWAVStruct.channels / (mWAVStruct.bits_per_sample / 8); + final int mDataChunkSize = WAVConverter.read__int32(pHeader, mOffset + 0x04); + byte[] mInterlacedByteBuffer = WAVConverter.read__bytes(pHeader, mOffset + 0x08, mDataChunkSize); + int mDataSize = mInterlacedByteBuffer.length / mWAVStruct.channels / (mWAVStruct.bits_per_sample / 8); if (VERBOSE) { System.out.println("+++ CHUNK: " + mDataChunkName); System.out.println(" chunk size : " + mDataChunkSize); @@ -116,10 +116,10 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { mWAVStruct.samples = new float[mWAVStruct.channels][mDataSize]; final int mBytesPerSample = mWAVStruct.bits_per_sample / 8; - final int mStride = mWAVStruct.channels * mBytesPerSample; + final int mStride = mWAVStruct.channels * mBytesPerSample; for (int j = 0; j < mWAVStruct.channels; j++) { byte[] mByteSamples = new byte[mBytesPerSample * mDataSize]; - int c = 0; + int c = 0; for (int i = 0; i < mInterlacedByteBuffer.length; i += mStride) { for (int l = 0; l < mBytesPerSample; l++) { byte b = mInterlacedByteBuffer[i + j * mBytesPerSample + l]; @@ -137,6 +137,21 @@ public static Info convert_bytes_to_samples(byte[] pHeader) { return mWAVStruct; } + private static String getFormatString(int pFormat) { + String mFormatString; + switch (pFormat) { + case Wellen.WAV_FORMAT_PCM: + mFormatString = "PCM_16BIT"; + break; + case Wellen.WAV_FORMAT_IEEE_FLOAT_32BIT: + mFormatString = "IEEE_FLOAT_32BIT"; + break; + default: + mFormatString = "UNKNOWN/UNSUPPORTED"; + } + return mFormatString; + } + public static byte[] convert_samples_to_bytes(Info pInfo) { WAVConverter mWAVConverter = new WAVConverter(pInfo); mWAVConverter.appendData(pInfo.samples); @@ -150,11 +165,11 @@ public static byte[] convert_samples_to_bytes(float[][] pBuffer, int pSampleRate, int pCompressionCode) { Info mInfo = new Info(); - mInfo.samples = pBuffer; - mInfo.channels = pChannels; + mInfo.samples = pBuffer; + mInfo.channels = pChannels; mInfo.bits_per_sample = pBitsPerSample; - mInfo.sample_rate = pSampleRate; - mInfo.format = pCompressionCode; + mInfo.sample_rate = pSampleRate; + mInfo.format = pCompressionCode; return convert_samples_to_bytes(mInfo); } @@ -203,8 +218,8 @@ private static int read__int32(byte[] pBuffer, int pStart) { } private static String read_string(byte[] pBuffer, int pStart) { - final int mStringLength = 4; - StringBuilder sb = new StringBuilder(); + final int mStringLength = 4; + StringBuilder sb = new StringBuilder(); for (int i = 0; i < mStringLength; i++) { sb.append((char) pBuffer[pStart + i]); } @@ -255,7 +270,7 @@ private static void write_string(ArrayList pBuffer, String s) { } public void appendData(float[][] pFloatBuffer) { - int mNumberOfFrames = findSingleBufferLength(pFloatBuffer); + int mNumberOfFrames = findSingleBufferLength(pFloatBuffer); float[] mInterleavedFloatBuffer = new float[mNumberOfFrames * mChannels]; for (int i = 0; i < mNumberOfFrames; i++) { for (int mChannel = 0; mChannel < mChannels; mChannel++) { @@ -272,7 +287,9 @@ public void appendData(float[][] pFloatBuffer) { System.err.println("+++ ERROR @" + WAVConverter.class.getSimpleName() + " / data format not supported."); mByteBuffer = null; } - write__bytes(mData, mByteBuffer); + if (mByteBuffer != null) { + write__bytes(mData, mByteBuffer); + } } public void writeHeader() { @@ -307,11 +324,11 @@ public byte[] getByteData() { } public static class Info { - public int bits_per_sample; - public int channels; - public byte[] data; - public int format; - public int sample_rate; + public int bits_per_sample; + public int channels; + public byte[] data; + public int format; + public int sample_rate; public float[][] samples; } } diff --git a/src/wellen/Wellen.java b/src/wellen/Wellen.java index 286a19b..457d0d6 100644 --- a/src/wellen/Wellen.java +++ b/src/wellen/Wellen.java @@ -30,6 +30,7 @@ import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.TargetDataLine; +import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; @@ -257,16 +258,28 @@ public static float[] bytes_to_floatIEEEs(byte[] pBytes) { return mSignal; } + /** + * convert byte array to float array. + * + * @param pBytes source unsigned byte array + * @param pFloats destination float array + * @param pBitsPerFloat number of bits per float ( usually 8, 16, 24, or 32-bits ) + */ public static void bytes_to_floats(byte[] pBytes, float[] pFloats, int pBitsPerFloat) { - final int mBytesPerFloat = pBitsPerFloat / 8; + final int mBytesPerFloat = pBitsPerFloat / 8; + final double mScale = 1.0 / ((1 << (pBitsPerFloat - 1)) - 1); for (int i = 0; i < pFloats.length; i++) { - final double mScale = 1.0 / ((1 << (pBitsPerFloat - 1)) - 1); - long f = 0; + long f = 0; + for (int j = 0; j < mBytesPerFloat; j++) { - final long mBitShift = j * 8; - long b = pBytes[i * mBytesPerFloat + j]; - f += b << mBitShift; + int b = pBytes[i * mBytesPerFloat + j] & 0xFF; + f |= (long) b << (j * 8); + } + + if (f >= (1L << (pBitsPerFloat - 1))) { + f -= 1L << pBitsPerFloat; } + pFloats[i] = (float) (f * mScale); } } @@ -606,7 +619,8 @@ public static float[] get_extremum(float[] pSignal) { } public static String get_resource_path() { - return Wellen.class.getResource("").getPath(); + URL mURLPath = Wellen.class.getResource(""); + return mURLPath == null ? "" : mURLPath.getPath(); } public static float[][] importWAV(PApplet p, String pFilepath) { diff --git a/src/wellen/dsp/Sampler.java b/src/wellen/dsp/Sampler.java index fc38b19..273c107 100644 --- a/src/wellen/dsp/Sampler.java +++ b/src/wellen/dsp/Sampler.java @@ -39,28 +39,28 @@ */ public class Sampler implements DSPNodeOutput { - public static final int NO_LOOP_POINT = -1; - private final ArrayList fSamplerListeners; - private final ArrayList fRecording; - private final float fSamplingRate; - private float fAmplitude; - private float[] fBuffer; - private float fBufferIndex; - private boolean fDirectionForward; - private int fEdgeFadePadding; - private boolean fEvaluateLoop; - private float fFrequency; - private float fFrequencyScale; - private int fInPoint; - private boolean fInterpolateSamples; - private boolean fIsPlaying; - private int fLoopIn; - private int fLoopOut; - private int fOutPoint; - private float fSpeed; - private float fStepSize; - private boolean fIsFlaggedDone; - private boolean fIsRecording; + public static final int NO_LOOP_POINT = -1; + private final ArrayList fSamplerListeners; + private final ArrayList fRecording; + private final float fSamplingRate; + private float fAmplitude; + private float[] fBuffer; + private double fBufferIndex; + private boolean fDirectionForward; + private int fEdgeFadePadding; + private boolean fEvaluateLoop; + private float fFrequency; + private float fFrequencyScale; + private int fInPoint; + private boolean fInterpolateSamples; + private boolean fIsPlaying; + private int fLoopIn; + private int fLoopOut; + private int fOutPoint; + private float fSpeed; + private float fStepSize; + private boolean fIsFlaggedDone; + private boolean fIsRecording; public Sampler() { this(0); @@ -76,20 +76,21 @@ public Sampler(float[] buffer) { public Sampler(float[] buffer, float sampling_rate) { fSamplerListeners = new ArrayList<>(); - fSamplingRate = sampling_rate; + fSamplingRate = sampling_rate; set_buffer(buffer); - fBufferIndex = 0; + fBufferIndex = 0; fInterpolateSamples = false; - fEdgeFadePadding = 0; - fIsPlaying = false; - fInPoint = 0; - fOutPoint = 0; + fEdgeFadePadding = 0; + fIsPlaying = false; + fEvaluateLoop = false; + fInPoint = 0; + fOutPoint = 0; set_in(0); set_out(fBuffer.length - 1); fFrequencyScale = 1.0f; set_speed(1.0f); set_amplitude(1.0f); - fRecording = new ArrayList<>(); + fRecording = new ArrayList<>(); fIsRecording = false; } @@ -155,7 +156,7 @@ public float get_speed() { } public void set_speed(float speed) { - fSpeed = speed; + fSpeed = speed; fDirectionForward = speed > 0; set_frequency(PApplet.abs(speed) * fSamplingRate / fBuffer.length); /* aka `step_size = speed` */ } @@ -163,7 +164,7 @@ public void set_speed(float speed) { public void set_frequency(float frequency) { if (fFrequency != frequency) { fFrequency = frequency; - fStepSize = fFrequency / fFrequencyScale * ((float) fBuffer.length / fSamplingRate); + fStepSize = fFrequency / fFrequencyScale * ((float) fBuffer.length / fSamplingRate); } } @@ -181,7 +182,7 @@ public void set_buffer(float[] buffer) { set_speed(fSpeed); set_in(0); set_out(fBuffer.length - 1); - fLoopIn = NO_LOOP_POINT; + fLoopIn = NO_LOOP_POINT; fLoopOut = NO_LOOP_POINT; } @@ -198,11 +199,11 @@ public int get_position() { } public float get_position_normalized() { - return fBuffer.length > 0 ? fBufferIndex / fBuffer.length : 0.0f; + return fBuffer.length > 0 ? (float) fBufferIndex / fBuffer.length : 0.0f; } public float get_position_fractional_part() { - return fBufferIndex - get_position(); + return (float) fBufferIndex - get_position(); } public boolean is_playing() { @@ -214,7 +215,7 @@ public void set_duration(float seconds) { return; } final float mNormDurationSec = (fBuffer.length / fSamplingRate); - final float mSpeed = mNormDurationSec / seconds; + final float mSpeed = mNormDurationSec / seconds; set_speed(mSpeed); } @@ -242,8 +243,8 @@ public float output() { fBufferIndex += fDirectionForward ? fStepSize : -fStepSize; final int mRoundedIndex = (int) fBufferIndex; - final float mFrac = fBufferIndex - mRoundedIndex; - final int mCurrentIndex = wrapIndex(mRoundedIndex); + final double mFrac = fBufferIndex - mRoundedIndex; + final int mCurrentIndex = wrapIndex(mRoundedIndex); fBufferIndex = mCurrentIndex + mFrac; if (fDirectionForward ? (mCurrentIndex >= fOutPoint) : (mCurrentIndex <= fInPoint)) { @@ -253,14 +254,18 @@ public float output() { fIsFlaggedDone = false; } - float mSample = fBuffer[mCurrentIndex]; + return getSample(mCurrentIndex, mFrac); + } + + private float getSample(int mCurrentIndex, double mFrac) { + double mSample = fBuffer[mCurrentIndex]; /* interpolate */ if (fInterpolateSamples) { // TODO evaluate direction? - final int mNextIndex = wrapIndex(mCurrentIndex + 1); - final float mNextSample = fBuffer[mNextIndex]; - mSample = mSample * (1.0f - mFrac) + mNextSample * mFrac; + final int mNextIndex = wrapIndex(mCurrentIndex + 1); + final double mNextSample = fBuffer[mNextIndex]; + mSample = mSample * (1.0 - mFrac) + mNextSample * mFrac; } mSample *= fAmplitude; @@ -276,8 +281,7 @@ public float output() { mSample *= mFadeOutAmount; } } - - return mSample; + return (float) mSample; } public int get_edge_fading() { @@ -306,8 +310,8 @@ public void enable_loop(boolean loop) { public void set_loop_all() { fEvaluateLoop = true; - fLoopIn = 0; - fLoopOut = fBuffer.length > 0 ? (fBuffer.length - 1) : 0; + fLoopIn = 0; + fLoopOut = fBuffer.length > 0 ? (fBuffer.length - 1) : 0; } public void play() { @@ -500,10 +504,10 @@ public static void draw_sampler_buffer_circular(Sampler sampler, int step) { g.beginShape(); for (int i = 0; i < sampler.get_buffer().length; i += step) { - final float r = TWO_PI * i / sampler.get_buffer().length; + final float r = TWO_PI * i / sampler.get_buffer().length; final float mSample = map(sampler.get_buffer()[i], -1.0f, 1.0f, radius_min, radius_max); - final float x = cos(r) * mSample; - final float y = sin(r) * mSample; + final float x = cos(r) * mSample; + final float y = sin(r) * mSample; g.vertex(x, y); } g.endShape(CLOSE); diff --git a/src/wellen/tests/TestMultiChannelDSP.java b/src/wellen/tests/TestMultiChannelDSP.java new file mode 100644 index 0000000..5933a76 --- /dev/null +++ b/src/wellen/tests/TestMultiChannelDSP.java @@ -0,0 +1,50 @@ +package wellen.tests; + +import processing.core.PApplet; +import wellen.Wellen; +import wellen.dsp.DSP; + +public class TestMultiChannelDSP extends PApplet { + +// private int mCounter = 0; +// private float mDetune = 1.1f; +// private float mFreq = 344.53125f; + + public void settings() { + size(640, 480); + } + + public void setup() { + Wellen.dumpAudioInputAndOutputDevices(); + DSP.start(this, + Wellen.DEFAULT_AUDIO_DEVICE, + 2, + Wellen.DEFAULT_AUDIO_DEVICE, + 0); + } + + public void draw() { + background(255); + stroke(0); + DSP.draw_buffers(g, width, height); + } + + public void mouseMoved() { +// mFreq = map(mouseX, 0, width, 86.1328125f, 344.53125f); +// mDetune = map(mouseY, 0, height, 1.0f, 1.5f); + } + + public void audioblock(float[][] output_signalLeft, float[][] output_signalRight) { +// for (int i = 0; i < output_signalLeft.length; i++) { +// mCounter++; +// float mLeft = 0.5f * sin(2 * PI * mFreq * mCounter / DSP.get_sample_rate()); +// float mRight = 0.5f * sin(2 * PI * mFreq * mDetune * mCounter / DSP.get_sample_rate()); +// output_signalLeft[i] = mLeft * 0.7f + mRight * 0.3f; +// output_signalRight[i] = mLeft * 0.3f + mRight * 0.7f; +// } + } + + public static void main(String[] args) { + PApplet.main(TestMultiChannelDSP.class.getName()); + } +}