more android
This commit is contained in:
parent
9026609bea
commit
119c5cba9a
@ -57,10 +57,6 @@ public class MainActivity extends Activity {
|
|||||||
public void setParameter(int i, float v) {
|
public void setParameter(int i, float v) {
|
||||||
nativeSetParameter(i, v);
|
nativeSetParameter(i, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
|
||||||
public void dummyFunc() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -4,17 +4,30 @@
|
|||||||
#define NUM_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}}
|
#define NUM_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}}
|
||||||
#define NUM_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}}
|
#define NUM_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}}
|
||||||
|
|
||||||
|
#define NUM_MIDI_INPUTS {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}}
|
||||||
|
|
||||||
#define PARAMETERS_N {{=it.product.parameters.length}}
|
#define PARAMETERS_N {{=it.product.parameters.length}}
|
||||||
|
|
||||||
#if PARAMETERS_N > 0
|
#if PARAMETERS_N > 0
|
||||||
|
|
||||||
|
# define PARAM_BYPASS 1
|
||||||
|
# define PARAM_TOGGLED (1<<1)
|
||||||
|
# define PARAM_INTEGER (1<<2)
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
char out;
|
char out;
|
||||||
float def;
|
float def;
|
||||||
|
float min;
|
||||||
|
float max;
|
||||||
|
uint32_t flags;
|
||||||
} param_data[PARAMETERS_N] = {
|
} param_data[PARAMETERS_N] = {
|
||||||
{{~it.product.parameters :p}}
|
{{~it.product.parameters :p}}
|
||||||
{
|
{
|
||||||
/* .out = */ {{=p.direction == "output" ? 1 : 0}},
|
/* .out = */ {{=p.direction == "output" ? 1 : 0}},
|
||||||
/* .def = */ {{=p.defaultValue.toExponential()}}
|
/* .def = */ {{=p.defaultValue.toExponential()}},
|
||||||
|
/* .min = */ {{=p.minimum.toExponential()}}f,
|
||||||
|
/* .max = */ {{=p.maximum.toExponential()}}f,
|
||||||
|
/* .flags = */ {{?p.isBypass}}PARAM_BYPASS{{??p.isLatency}}PARAM_INTEGER{{??}}0{{?p.toggled}} | PARAM_TOGGLED{{?}}{{?p.integer}} | PARAM_INTEGER{{?}}{{?}}
|
||||||
},
|
},
|
||||||
{{~}}
|
{{~}}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,65 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||||
<title>{{=it.product.name}}</title>
|
<title>{{=it.product.name}}</title>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var data = {
|
||||||
|
buses: {{=JSON.stringify(it.product.buses, null, 2)}},
|
||||||
|
parameters: {{=JSON.stringify(it.product.parameters, null, 2)}}
|
||||||
|
};
|
||||||
|
|
||||||
|
var hasAudioPermission = true;
|
||||||
|
for (var i = 0; i < buses.length; i++)
|
||||||
|
if (!data.buses[i].output) {
|
||||||
|
hasAudioPermission = Android.hasAudioPermission();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var audioStarted = false;
|
||||||
|
var topButtonElem;
|
||||||
|
var outParamInterval;
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
topButtonElem = document.getElementById("topButton");
|
||||||
|
var paramsElem = document.getElementById("params");
|
||||||
|
|
||||||
|
topButtonElem.value = hasAudioPermission ? "START" : "INIT";
|
||||||
|
};
|
||||||
|
|
||||||
|
function gotAudioPermission() {
|
||||||
|
hasAudioPermission = true;
|
||||||
|
topButtonElem.value = "START";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topButton {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
background-color: #04aa6d;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
Prova
|
<input id="topButton" type="button">
|
||||||
|
<div id="params"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -114,118 +114,6 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL
|
|
||||||
Java_com_orastron_@JNI_NAME@_MainActivity_nativeAudioStart(JNIEnv* env, jobject thiz) {
|
|
||||||
(void)env;
|
|
||||||
(void)thiz;
|
|
||||||
|
|
||||||
#if NUM_CHANNELS_IN == 0
|
|
||||||
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
|
|
||||||
#else
|
|
||||||
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
|
||||||
#endif
|
|
||||||
deviceConfig.periodSizeInFrames = BLOCK_SIZE;
|
|
||||||
deviceConfig.periods = 1;
|
|
||||||
deviceConfig.performanceProfile = ma_performance_profile_low_latency;
|
|
||||||
deviceConfig.noPreSilencedOutputBuffer = 1;
|
|
||||||
deviceConfig.noClip = 0;
|
|
||||||
deviceConfig.noDisableDenormals = 0;
|
|
||||||
deviceConfig.noFixedSizedCallback = 1;
|
|
||||||
deviceConfig.dataCallback = data_callback;
|
|
||||||
deviceConfig.capture.pDeviceID = NULL;
|
|
||||||
deviceConfig.capture.format = ma_format_f32;
|
|
||||||
deviceConfig.capture.channels = NUM_CHANNELS_IN;
|
|
||||||
deviceConfig.capture.shareMode = ma_share_mode_shared;
|
|
||||||
deviceConfig.playback.pDeviceID = NULL;
|
|
||||||
deviceConfig.playback.format = ma_format_f32;
|
|
||||||
deviceConfig.playback.channels = NUM_CHANNELS_OUT;
|
|
||||||
deviceConfig.playback.shareMode = ma_share_mode_shared;
|
|
||||||
|
|
||||||
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
P_INIT(&instance);
|
|
||||||
P_SET_SAMPLE_RATE(&instance, (float)device.sampleRate);
|
|
||||||
#ifdef P_MEM_REQ
|
|
||||||
size_t req = P_MEM_REQ(&instance);
|
|
||||||
if (req) {
|
|
||||||
mem = malloc(req);
|
|
||||||
if (mem == NULL) {
|
|
||||||
ma_device_uninit(&device);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
P_MEM_SET(&instance, mem);
|
|
||||||
} else
|
|
||||||
mem = NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_PARAMETERS; i++) {
|
|
||||||
paramValues[i] = config_parameters[i].defaultValueUnmapped;
|
|
||||||
if (!config_parameters[i].out)
|
|
||||||
P_SET_PARAMETER(&instance, i, paramValues[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
P_RESET(&instance);
|
|
||||||
|
|
||||||
#if NUM_CHANNELS_IN != 0
|
|
||||||
for (int i = 0; i < NUM_CHANNELS_IN; i++)
|
|
||||||
inBufs[i] = bufs[i];
|
|
||||||
#endif
|
|
||||||
for (int i = 0; i < NUM_CHANNELS_OUT; i++)
|
|
||||||
outBufs[i] = bufs[i];
|
|
||||||
|
|
||||||
if (ma_device_start(&device) != MA_SUCCESS) {
|
|
||||||
#ifdef P_MEM_REQ
|
|
||||||
free(mem);
|
|
||||||
#endif
|
|
||||||
ma_device_uninit(&device);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_orastron_@JNI_NAME@_MainActivity_nativeAudioStop(JNIEnv* env, jobject thiz) {
|
|
||||||
(void)env;
|
|
||||||
(void)thiz;
|
|
||||||
|
|
||||||
#ifdef P_MEM_REQ
|
|
||||||
free(mem);
|
|
||||||
#endif
|
|
||||||
#ifdef P_FINI
|
|
||||||
P_FINI(&instance);
|
|
||||||
#endif
|
|
||||||
ma_device_stop(&device);
|
|
||||||
ma_device_uninit(&device);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jfloat JNICALL
|
|
||||||
Java_com_orastron_@JNI_NAME@_MainActivity_nativeGetParameter(JNIEnv* env, jobject thiz, jint i) {
|
|
||||||
(void)env;
|
|
||||||
(void)thiz;
|
|
||||||
|
|
||||||
mutex.lock();
|
|
||||||
float v = paramValues[i];
|
|
||||||
mutex.unlock();
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_com_orastron_@JNI_NAME@_MainActivity_nativeSetParameter(JNIEnv* env, jobject thiz, jint i, jfloat v) {
|
|
||||||
(void)env;
|
|
||||||
(void)thiz;
|
|
||||||
|
|
||||||
mutex.lock();
|
|
||||||
paramValues[i] = v;
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef P_NOTE_ON
|
#ifdef P_NOTE_ON
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
@ -270,6 +158,12 @@ Java_com_orastron_@JNI_NAME@_MainActivity_removeMidiPort(JNIEnv* env, jobject th
|
|||||||
#include "plugin.h"
|
#include "plugin.h"
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
#if PARAMETERS_N + NUM_MIDI_INPUTS > 0
|
||||||
|
# include <mutex>
|
||||||
|
#endif
|
||||||
|
#if PARAMETERS_N > 0
|
||||||
|
# include <algorithm>
|
||||||
|
#endif
|
||||||
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
|
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
|
||||||
# define MINIAUDIO_IMPLEMENTATION
|
# define MINIAUDIO_IMPLEMENTATION
|
||||||
# define MA_ENABLE_ONLY_SPECIFIC_BACKENDS
|
# define MA_ENABLE_ONLY_SPECIFIC_BACKENDS
|
||||||
@ -277,14 +171,71 @@ Java_com_orastron_@JNI_NAME@_MainActivity_removeMidiPort(JNIEnv* env, jobject th
|
|||||||
# include <miniaudio.h>
|
# include <miniaudio.h>
|
||||||
|
|
||||||
# define BLOCK_SIZE 32
|
# define BLOCK_SIZE 32
|
||||||
# define NUM_BUFS (NUM_CHANNELS_IN > NUM_CHANNELS_OUT ? NUM_CHANNELS_IN : NUM_CHANNELS_OUT)
|
#endif
|
||||||
|
|
||||||
|
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
|
||||||
static ma_device device;
|
static ma_device device;
|
||||||
#endif
|
#endif
|
||||||
static plugin instance;
|
static plugin instance;
|
||||||
static void * mem;
|
static void * mem;
|
||||||
|
#if NUM_CHANNELS_IN > 0
|
||||||
|
float x_buf[NUM_CHANNELS_IN * BLOCK_SIZE];
|
||||||
|
#endif
|
||||||
|
#if NUM_CHANNELS_OUT > 0
|
||||||
|
float y_buf[NUM_CHANNELS_OUT * BLOCK_SIZE];
|
||||||
|
#endif
|
||||||
|
const float ** x;
|
||||||
|
float ** y;
|
||||||
|
#if PARAMETERS_N > 0
|
||||||
|
std::mutex mutex;
|
||||||
|
float param_values[PARAMETERS_N];
|
||||||
|
float param_values_prev[PARAMETERS_N];
|
||||||
|
#endif
|
||||||
|
|
||||||
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) {
|
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) {
|
||||||
|
(void)pDevice;
|
||||||
|
|
||||||
|
#if PARAMETERS_N + NUM_MIDI_INPUTS > 0
|
||||||
|
if (mutex.try_lock()) {
|
||||||
|
# if PARAMETERS_N > 0
|
||||||
|
for (size_t i = 0; i < PARAMETERS_N; i++) {
|
||||||
|
if (param_data[i].out)
|
||||||
|
param_values_prev[i] = param_values[i] = plugin_get_parameter(&instance, i);
|
||||||
|
else if (param_values_prev[i] != param_values[i]) {
|
||||||
|
plugin_set_parameter(&instance, i, param_values[i]);
|
||||||
|
param_values_prev[i] = param_values[i];
|
||||||
|
}
|
||||||
|
// TODO: midi
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const float * in_buf = reinterpret_cast<const float *>(pInput);
|
||||||
|
float * out_buf = reinterpret_cast<float *>(pOutput);
|
||||||
|
ma_uint32 i = 0;
|
||||||
|
while (i < frameCount) {
|
||||||
|
ma_uint32 n = std::min(frameCount - i, static_cast<ma_uint32>(BLOCK_SIZE));
|
||||||
|
|
||||||
|
#if NUM_CHANNELS_IN > 0
|
||||||
|
size_t ix = NUM_CHANNELS_IN * i;
|
||||||
|
for (ma_uint32 j = 0; j < n; j++)
|
||||||
|
for (size_t k = 0; k < NUM_CHANNELS_IN; k++, ix++)
|
||||||
|
x_buf[BLOCK_SIZE * k + j] = in_buf[ix];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
plugin_process(&instance, x, y, n);
|
||||||
|
|
||||||
|
#if NUM_CHANNELS_OUT > 0
|
||||||
|
size_t iy = NUM_CHANNELS_OUT * i;
|
||||||
|
for (ma_uint32 j = 0; j < n; j++)
|
||||||
|
for (size_t k = 0; k < NUM_CHANNELS_OUT; k++, iy++)
|
||||||
|
y_buf[BLOCK_SIZE * k + j] = out_buf[ix];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
i += n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@ -325,9 +276,11 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) {
|
|||||||
plugin_init(&instance);
|
plugin_init(&instance);
|
||||||
|
|
||||||
#if PARAMETERS_N > 0
|
#if PARAMETERS_N > 0
|
||||||
for (size_t i = 0; i < PARAMETERS_N; i++)
|
for (size_t i = 0; i < PARAMETERS_N; i++) {
|
||||||
if (!param_data[i].out)
|
if (!param_data[i].out)
|
||||||
plugin_set_parameter(&instance, i, param_data[i].def);
|
plugin_set_parameter(&instance, i, param_data[i].def);
|
||||||
|
param_values_prev[i] = param_values[i] = param_data[i].def;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
plugin_set_sample_rate(&instance, (float)device.sampleRate);
|
plugin_set_sample_rate(&instance, (float)device.sampleRate);
|
||||||
@ -347,6 +300,28 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) {
|
|||||||
|
|
||||||
plugin_reset(&instance);
|
plugin_reset(&instance);
|
||||||
|
|
||||||
|
#if NUM_CHANNELS_IN > 0
|
||||||
|
for (size_t i = 0; i < NUM_CHANNELS_IN; i++)
|
||||||
|
x[i] = x_buf + BLOCK_SIZE * i;
|
||||||
|
#else
|
||||||
|
x = NULL;
|
||||||
|
#endif
|
||||||
|
#if NUM_CHANNELS_OUT > 0
|
||||||
|
for (size_t i = 0; i < NUM_CHANNELS_OUT; i++)
|
||||||
|
y[i] = y_buf + BLOCK_SIZE * i;
|
||||||
|
#else
|
||||||
|
y = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
|
||||||
|
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||||
|
if (mem != NULL)
|
||||||
|
free(mem);
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,8 +331,9 @@ JNI_FUNC(nativeAudioStop)(JNIEnv* env, jobject thiz) {
|
|||||||
(void)env;
|
(void)env;
|
||||||
(void)thiz;
|
(void)thiz;
|
||||||
|
|
||||||
|
if (mem != NULL)
|
||||||
|
free(mem);
|
||||||
plugin_fini(&instance);
|
plugin_fini(&instance);
|
||||||
|
|
||||||
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
|
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
|
||||||
ma_device_stop(&device);
|
ma_device_stop(&device);
|
||||||
ma_device_uninit(&device);
|
ma_device_uninit(&device);
|
||||||
@ -367,20 +343,51 @@ JNI_FUNC(nativeAudioStop)(JNIEnv* env, jobject thiz) {
|
|||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jfloat JNICALL
|
JNIEXPORT jfloat JNICALL
|
||||||
JNI_FUNC(nativeGetParameter)(JNIEnv* env, jobject thiz, jint i) {
|
JNI_FUNC(nativeGetParameter)(JNIEnv* env, jobject thiz, jint i) {
|
||||||
|
(void)env;
|
||||||
|
(void)thiz;
|
||||||
|
|
||||||
|
#if PARAMETERS_N > 0
|
||||||
|
mutex.lock();
|
||||||
|
float v = param_values[i];
|
||||||
|
mutex.unlock();
|
||||||
|
return v;
|
||||||
|
#else
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
JNI_FUNC(nativeSetParameter)(JNIEnv* env, jobject thiz, jint i, jfloat v) {
|
JNI_FUNC(nativeSetParameter)(JNIEnv* env, jobject thiz, jint i, jfloat v) {
|
||||||
|
(void)env;
|
||||||
|
(void)thiz;
|
||||||
|
|
||||||
|
if (param_data[i].flags & (PARAM_BYPASS | PARAM_TOGGLED))
|
||||||
|
v = v > 0.5f ? 1.f : 0.f;
|
||||||
|
else if (param_data[i].flags & PARAM_INTEGER)
|
||||||
|
v = (int32_t)(v + 0.5f);
|
||||||
|
v = std::min(std::max(v, param_data[i].min), param_data[i].max);
|
||||||
|
#if PARAMETERS_N > 0
|
||||||
|
mutex.lock();
|
||||||
|
param_values[i] = v;
|
||||||
|
mutex.unlock();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
JNI_FUNC(addMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) {
|
JNI_FUNC(addMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) {
|
||||||
|
(void)env;
|
||||||
|
(void)thiz;
|
||||||
|
|
||||||
|
//TBD
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
JNI_FUNC(removeMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) {
|
JNI_FUNC(removeMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) {
|
||||||
|
(void)env;
|
||||||
|
(void)thiz;
|
||||||
|
|
||||||
|
//TBD
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user