Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
No known key found for this signature in database
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1,46 @@
Soundboard
==========
Do you want to just jam with some digital tunes? Now you can!
This sample demonstrates how to obtain the lowest latency and optimal computational throughput by:
1) Leaving Oboe to choose the best default stream properties for the current device
2) Setting performance mode to LowLatency
3) Setting sharing mode to Exclusive
4) Setting the buffer size to 2 bursts
5) Using the `-Ofast` compiler optimization flag, even when building the `Debug` variant
The [following article explaining how to debug CPU performance problems](https://medium.com/@donturner/debugging-audio-glitches-on-android-ed10782f9c64) may also be useful when looking at this code.
Implementation details
---
The stream properties are left to Oboe as such the app must output audio data in a format which matches that of the stream.
Four different formats are supported:
|Channel count|Format|
|-------------|------|
|1 - Mono|16-bit int|
|2 - Stereo|16-bit int|
|1 - Mono|Float|
|2 - Stereo|Float|
The signal chain for mono streams is:
SynthSound->Mixer
For stereo chains a mono to stereo converter is added to the end of the chain:
SynthSound->Mixer->MonoToStereo
The compiler optimization flag `-Ofast` can be found in [CMakeLists.txt](CMakeLists.txt).
Each SynthSound is a series of 5 Oscillators, creating a pleasant sounding note when combined.
The number of notes depends on the shape of the screen, with G3 being the first note.
In order to determine whether a note should be played, MusicTileView demonstrates how to keep track of where each finger is.
Images
-----------
![soundboard_image](soundboard_image.png)

View file

@ -0,0 +1,59 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
defaultConfig {
applicationId "com.google.oboe.samples.soundboard"
minSdkVersion 21
targetSdkVersion 35
compileSdkVersion 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++17"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
signingConfigs {
release {
storeFile new File("${System.properties['user.home']}/.android/debug.keystore")
storePassword 'android'
storeType "jks"
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable false
}
debug {
debuggable true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
kotlinOptions {
jvmTarget = '18'
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
namespace 'com.google.oboe.samples.soundboard'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.google.oboe.samples.soundboard.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.4.1)
### INCLUDE OBOE LIBRARY ###
# Set the path to the Oboe library directory
set (OBOE_DIR ../../../../..)
# Add the Oboe library as a subproject. Since Oboe is an out-of-tree source library we must also
# specify a binary directory
add_subdirectory(${OBOE_DIR} ./oboe-bin)
# Include the Oboe headers
include_directories(${OBOE_DIR}/include ${OBOE_DIR}/samples/shared ${OBOE_DIR}/samples/debug-utils)
### END OBOE INCLUDE SECTION ###
add_library( soundboard SHARED
native-lib.cpp
SoundBoardEngine.cpp
)
target_link_libraries(soundboard log oboe )
target_link_options(soundboard PRIVATE "-Wl,-z,max-page-size=16384")
# Enable optimization flags: if having problems with source level debugging,
# disable -Ofast ( and debug ), re-enable it after done debugging.
target_compile_options(soundboard PRIVATE -Wall -Werror -Ofast)

View file

@ -0,0 +1,130 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include "SoundBoardEngine.h"
/**
* Main audio engine for the SoundBoard sample. It is responsible for:
*
* - Creating the callback object which will be supplied when constructing the audio stream
* - Creating the playback stream, including setting the callback object
* - Creating `Synth` which will render the audio inside the callback
* - Starting the playback stream
* - Restarting the playback stream when `restart()` is called by the callback object
*
* @param numSignals
*/
SoundBoardEngine::SoundBoardEngine(int32_t numSignals) {
createCallback(numSignals);
}
SoundBoardEngine::~SoundBoardEngine() {
if (mStream) {
LOGE("SoundBoardEngine destructor was called without calling stop()."
"Please call stop() to ensure stream resources are not leaked.");
stop();
}
}
void SoundBoardEngine::noteOff(int32_t noteIndex) {
mSynth->noteOff(noteIndex);
}
void SoundBoardEngine::noteOn(int32_t noteIndex) {
mSynth->noteOn(noteIndex);
}
void SoundBoardEngine::tap(bool isDown) {
mSynth->tap(isDown);
}
void SoundBoardEngine::restart() {
stop();
start();
}
// Create the playback stream
oboe::Result SoundBoardEngine::createPlaybackStream() {
oboe::AudioStreamBuilder builder;
return builder.setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setFormat(oboe::AudioFormat::Float)
->setDataCallback(mDataCallback)
->setErrorCallback(mErrorCallback)
->openStream(mStream);
}
// Create the callback and set its thread affinity to the supplied CPU core IDs
void SoundBoardEngine::createCallback(int32_t numSignals){
mDataCallback = std::make_shared<DefaultDataCallback>();
// Create the error callback, we supply ourselves as the parent so that we can restart the stream
// when it's disconnected
mErrorCallback = std::make_shared<DefaultErrorCallback>(*this);
mNumSignals = numSignals;
}
bool SoundBoardEngine::start() {
// It is possible for a stream's device to become disconnected during stream open or between
// stream open and stream start.
// If the stream fails to start, close the old stream and try again.
bool didStart = false;
int tryCount = 0;
do {
if (tryCount > 0) {
usleep(20 * 1000); // Sleep between tries to give the system time to settle.
}
didStart = attemptStart();
} while (!didStart && tryCount++ < 3);
if (!didStart) {
LOGE("Failed at starting the stream");
}
return didStart;
}
bool SoundBoardEngine::attemptStart() {
auto result = createPlaybackStream();
if (result == Result::OK) {
// Create our synthesizer audio source using the properties of the stream
mSynth = Synth::create(mStream->getSampleRate(), mStream->getChannelCount(), mNumSignals);
mDataCallback->reset();
mDataCallback->setSource(std::dynamic_pointer_cast<IRenderableAudio>(mSynth));
result = mStream->start();
if (result == Result::OK) {
return true;
} else {
LOGW("Failed attempt at starting the playback stream. Error: %s", convertToText(result));
return false;
}
} else {
LOGW("Failed attempt at creating the playback stream. Error: %s", convertToText(result));
return false;
}
}
bool SoundBoardEngine::stop() {
if(mStream && mStream->getState() != oboe::StreamState::Closed) {
mStream->stop();
mStream->close();
}
mStream.reset();
return true;
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SOUNDBOARD_ENGINE_H
#define SOUNDBOARD_ENGINE_H
#include <oboe/Oboe.h>
#include <vector>
#include "Synth.h"
#include <DefaultDataCallback.h>
#include <TappableAudioSource.h>
#include <IRestartable.h>
#include <DefaultErrorCallback.h>
using namespace oboe;
class SoundBoardEngine : public IRestartable {
public:
SoundBoardEngine(int32_t numSignals);
virtual ~SoundBoardEngine();
void noteOff(int32_t noteIndex);
void noteOn(int32_t noteIndex);
void tap(bool isDown);
// from IRestartable
virtual void restart() override;
bool start();
bool stop();
private:
int32_t mNumSignals;
std::shared_ptr<AudioStream> mStream;
std::shared_ptr<Synth> mSynth;
std::shared_ptr<DefaultDataCallback> mDataCallback;
std::shared_ptr<DefaultErrorCallback> mErrorCallback;
bool attemptStart();
oboe::Result createPlaybackStream();
void createCallback(int32_t numSignals);
};
#endif //SOUNDBOARD_ENGINE_H

View file

@ -0,0 +1,94 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SOUNDBOARD_SYNTH_H
#define SOUNDBOARD_SYNTH_H
#include <array>
#include <TappableAudioSource.h>
#include <SynthSound.h>
#include <Mixer.h>
#include <MonoToStereo.h>
constexpr float kOscBaseFrequency = 196.00; // Start at G3
constexpr float kOscFrequencyMultiplier = 1.05946309436;
constexpr float kOscBaseAmplitude = 0.20;
constexpr float kOscAmplitudeMultiplier = 0.96;
class Synth : public IRenderableAudio, public ITappable {
public:
static ::std::shared_ptr<Synth> create(const int32_t sampleRate, const int32_t channelCount, const int32_t numSignals) {
return ::std::make_shared<Synth>(sampleRate, channelCount, numSignals);
}
Synth(const int32_t sampleRate, const int32_t channelCount, const int32_t numSignals) {
mNumSignals = numSignals;
mOscs = std::make_unique<SynthSound[]>(numSignals);
float curFrequency = kOscBaseFrequency;
float curAmplitude = kOscBaseAmplitude;
for (int i = 0; i < numSignals; ++i) {
mOscs[i].setSampleRate(sampleRate);
mOscs[i].setFrequency(curFrequency);
curFrequency *= kOscFrequencyMultiplier;
mOscs[i].setAmplitude(curAmplitude);
curAmplitude *= kOscAmplitudeMultiplier;
mMixer.addTrack(&mOscs[i]);
}
if (channelCount == oboe::ChannelCount::Stereo) {
mOutputStage = &mConverter;
} else {
mOutputStage = &mMixer;
}
}
void noteOff(int32_t noteIndex) {
mOscs[noteIndex].noteOff();
}
void noteOn(int32_t noteIndex) {
mOscs[noteIndex].noteOn();
}
void tap(bool isOn) override {
for (int i = 0; i < mNumSignals; i++) {
if (isOn) {
mOscs[i].noteOn();
} else {
mOscs[i].noteOff();
}
}
};
// From IRenderableAudio
void renderAudio(float *audioData, int32_t numFrames) override {
mOutputStage->renderAudio(audioData, numFrames);
};
virtual ~Synth() {
}
private:
// Rendering objects
int32_t mNumSignals;
std::unique_ptr<SynthSound[]> mOscs;
Mixer mMixer;
MonoToStereo mConverter = MonoToStereo(&mMixer);
IRenderableAudio *mOutputStage; // This will point to either the mixer or converter, so it needs to be raw
};
#endif //SOUNDBOARD_SYNTH_H

View file

@ -0,0 +1,91 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <string>
#include <vector>
#include "SoundBoardEngine.h"
extern "C" {
/**
* Start the audio engine
*
* @param env
* @param instance
* @param jCpuIds - CPU core IDs which the audio process should affine to
* @return a pointer to the audio engine. This should be passed to other methods
*/
JNIEXPORT jlong JNICALL
Java_com_google_oboe_samples_soundboard_MainActivity_startEngine(JNIEnv *env, jobject /*unused*/,
jint jNumSignals) {
LOGD("numSignals : %d", static_cast<int>(jNumSignals));
SoundBoardEngine *engine = new SoundBoardEngine(jNumSignals);
if (!engine->start()) {
LOGE("Failed to start SoundBoard Engine");
delete engine;
engine = nullptr;
} else {
LOGD("Engine Started");
}
return reinterpret_cast<jlong>(engine);
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_soundboard_MainActivity_stopEngine(JNIEnv *env, jobject instance,
jlong jEngineHandle) {
auto engine = reinterpret_cast<SoundBoardEngine*>(jEngineHandle);
if (engine) {
engine->stop();
delete engine;
} else {
LOGD("Engine invalid, call startEngine() to create");
}
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_soundboard_MainActivity_native_1setDefaultStreamValues(JNIEnv *env,
jclass type,
jint sampleRate,
jint framesPerBurst) {
oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate;
oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst;
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_soundboard_NoteListener_noteOff(JNIEnv *env, jobject thiz,
jlong engine_handle, jint noteIndex) {
auto *engine = reinterpret_cast<SoundBoardEngine*>(engine_handle);
if (engine) {
engine->noteOff(noteIndex);
} else {
LOGE("Engine handle is invalid, call createEngine() to create a new one");
}
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_soundboard_NoteListener_noteOn(JNIEnv *env, jobject thiz,
jlong engine_handle, jint noteIndex) {
auto *engine = reinterpret_cast<SoundBoardEngine*>(engine_handle);
if (engine) {
engine->noteOn(noteIndex);
} else {
LOGE("Engine handle is invalid, call createEngine() to create a new one");
}
}
} // extern "C"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -0,0 +1,152 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.oboe.samples.soundboard
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.graphics.Point
import android.graphics.Rect
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.view.WindowInsets
import android.view.WindowManager
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import kotlin.math.min
class MainActivity : AppCompatActivity() {
private external fun startEngine(numSignals: Int): Long
private external fun stopEngine(engineHandle: Long)
private external fun native_setDefaultStreamValues(sampleRate: Int, framesPerBurst: Int)
companion object {
private const val DIMENSION_MIN_SIZE = 6
private const val DIMENSION_MAX_SIZE = 8
private var mNumColumns : Int = 0;
private var mNumRows : Int = 0;
private var mTiles = ArrayList<Rect>()
private var mBorders = ArrayList<Rect>()
private var mEngineHandle: Long = 0
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("soundboard")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
setup()
}
override fun onPause() {
stopEngine(mEngineHandle)
super.onPause()
}
private fun setup() {
setDefaultStreamValues(this)
calculateAndSetRectangles(this)
mEngineHandle = startEngine(mNumRows * mNumColumns)
createMusicTiles(this)
}
private fun setDefaultStreamValues(context: Context) {
val myAudioMgr = context.getSystemService(AUDIO_SERVICE) as AudioManager
val sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
val defaultSampleRate = sampleRateStr.toInt()
val framesPerBurstStr =
myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
val defaultFramesPerBurst = framesPerBurstStr.toInt()
native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst)
}
private fun calculateAndSetRectangles(context: Context) {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = windowManager.defaultDisplay
val size = Point()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
val windowInsets = windowMetrics.windowInsets
val insets = windowInsets.getInsetsIgnoringVisibility(
WindowInsets.Type.navigationBars() or WindowInsets.Type.statusBars())
val width = windowMetrics.bounds.width() - insets.left - insets.right
val height = windowMetrics.bounds.height() - insets.top - insets.bottom
size.set(width, height)
} else {
display.getSize(size) // Use getSize to exclude navigation bar if visible
}
val width = size.x
val height = size.y
if (height > width) {
mNumColumns = DIMENSION_MIN_SIZE
mNumRows = min(DIMENSION_MIN_SIZE * height / width, DIMENSION_MAX_SIZE)
} else {
mNumRows = DIMENSION_MIN_SIZE
mNumColumns = min(DIMENSION_MIN_SIZE * width / height, DIMENSION_MAX_SIZE)
}
val tileLength = min(height / mNumRows, width / mNumColumns)
val xStartLocation = (width - tileLength * mNumColumns) / 2
val yStartLocation = (height - tileLength * mNumRows) / 2
mTiles = ArrayList<Rect>()
for (i in 0 until mNumRows) {
for (j in 0 until mNumColumns) {
val rectangle = Rect(
xStartLocation + j * tileLength,
yStartLocation + i * tileLength,
xStartLocation + j * tileLength + tileLength,
yStartLocation + i * tileLength + tileLength
)
mTiles.add(rectangle)
}
}
mBorders = ArrayList<Rect>()
// Top border
mBorders.add(Rect(0, 0, width, yStartLocation))
// Bottom border
mBorders.add(Rect(0, yStartLocation + tileLength * mNumRows, width, height))
// Left border
mBorders.add(Rect(0, 0, xStartLocation, height))
// Right border
mBorders.add(Rect(xStartLocation + tileLength * mNumColumns, 0, width, height))
}
private fun createMusicTiles(context: Context) {
setContentView(MusicTileView(this, mTiles, mBorders, NoteListener(mEngineHandle),
ScreenChangeListener { setup() }))
}
class ScreenChangeListener(private var mFunc: () -> Unit) : MusicTileView.ConfigChangeListener {
override fun onConfigurationChanged() {
mFunc()
}
}
}

View file

@ -0,0 +1,171 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.oboe.samples.soundboard
import android.content.Context
import android.content.res.Configuration
import android.graphics.*
import android.util.SparseArray
import android.view.MotionEvent
import android.view.View
import androidx.core.content.ContextCompat
class MusicTileView(
context: Context?,
private val mTiles: ArrayList<Rect>,
private val mBorders: ArrayList<Rect>,
tileListener: TileListener,
configChangeListener: ConfigChangeListener
) : View(context) {
private val mIsPressedPerRectangle: BooleanArray = BooleanArray(mTiles.size)
private val mPaint: Paint = Paint()
private val mLocationsOfFingers: SparseArray<PointF> = SparseArray()
private val mTileListener: TileListener
private val mConfigChangeListener : ConfigChangeListener
interface ConfigChangeListener {
fun onConfigurationChanged()
}
interface TileListener {
fun onTileOn(index: Int)
fun onTileOff(index: Int)
}
private fun getIndexFromLocation(pointF: PointF): Int {
for (i in mTiles.indices) {
if (pointF.x > mTiles[i].left && pointF.x < mTiles[i].right && pointF.y > mTiles[i].top && pointF.y < mTiles[i].bottom) {
return i
}
}
return -1
}
override fun onDraw(canvas: Canvas) {
for (i in mTiles.indices) {
mPaint.style = Paint.Style.FILL
if (mIsPressedPerRectangle[i]) {
mPaint.color = ContextCompat.getColor(context, R.color.colorPrimary)
} else {
mPaint.color = Color.BLACK
}
canvas.drawRect(mTiles[i], mPaint)
// white border
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = 10f
mPaint.color = Color.WHITE
canvas.drawRect(mTiles[i], mPaint)
}
for (i in mBorders.indices) {
mPaint.style = Paint.Style.FILL
mPaint.color = ContextCompat.getColor(context, R.color.colorPrimaryDark)
canvas.drawRect(mBorders[i], mPaint)
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
val maskedAction = event.actionMasked
var didImageChange = false
when (maskedAction) {
MotionEvent.ACTION_MOVE -> {
// Create an array to check for finger changes as multiple fingers may be on the
// same tile. This two-pass algorithm records the overall difference before changing
// the actual tiles.
val notesChangedBy = IntArray(mTiles.size)
run {
val size = event.pointerCount
var i = 0
while (i < size) {
val point = mLocationsOfFingers[event.getPointerId(i)]
if (point != null) {
val prevIndex = getIndexFromLocation(point)
point.x = event.getX(i)
point.y = event.getY(i)
val newIndex = getIndexFromLocation(point)
if (newIndex != prevIndex) {
if (prevIndex != -1) {
notesChangedBy[prevIndex]--
}
if (newIndex != -1) {
notesChangedBy[newIndex]++
}
}
}
i++
}
}
// Now go through the rectangles to see if they have changed
var i = 0
while (i < mTiles.size) {
if (notesChangedBy[i] > 0) {
mIsPressedPerRectangle[i] = true
mTileListener.onTileOn(i)
didImageChange = true
} else if (notesChangedBy[i] < 0) {
mIsPressedPerRectangle[i] = false
mTileListener.onTileOff(i)
didImageChange = true
}
i++
}
}
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
val f = PointF()
f.x = event.getX(pointerIndex)
f.y = event.getY(pointerIndex)
mLocationsOfFingers.put(pointerId, f)
val curIndex = getIndexFromLocation(f)
if (curIndex != -1) {
mIsPressedPerRectangle[curIndex] = true
mTileListener.onTileOn(curIndex)
didImageChange = true
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> {
val curIndex =
getIndexFromLocation(mLocationsOfFingers[event.getPointerId(pointerIndex)])
if (curIndex != -1) {
mIsPressedPerRectangle[curIndex] = false
mTileListener.onTileOff(curIndex)
didImageChange = true
}
mLocationsOfFingers.remove(pointerId)
}
}
// Calling invalidate() will force onDraw() to be called
if (didImageChange) {
invalidate()
}
return true
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mConfigChangeListener.onConfigurationChanged()
}
init {
mTileListener = tileListener
mConfigChangeListener = configChangeListener
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.oboe.samples.soundboard
import com.google.oboe.samples.soundboard.MusicTileView.TileListener
class NoteListener(private var mEngineHandle: Long) : TileListener {
private external fun noteOn(engineHandle: Long, noteIndex: Int)
private external fun noteOff(engineHandle: Long, noteIndex: Int)
override fun onTileOn(index: Int) {
noteOn(mEngineHandle, index)
}
override fun onTileOff(index: Int) {
noteOff(mEngineHandle, index)
}
}

View file

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#700000</color>
<color name="colorPrimaryDark">#280000</color>
<color name="colorAccent">#D81B60</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">soundboard</string>
</resources>

View file

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>