diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt index 624d911..1125b74 100644 --- a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt +++ b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt @@ -12,7 +12,11 @@ RecyclerView.ViewHolder(binding.root) private val instruments = - mutableListOf(Instrument("First Instrument"), Instrument("second instrument")) + mutableListOf( + Instrument("A", 440.0), + Instrument("C#", 440 * 1.25), + Instrument("E", 440 * 1.5), + ) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstrumentViewHolder { val context = parent.context @@ -23,7 +27,7 @@ override fun onBindViewHolder(holder: InstrumentViewHolder, position: Int) { val instrument = instruments[position] - holder.binding.instrumentNameText.text = instrument.name + instrument.applyToView(holder.binding) } override fun getItemCount(): Int { diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt index 624d911..1125b74 100644 --- a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt +++ b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt @@ -12,7 +12,11 @@ RecyclerView.ViewHolder(binding.root) private val instruments = - mutableListOf(Instrument("First Instrument"), Instrument("second instrument")) + mutableListOf( + Instrument("A", 440.0), + Instrument("C#", 440 * 1.25), + Instrument("E", 440 * 1.5), + ) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstrumentViewHolder { val context = parent.context @@ -23,7 +27,7 @@ override fun onBindViewHolder(holder: InstrumentViewHolder, position: Int) { val instrument = instruments[position] - holder.binding.instrumentNameText.text = instrument.name + instrument.applyToView(holder.binding) } override fun getItemCount(): Int { diff --git a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt index 66a31d3..d79b8e2 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.lukas.music.R import com.lukas.music.databinding.FragmentInstrumentBinding +import com.lukas.music.instruments.Instrument class InstrumentFragment() : Fragment(R.layout.fragment_instrument) { private lateinit var binding: FragmentInstrumentBinding diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt index 624d911..1125b74 100644 --- a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt +++ b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt @@ -12,7 +12,11 @@ RecyclerView.ViewHolder(binding.root) private val instruments = - mutableListOf(Instrument("First Instrument"), Instrument("second instrument")) + mutableListOf( + Instrument("A", 440.0), + Instrument("C#", 440 * 1.25), + Instrument("E", 440 * 1.5), + ) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstrumentViewHolder { val context = parent.context @@ -23,7 +27,7 @@ override fun onBindViewHolder(holder: InstrumentViewHolder, position: Int) { val instrument = instruments[position] - holder.binding.instrumentNameText.text = instrument.name + instrument.applyToView(holder.binding) } override fun getItemCount(): Int { diff --git a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt index 66a31d3..d79b8e2 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.lukas.music.R import com.lukas.music.databinding.FragmentInstrumentBinding +import com.lukas.music.instruments.Instrument class InstrumentFragment() : Fragment(R.layout.fragment_instrument) { private lateinit var binding: FragmentInstrumentBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt index cee2515..23517ea 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt @@ -18,11 +18,6 @@ ): View? { binding = FragmentPlayBinding.inflate(inflater) binding.playSwitch.setOnCheckedChangeListener { _, isOn -> - if (isOn) { - MainActivity.unmuteAudio() - } else { - MainActivity.muteAudio() - } } return binding.root } diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt index 624d911..1125b74 100644 --- a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt +++ b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt @@ -12,7 +12,11 @@ RecyclerView.ViewHolder(binding.root) private val instruments = - mutableListOf(Instrument("First Instrument"), Instrument("second instrument")) + mutableListOf( + Instrument("A", 440.0), + Instrument("C#", 440 * 1.25), + Instrument("E", 440 * 1.5), + ) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstrumentViewHolder { val context = parent.context @@ -23,7 +27,7 @@ override fun onBindViewHolder(holder: InstrumentViewHolder, position: Int) { val instrument = instruments[position] - holder.binding.instrumentNameText.text = instrument.name + instrument.applyToView(holder.binding) } override fun getItemCount(): Int { diff --git a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt index 66a31d3..d79b8e2 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.lukas.music.R import com.lukas.music.databinding.FragmentInstrumentBinding +import com.lukas.music.instruments.Instrument class InstrumentFragment() : Fragment(R.layout.fragment_instrument) { private lateinit var binding: FragmentInstrumentBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt index cee2515..23517ea 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt @@ -18,11 +18,6 @@ ): View? { binding = FragmentPlayBinding.inflate(inflater) binding.playSwitch.setOnCheckedChangeListener { _, isOn -> - if (isOn) { - MainActivity.unmuteAudio() - } else { - MainActivity.muteAudio() - } } return binding.root } diff --git a/app/src/main/res/layout/fragment_instrument.xml b/app/src/main/res/layout/fragment_instrument.xml index e29a9b2..1065e57 100644 --- a/app/src/main/res/layout/fragment_instrument.xml +++ b/app/src/main/res/layout/fragment_instrument.xml @@ -4,13 +4,15 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="20dp" + android:contentDescription="edit this instrument's properties" tools:context=".ui.fragments.InstrumentFragment"> + android:layout_height="match_parent" + android:contentDescription="edit this instrument's properties" + android:paddingBottom="20dp"> + + + + \ No newline at end of file diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt index 624d911..1125b74 100644 --- a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt +++ b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt @@ -12,7 +12,11 @@ RecyclerView.ViewHolder(binding.root) private val instruments = - mutableListOf(Instrument("First Instrument"), Instrument("second instrument")) + mutableListOf( + Instrument("A", 440.0), + Instrument("C#", 440 * 1.25), + Instrument("E", 440 * 1.5), + ) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstrumentViewHolder { val context = parent.context @@ -23,7 +27,7 @@ override fun onBindViewHolder(holder: InstrumentViewHolder, position: Int) { val instrument = instruments[position] - holder.binding.instrumentNameText.text = instrument.name + instrument.applyToView(holder.binding) } override fun getItemCount(): Int { diff --git a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt index 66a31d3..d79b8e2 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.lukas.music.R import com.lukas.music.databinding.FragmentInstrumentBinding +import com.lukas.music.instruments.Instrument class InstrumentFragment() : Fragment(R.layout.fragment_instrument) { private lateinit var binding: FragmentInstrumentBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt index cee2515..23517ea 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt @@ -18,11 +18,6 @@ ): View? { binding = FragmentPlayBinding.inflate(inflater) binding.playSwitch.setOnCheckedChangeListener { _, isOn -> - if (isOn) { - MainActivity.unmuteAudio() - } else { - MainActivity.muteAudio() - } } return binding.root } diff --git a/app/src/main/res/layout/fragment_instrument.xml b/app/src/main/res/layout/fragment_instrument.xml index e29a9b2..1065e57 100644 --- a/app/src/main/res/layout/fragment_instrument.xml +++ b/app/src/main/res/layout/fragment_instrument.xml @@ -4,13 +4,15 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="20dp" + android:contentDescription="edit this instrument's properties" tools:context=".ui.fragments.InstrumentFragment"> + android:layout_height="match_parent" + android:contentDescription="edit this instrument's properties" + android:paddingBottom="20dp"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_instrument_list.xml b/app/src/main/res/layout/fragment_instrument_list.xml index 4661fb3..bc04751 100644 --- a/app/src/main/res/layout/fragment_instrument_list.xml +++ b/app/src/main/res/layout/fragment_instrument_list.xml @@ -30,6 +30,7 @@ android:layout_marginEnd="20dp" android:layout_marginBottom="20dp" android:clickable="true" + android:contentDescription="add a new instrument" android:src="@android:drawable/ic_input_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/cpp/AndroidBridge.cpp b/app/src/main/cpp/AndroidBridge.cpp deleted file mode 100644 index 58a30cf..0000000 --- a/app/src/main/cpp/AndroidBridge.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include "AudioEngine.h" -#include -#include - -static AudioEngine *audioEngine; - -extern "C" { - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { - audioEngine = new AudioEngine(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_muteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->stop(); -} - -JNIEXPORT void JNICALL -Java_com_lukas_music_MainActivity_00024Companion_unmuteAudio(JNIEnv *env, jobject activity, float volume) { - audioEngine->start(); -} -} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.cpp b/app/src/main/cpp/AudioHost.cpp new file mode 100644 index 0000000..cf182be --- /dev/null +++ b/app/src/main/cpp/AudioHost.cpp @@ -0,0 +1,62 @@ +#include "AudioHost.h" +#include +#include +#include + +const uint32_t bufferSize = 2; +static uint32_t sampleCount; +static float *buffer; + +void renderInstrument(Instrument * instrument) { + instrument->render(buffer, sampleCount); +} + +aaudio_data_callback_result_t dataCallback( + AAudioStream *stream, + void *userData, + void *audioData, + int32_t _sampleCount) { + sampleCount = _sampleCount; + buffer = static_cast(audioData); + for (uint32_t i = 0; i < _sampleCount; i++) { + buffer[i] = 0; + } + AudioHost *thiz = static_cast(userData); + std::for_each(thiz->instruments->begin(), thiz->instruments->end(), renderInstrument); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + +void errorCallback(AAudioStream *stream, void *data, aaudio_result_t error) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "an error occurred"); +} + +AudioHost::AudioHost() { + AAudioStreamBuilder *streamBuilder; + AAudio_createStreamBuilder(&streamBuilder); + AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT); + AAudioStreamBuilder_setChannelCount(streamBuilder, 1); + AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, this); + AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this); + + aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error opening stream %s", + AAudio_convertResultToText(result)); + return; + } + + this->sampleRate = AAudioStream_getSampleRate(stream); + + AAudioStream_setBufferSizeInFrames( + stream, AAudioStream_getFramesPerBurst(stream) * bufferSize); + + result = AAudioStream_requestStart(stream); + if (result != AAUDIO_OK) { + __android_log_print(ANDROID_LOG_ERROR, "AudioHost", "Error starting stream %s", + AAudio_convertResultToText(result)); + return; + } + + AAudioStreamBuilder_delete(streamBuilder); +} \ No newline at end of file diff --git a/app/src/main/cpp/AudioHost.h b/app/src/main/cpp/AudioHost.h new file mode 100644 index 0000000..e8976a8 --- /dev/null +++ b/app/src/main/cpp/AudioHost.h @@ -0,0 +1,21 @@ +#ifndef MUSIC_AUDIO_HOST_H +#define MUSIC_AUDIO_HOST_H + +class AudioHost; + +#include "SineWave.h" +#include "Instrument.h" +#include +#include + +class AudioHost { +private: + AAudioStream *stream; +public: + uint32_t sampleRate = 0; + std::list *instruments = new std::list(); + AudioHost(); +}; + + +#endif //MUSIC_AUDIO_HOST_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 92cc33c..9d6721f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -8,8 +8,9 @@ SHARED SineWave.cpp - AndroidBridge.cpp - AudioEngine.cpp + JavaFunctions.cpp + AudioHost.cpp + Instrument.cpp ) find_library( diff --git a/app/src/main/cpp/Instrument.cpp b/app/src/main/cpp/Instrument.cpp new file mode 100644 index 0000000..f17846b --- /dev/null +++ b/app/src/main/cpp/Instrument.cpp @@ -0,0 +1,9 @@ +#include "Instrument.h" + +Instrument::Instrument(AudioHost *host) { + this->wave = new SineWave(host); +} + +void Instrument::render(float *buffer, uint32_t count) { + this->wave->render(buffer, count); +} \ No newline at end of file diff --git a/app/src/main/cpp/Instrument.h b/app/src/main/cpp/Instrument.h new file mode 100644 index 0000000..9f9f901 --- /dev/null +++ b/app/src/main/cpp/Instrument.h @@ -0,0 +1,15 @@ +#ifndef MUSIC_INSTRUMENT_H +#define MUSIC_INSTRUMENT_H + +class Instrument; + +#include "AudioHost.h" + +class Instrument { +public: + Instrument(AudioHost *host); + SineWave *wave; + void render(float *buffer, uint32_t count); +}; + +#endif //MUSIC_INSTRUMENT_H diff --git a/app/src/main/cpp/JavaFunctions.cpp b/app/src/main/cpp/JavaFunctions.cpp new file mode 100644 index 0000000..af3f819 --- /dev/null +++ b/app/src/main/cpp/JavaFunctions.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AudioHost.h" +#include +#include +#include + +static AudioHost *audioHost; + + +template +void *listGet(_InputIterator iterator, uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + iterator++; + } + return *iterator; +} + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_lukas_music_MainActivity_00024Companion_startAudio(JNIEnv *env, jobject activity) { + audioHost = new AudioHost(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_lukas_music_instruments_Instrument_createInstrument(JNIEnv *env, jobject thiz) { + uint32_t result = audioHost->instruments->size(); + Instrument *instrument = new Instrument(audioHost); + audioHost->instruments->push_back(instrument); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentFrequency(JNIEnv *env, jobject thiz, + jint id, jdouble frequency) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->setFrequency(frequency); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_lukas_music_instruments_Instrument_setInstrumentActive(JNIEnv *env, jobject thiz, + jint id, jboolean active) { + Instrument *instrument = static_cast(listGet(audioHost->instruments->begin(), id)); + instrument->wave->amplitude = active ? 0.3 : 0.0; +} +} \ No newline at end of file diff --git a/app/src/main/cpp/SineWave.cpp b/app/src/main/cpp/SineWave.cpp index 1aad9c3..8b572b7 100644 --- a/app/src/main/cpp/SineWave.cpp +++ b/app/src/main/cpp/SineWave.cpp @@ -2,16 +2,21 @@ #include #include -void SineWave::setSampleRate(uint32_t sampleRate) { - phaseStep = (2 * M_PI * frequency) / (double) sampleRate; +SineWave::SineWave(AudioHost *host) { + this->host = host; +} + +void SineWave::setFrequency(float freq) { + frequency = freq; + phaseStep = (2 * M_PI * frequency) / (double) host->sampleRate; } void SineWave::render(float *data, uint32_t frameCount) { for (uint32_t i = 0; i < frameCount; i++) { - data[i] = (float)(sin(phase) * amplitude); + data[i] += (float)(sin(phase) * amplitude); phase += phaseStep; if (phase > 2*M_PI) { phase -= 2*M_PI; } } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/SineWave.h b/app/src/main/cpp/SineWave.h index acdd262..b13aab4 100644 --- a/app/src/main/cpp/SineWave.h +++ b/app/src/main/cpp/SineWave.h @@ -1,15 +1,21 @@ #ifndef MUSIC_SINEWAVE_H #define MUSIC_SINEWAVE_H +class SineWave; + #include +#include "AudioHost.h" class SineWave { private: - float phaseStep = 0.01, phase = 0; + float phaseStep, phase = 0; + float frequency; + AudioHost *host; public: - float amplitude = 0.3, frequency = 440.0f; + SineWave(AudioHost *host); + float amplitude = 0.0f; void render(float *data, uint32_t frameCount); - void setSampleRate(uint32_t sampleRate); + void setFrequency(float freq); }; #endif //MUSIC_SINEWAVE_H \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index 8693298..0000000 --- a/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -extern "C" JNIEXPORT jstring JNICALL -Java_com_lukas_music_MainActivity_stringFromJNI( - JNIEnv* env, - jobject /* this */) { - std::string hello = "Hello from C++"; - return env->NewStringUTF(hello.c_str()); -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/InstrumentView.kt b/app/src/main/java/com/lukas/music/InstrumentView.kt deleted file mode 100644 index 6ac6230..0000000 --- a/app/src/main/java/com/lukas/music/InstrumentView.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lukas.music - -import android.content.Context -import android.graphics.Canvas -import android.util.AttributeSet -import android.view.View -import com.lukas.music.ui.fragments.InstrumentFragment - -class InstrumentView : View { - val fragment = InstrumentFragment() - - constructor(context: Context) : super(context) { - init(null, 0) - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init(attrs, 0) - } - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - init(attrs, defStyle) - } - - private fun init(attrs: AttributeSet?, defStyle: Int) { - val a = context.obtainStyledAttributes( - attrs, R.styleable.InstrumentView, defStyle, 0 - ) - - a.recycle() - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - val paddingLeft = paddingLeft - val paddingTop = paddingTop - val paddingRight = paddingRight - val paddingBottom = paddingBottom - - val contentWidth = width - paddingLeft - paddingRight - val contentHeight = height - paddingTop - paddingBottom - // fragment.view?.left = paddingLeft - // fragment.view?.top = paddingTop - // fragment.view?.right = paddingLeft + contentWidth - // fragment.view?.bottom = paddingTop + contentHeight - fragment.view?.draw(canvas) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/MainActivity.kt b/app/src/main/java/com/lukas/music/MainActivity.kt index abb2312..1a534dd 100644 --- a/app/src/main/java/com/lukas/music/MainActivity.kt +++ b/app/src/main/java/com/lukas/music/MainActivity.kt @@ -25,13 +25,10 @@ binding.tabPager.registerOnPageChangeCallback(PageListener(binding.tabLayout)) binding.tabLayout.addOnTabSelectedListener(TabListener(binding.tabPager)) startAudio() - // muteAudio() } companion object { external fun startAudio() - external fun muteAudio() - external fun unmuteAudio() init { System.loadLibrary("music") diff --git a/app/src/main/java/com/lukas/music/instruments/Instrument.kt b/app/src/main/java/com/lukas/music/instruments/Instrument.kt index 080f689..a3000b7 100644 --- a/app/src/main/java/com/lukas/music/instruments/Instrument.kt +++ b/app/src/main/java/com/lukas/music/instruments/Instrument.kt @@ -1,5 +1,29 @@ package com.lukas.music.instruments -class Instrument(val name: String) { - val id = 0 +import com.lukas.music.databinding.FragmentInstrumentBinding + +class Instrument(val name: String, frequency: Double) { + private val id = createInstrument() + private var active = false + + init { + print("id: $id") + setInstrumentFrequency(id, frequency) + } + + fun applyToView(binding: FragmentInstrumentBinding) { + binding.instrumentNameText.text = name + binding.floatingActionButton2.setOnClickListener { + println("click instrument $name") + } + binding.activeSwitch.setOnCheckedChangeListener {_, newActive -> + active = newActive + setInstrumentActive(id, newActive) + } + binding.activeSwitch.isChecked = active + } + + private external fun createInstrument(): Int + private external fun setInstrumentFrequency(id: Int, frequency: Double) + private external fun setInstrumentActive(id: Int, isActive: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt index 624d911..1125b74 100644 --- a/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt +++ b/app/src/main/java/com/lukas/music/ui/InstrumentAdapter.kt @@ -12,7 +12,11 @@ RecyclerView.ViewHolder(binding.root) private val instruments = - mutableListOf(Instrument("First Instrument"), Instrument("second instrument")) + mutableListOf( + Instrument("A", 440.0), + Instrument("C#", 440 * 1.25), + Instrument("E", 440 * 1.5), + ) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstrumentViewHolder { val context = parent.context @@ -23,7 +27,7 @@ override fun onBindViewHolder(holder: InstrumentViewHolder, position: Int) { val instrument = instruments[position] - holder.binding.instrumentNameText.text = instrument.name + instrument.applyToView(holder.binding) } override fun getItemCount(): Int { diff --git a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt index 66a31d3..d79b8e2 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/InstrumentFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import com.lukas.music.R import com.lukas.music.databinding.FragmentInstrumentBinding +import com.lukas.music.instruments.Instrument class InstrumentFragment() : Fragment(R.layout.fragment_instrument) { private lateinit var binding: FragmentInstrumentBinding diff --git a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt index cee2515..23517ea 100644 --- a/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt +++ b/app/src/main/java/com/lukas/music/ui/fragments/PlayFragment.kt @@ -18,11 +18,6 @@ ): View? { binding = FragmentPlayBinding.inflate(inflater) binding.playSwitch.setOnCheckedChangeListener { _, isOn -> - if (isOn) { - MainActivity.unmuteAudio() - } else { - MainActivity.muteAudio() - } } return binding.root } diff --git a/app/src/main/res/layout/fragment_instrument.xml b/app/src/main/res/layout/fragment_instrument.xml index e29a9b2..1065e57 100644 --- a/app/src/main/res/layout/fragment_instrument.xml +++ b/app/src/main/res/layout/fragment_instrument.xml @@ -4,13 +4,15 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="20dp" + android:contentDescription="edit this instrument's properties" tools:context=".ui.fragments.InstrumentFragment"> + android:layout_height="match_parent" + android:contentDescription="edit this instrument's properties" + android:paddingBottom="20dp"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_instrument_list.xml b/app/src/main/res/layout/fragment_instrument_list.xml index 4661fb3..bc04751 100644 --- a/app/src/main/res/layout/fragment_instrument_list.xml +++ b/app/src/main/res/layout/fragment_instrument_list.xml @@ -30,6 +30,7 @@ android:layout_marginEnd="20dp" android:layout_marginBottom="20dp" android:clickable="true" + android:contentDescription="add a new instrument" android:src="@android:drawable/ic_input_add" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/sample_instrument_view.xml b/app/src/main/res/layout/sample_instrument_view.xml deleted file mode 100644 index f6620a6..0000000 --- a/app/src/main/res/layout/sample_instrument_view.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file