diff --git a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp index 5ae41f734..9f55e137d 100644 --- a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp +++ b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp @@ -45,11 +45,11 @@ static SimpleMultiPlayer sDTPlayer; * Native (JNI) implementation of DrumPlayer.setupAudioStreamNative() */ JNIEXPORT void JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_setupAudioStreamNative( - JNIEnv* env, jobject, jint sampleRate, jint numChannels) { + JNIEnv* env, jobject, jint numChannels) { __android_log_print(ANDROID_LOG_INFO, TAG, "%s", "init()"); // we know in this case that the sample buffers are all 1-channel, 41K - sDTPlayer.setupAudioStream(sampleRate, numChannels); + sDTPlayer.setupAudioStream(numChannels); } /** @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_teardow * Native (JNI) implementation of DrumPlayer.loadWavAssetNative() */ JNIEXPORT jboolean JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_loadWavAssetNative( - JNIEnv* env, jobject, jbyteArray bytearray, jint index, jfloat pan, jint rate, jint channels) { + JNIEnv* env, jobject, jbyteArray bytearray, jint index, jfloat pan, jint channels) { int len = env->GetArrayLength (bytearray); unsigned char* buf = new unsigned char[len]; @@ -80,12 +80,14 @@ JNIEXPORT jboolean JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_loa WavStreamReader reader(&stream); reader.parse(); - jboolean isFormatValid = - (reader.getSampleRate() == rate) && (reader.getNumChannels() == channels); + jboolean isFormatValid = reader.getNumChannels() == channels; SampleBuffer* sampleBuffer = new SampleBuffer(); sampleBuffer->loadSampleData(&reader); + int sampleRate = sDTPlayer.getDeviceSampleRate(2 /*channelCount*/); + sampleBuffer->resampleData(sampleRate); + OneShotSampleSource* source = new OneShotSampleSource(sampleBuffer, pan); sDTPlayer.addSampleSource(source, sampleBuffer); diff --git a/samples/drumthumper/src/main/java/com/plausibleaudio/drumthumper/DrumPlayer.kt b/samples/drumthumper/src/main/java/com/plausibleaudio/drumthumper/DrumPlayer.kt index 8d50510a9..fbe755397 100644 --- a/samples/drumthumper/src/main/java/com/plausibleaudio/drumthumper/DrumPlayer.kt +++ b/samples/drumthumper/src/main/java/com/plausibleaudio/drumthumper/DrumPlayer.kt @@ -27,8 +27,8 @@ class DrumPlayer { // This IS NOT the channel format of the source samples // (which must be mono). val NUM_SAMPLE_CHANNELS: Int = 1; // All WAV resource must be mono - val SAMPLE_RATE: Int = 44100 // All the input samples are assumed to BE 44.1K - // All the input samples are assumed to be mono. +// val SAMPLE_RATE: Int = 44100 // All the input samples are assumed to BE 44.1K +// // All the input samples are assumed to be mono. // Sample Buffer IDs val BASSDRUM: Int = 0 @@ -55,7 +55,7 @@ class DrumPlayer { } fun setupAudioStream() { - setupAudioStreamNative(SAMPLE_RATE, NUM_PLAY_CHANNELS) + setupAudioStreamNative(NUM_PLAY_CHANNELS) } fun teardownAudioStream() { @@ -89,7 +89,7 @@ class DrumPlayer { var dataLen = assetFD.getLength().toInt() var dataBytes: ByteArray = ByteArray(dataLen) dataStream.read(dataBytes, 0, dataLen) - returnVal = loadWavAssetNative(dataBytes, index, pan, SAMPLE_RATE, NUM_SAMPLE_CHANNELS) + returnVal = loadWavAssetNative(dataBytes, index, pan, NUM_SAMPLE_CHANNELS) assetFD.close() } catch (ex: IOException) { Log.i(TAG, "IOException" + ex) @@ -98,11 +98,11 @@ class DrumPlayer { return returnVal } - external fun setupAudioStreamNative(sampleRate: Int, numChannels: Int) + external fun setupAudioStreamNative(numChannels: Int) external fun teardownAudioStreamNative() external fun loadWavAssetNative( - wavBytes: ByteArray, index: Int, pan: Float, rate: Int, channels: Int) : Boolean + wavBytes: ByteArray, index: Int, pan: Float, channels: Int) : Boolean external fun unloadWavAssetsNative() external fun trigger(drumIndex: Int) diff --git a/samples/iolib/src/main/cpp/CMakeLists.txt b/samples/iolib/src/main/cpp/CMakeLists.txt index 2c3d92328..dd8c62634 100644 --- a/samples/iolib/src/main/cpp/CMakeLists.txt +++ b/samples/iolib/src/main/cpp/CMakeLists.txt @@ -29,6 +29,7 @@ set (PARSELIB_DIR ../../../../parselib) include_directories( ${PARSELIB_DIR}/src/main/cpp ${OBOE_DIR}/include + ${OBOE_DIR}/src/flowgraph ${CMAKE_CURRENT_LIST_DIR} ../../../../shared) diff --git a/samples/iolib/src/main/cpp/player/SampleBuffer.cpp b/samples/iolib/src/main/cpp/player/SampleBuffer.cpp index ca3a90580..a1544bd99 100644 --- a/samples/iolib/src/main/cpp/player/SampleBuffer.cpp +++ b/samples/iolib/src/main/cpp/player/SampleBuffer.cpp @@ -16,8 +16,13 @@ #include "SampleBuffer.h" +// Resampler Includes +#include + #include "wav/WavStreamReader.h" +using namespace resampler; + namespace iolib { void SampleBuffer::loadSampleData(parselib::WavStreamReader* reader) { @@ -41,4 +46,73 @@ void SampleBuffer::unloadSampleData() { mNumSamples = 0; } +class ResampleBlock { +public: + int32_t mSampleRate; + float* mBuffer; + int32_t mNumFrames; +}; + +void resampleData(const ResampleBlock& input, ResampleBlock* output) { + // Calculate output buffer size + double temp = + ((double)input.mNumFrames * (double)output->mSampleRate) / (double)input.mSampleRate; + + // round up + int32_t numOutFrames = (int32_t)(temp + 0.5); + output->mNumFrames = numOutFrames; + + MultiChannelResampler *resampler = MultiChannelResampler::make( + 1, // channel count + input.mSampleRate, // input sampleRate + output->mSampleRate, // output sampleRate + MultiChannelResampler::Quality::Medium); // conversion quality + + float *inputBuffer = input.mBuffer;; // multi-channel buffer to be consumed + float *outputBuffer = new float[numOutFrames]; // multi-channel buffer to be filled + output->mBuffer = outputBuffer; + int numInputFrames = input.mNumFrames; // number of frames of input + int numOutputFrames = 0; + int channelCount = 1; // 1 for mono, 2 for stereo + + int inputFramesLeft = numInputFrames; + while (inputFramesLeft > 0) { + if(resampler->isWriteNeeded()) { + resampler->writeNextFrame(inputBuffer); + inputBuffer += channelCount; + inputFramesLeft--; + } else { + resampler->readNextFrame(outputBuffer); + outputBuffer += channelCount; + numOutputFrames++; + } + } + + delete resampler; } + +void SampleBuffer::resampleData(int sampleRate) { + if (mAudioProperties.sampleRate == sampleRate) { + // nothing to do + return; + } + + ResampleBlock inputBlock; + inputBlock.mBuffer = mSampleData; + inputBlock.mNumFrames = mNumSamples; + inputBlock.mSampleRate = mAudioProperties.sampleRate; + + ResampleBlock outputBlock; + outputBlock.mSampleRate = sampleRate; + iolib::resampleData(inputBlock, &outputBlock); + + // delete previous samples + delete[] mSampleData; + + // install the resampled data + mSampleData = outputBlock.mBuffer; + mNumSamples = outputBlock.mNumFrames; + mAudioProperties.sampleRate = outputBlock.mSampleRate; +} + +} // namespace iolib diff --git a/samples/iolib/src/main/cpp/player/SampleBuffer.h b/samples/iolib/src/main/cpp/player/SampleBuffer.h index 9d61f28db..c92ee78f5 100644 --- a/samples/iolib/src/main/cpp/player/SampleBuffer.h +++ b/samples/iolib/src/main/cpp/player/SampleBuffer.h @@ -38,6 +38,8 @@ class SampleBuffer { void loadSampleData(parselib::WavStreamReader* reader); void unloadSampleData(); + void resampleData(int sampleRate); + virtual AudioProperties getProperties() const { return mAudioProperties; } float* getSampleData() { return mSampleData; } diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp index 6110b2900..3c24b9e07 100644 --- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp +++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp @@ -34,7 +34,7 @@ namespace iolib { constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer) SimpleMultiPlayer::SimpleMultiPlayer() - : mChannelCount(0), mSampleRate(0), mOutputReset(false) + : mChannelCount(0), mOutputReset(false) {} DataCallbackResult SimpleMultiPlayer::onAudioReady(AudioStream *oboeStream, void *audioData, @@ -72,13 +72,34 @@ void SimpleMultiPlayer::onErrorBeforeClose(AudioStream *, Result error) { __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorBeforeClose() error:%d", error); } +int SimpleMultiPlayer::getDeviceSampleRate(int32_t channelCount) { + + // Create an audio stream + AudioStreamBuilder builder; + builder.setChannelCount(channelCount); + builder.setPerformanceMode(PerformanceMode::LowLatency); + builder.setSharingMode(SharingMode::Exclusive); + builder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium); + + oboe::ManagedStream audioStream; + Result result = builder.openManagedStream(audioStream); + if (result != Result::OK) { + __android_log_print(ANDROID_LOG_ERROR, TAG, + "openStream failed. Error: %s", convertToText(result)); + return 0; + } else { + int rate = audioStream->getSampleRate(); + return rate; + } +} + bool SimpleMultiPlayer::openStream() { __android_log_print(ANDROID_LOG_INFO, TAG, "openStream()"); // Create an audio stream AudioStreamBuilder builder; builder.setChannelCount(mChannelCount); - builder.setSampleRate(mSampleRate); + // builder.setSampleRate(mSampleRate); // we will resample to device rate builder.setCallback(this); builder.setPerformanceMode(PerformanceMode::LowLatency); builder.setSharingMode(SharingMode::Exclusive); @@ -117,11 +138,9 @@ bool SimpleMultiPlayer::openStream() { return true; } -void SimpleMultiPlayer::setupAudioStream(int32_t sampleRate, int32_t channelCount) { +void SimpleMultiPlayer::setupAudioStream(int32_t channelCount) { __android_log_print(ANDROID_LOG_INFO, TAG, "setupAudioStream()"); mChannelCount = channelCount; - mSampleRate = sampleRate; - mSampleRate = sampleRate; openStream(); } diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h index 3e0a1c777..efec8b91c 100644 --- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h +++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h @@ -41,9 +41,11 @@ class SimpleMultiPlayer : public oboe::AudioStreamCallback { virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override; virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override; - void setupAudioStream(int32_t sampleRate, int32_t channelCount); + void setupAudioStream(int32_t channelCount); void teardownAudioStream(); + static int getDeviceSampleRate(int32_t channelCount); + bool openStream(); // Wave Sample Loading... diff --git a/src/flowgraph/resampler/README.md b/src/flowgraph/resampler/README.md index 2026773ab..ecf030ff0 100644 --- a/src/flowgraph/resampler/README.md +++ b/src/flowgraph/resampler/README.md @@ -20,7 +20,7 @@ Only do this once, when you open your stream. Then use the sample resampler to p 2, // channel count 44100, // input sampleRate 48000, // output sampleRate - MultiChannelResampler::Medium); // conversion quality + MultiChannelResampler::Quality::Medium); // conversion quality Possible values for quality include { Fastest, Low, Medium, High, Best }. Higher quality levels will sound better but consume more CPU because they have more taps in the filter.