mirror of
https://github.com/Keychron/qmk_firmware.git
synced 2024-11-24 17:37:40 +06:00
[Audio] Add support for audio shutdown pin (#22731)
Co-authored-by: Ryan <fauxpark@gmail.com>
This commit is contained in:
parent
045e5c9729
commit
83e6ddbbb4
|
@ -19,6 +19,8 @@
|
||||||
// Audio
|
// Audio
|
||||||
"AUDIO_DEFAULT_ON": {"info_key": "audio.default.on", "value_type": "bool"},
|
"AUDIO_DEFAULT_ON": {"info_key": "audio.default.on", "value_type": "bool"},
|
||||||
"AUDIO_DEFAULT_CLICKY_ON": {"info_key": "audio.default.clicky", "value_type": "bool"},
|
"AUDIO_DEFAULT_CLICKY_ON": {"info_key": "audio.default.clicky", "value_type": "bool"},
|
||||||
|
"AUDIO_POWER_CONTROL_PIN": {"info_key": "audio.power_control.pin"},
|
||||||
|
"AUDIO_POWER_CONTROL_PIN_ON_STATE": {"info_key": "audio.power_control.on_state", "value_type": "int" },
|
||||||
"AUDIO_VOICES": {"info_key": "audio.voices", "value_type": "flag"},
|
"AUDIO_VOICES": {"info_key": "audio.voices", "value_type": "flag"},
|
||||||
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "flag"},
|
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "flag"},
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,14 @@
|
||||||
},
|
},
|
||||||
"macro_beep": {"type": "boolean"},
|
"macro_beep": {"type": "boolean"},
|
||||||
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
|
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
|
||||||
|
"power_control": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"on_state": {"$ref": "qmk.definitions.v1#/bit"},
|
||||||
|
"pin": {"$ref": "qmk.definitions.v1#/mcu_pin"}
|
||||||
|
}
|
||||||
|
},
|
||||||
"voices": {"type": "boolean"}
|
"voices": {"type": "boolean"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -172,12 +172,14 @@ The available keycodes for audio are:
|
||||||
## Audio Config
|
## Audio Config
|
||||||
|
|
||||||
| Settings | Default | Description |
|
| Settings | Default | Description |
|
||||||
|---------------------------------|----------------------|-------------------------------------------------------------------------------|
|
|----------------------------------|----------------------|---------------------------------------------------------------------------------------------|
|
||||||
|`AUDIO_PIN` | *Not defined* |Configures the pin that the speaker is connected to. |
|
|`AUDIO_PIN` | *Not defined* |Configures the pin that the speaker is connected to. |
|
||||||
|`AUDIO_PIN_ALT` | *Not defined* |Configures the pin for a second speaker or second pin connected to one speaker.|
|
|`AUDIO_PIN_ALT` | *Not defined* |Configures the pin for a second speaker or second pin connected to one speaker. |
|
||||||
|`AUDIO_PIN_ALT_AS_NEGATIVE` | *Not defined* |Enables support for one speaker connected to two pins. |
|
|`AUDIO_PIN_ALT_AS_NEGATIVE` | *Not defined* |Enables support for one speaker connected to two pins. |
|
||||||
|`AUDIO_INIT_DELAY` | *Not defined* |Enables delay during startup song to accomidate for USB startup issues. |
|
|`AUDIO_INIT_DELAY` | *Not defined* |Enables delay during startup song to accomidate for USB startup issues. |
|
||||||
|`AUDIO_ENABLE_TONE_MULTIPLEXING` | *Not defined* |Enables time splicing/multiplexing to create multiple tones simutaneously. |
|
|`AUDIO_ENABLE_TONE_MULTIPLEXING` | *Not defined* |Enables time splicing/multiplexing to create multiple tones simutaneously. |
|
||||||
|
|`AUDIO_POWER_CONTROL_PIN` | *Not defined* |Enables power control code to enable or cut off power to speaker (such as with PAM8302 amp). |
|
||||||
|
|`AUDIO_POWER_CONTROL_PIN_ON_STATE`| `1` |The state of the audio power control pin when audio is "on" - `1` for high, `0` for low. |
|
||||||
|`STARTUP_SONG` | `STARTUP_SOUND` |Plays when the keyboard starts up (audio.c) |
|
|`STARTUP_SONG` | `STARTUP_SOUND` |Plays when the keyboard starts up (audio.c) |
|
||||||
|`GOODBYE_SONG` | `GOODBYE_SOUND` |Plays when you press the QK_BOOT key (quantum.c) |
|
|`GOODBYE_SONG` | `GOODBYE_SOUND` |Plays when you press the QK_BOOT key (quantum.c) |
|
||||||
|`AG_NORM_SONG` | `AG_NORM_SOUND` |Plays when you press AG_NORM (process_magic.c) |
|
|`AG_NORM_SONG` | `AG_NORM_SOUND` |Plays when you press AG_NORM (process_magic.c) |
|
||||||
|
@ -192,7 +194,7 @@ The available keycodes for audio are:
|
||||||
|`GUITAR_SONG` | `GUITAR_SOUND` |Plays when the guitar music mode is selected (process_music.c) |
|
|`GUITAR_SONG` | `GUITAR_SOUND` |Plays when the guitar music mode is selected (process_music.c) |
|
||||||
|`VIOLIN_SONG` | `VIOLIN_SOUND` |Plays when the violin music mode is selected (process_music.c) |
|
|`VIOLIN_SONG` | `VIOLIN_SOUND` |Plays when the violin music mode is selected (process_music.c) |
|
||||||
|`MAJOR_SONG` | `MAJOR_SOUND` |Plays when the major music mode is selected (process_music.c) |
|
|`MAJOR_SONG` | `MAJOR_SOUND` |Plays when the major music mode is selected (process_music.c) |
|
||||||
|`DEFAULT_LAYER_SONGS` | *Not defined* |Plays song when switched default layers with [`set_single_persistent_default_layer(layer)`](ref_functions.md#setting-the-persistent-default-layer)(quantum.c) |
|
|`DEFAULT_LAYER_SONGS` | *Not defined* |Plays song when switched default layers with [`set_single_persistent_default_layer(layer)`](ref_functions.md#setting-the-persistent-default-layer)(quantum.c). |
|
||||||
|`SENDSTRING_BELL` | *Not defined* |Plays chime when the "enter" ("\a") character is sent (send_string.c) |
|
|`SENDSTRING_BELL` | *Not defined* |Plays chime when the "enter" ("\a") character is sent (send_string.c) |
|
||||||
|
|
||||||
## Tempo
|
## Tempo
|
||||||
|
|
|
@ -123,10 +123,17 @@ Configures the [Audio](feature_audio.md) feature.
|
||||||
* Default: `false`
|
* Default: `false`
|
||||||
* `pins` (Required)
|
* `pins` (Required)
|
||||||
* The GPIO pin(s) connected to the speaker(s).
|
* The GPIO pin(s) connected to the speaker(s).
|
||||||
|
* `power_control`
|
||||||
|
* `on_state`
|
||||||
|
* The logical GPIO state required to turn the speaker on.
|
||||||
|
* Default: `1` (on = high)
|
||||||
|
* `pin`
|
||||||
|
* The GPIO pin connected to speaker power circuit.
|
||||||
* `voices`
|
* `voices`
|
||||||
* Use multiple audio voices.
|
* Use multiple audio voices.
|
||||||
* Default: `false`
|
* Default: `false`
|
||||||
|
|
||||||
|
|
||||||
## Backlight :id=backlight
|
## Backlight :id=backlight
|
||||||
|
|
||||||
Configures the [Backlight](feature_backlight.md) feature.
|
Configures the [Backlight](feature_backlight.md) feature.
|
||||||
|
|
|
@ -48,5 +48,3 @@
|
||||||
#define AUDIO_PWM_CHANNEL RP2040_PWM_CHANNEL_A
|
#define AUDIO_PWM_CHANNEL RP2040_PWM_CHANNEL_A
|
||||||
#define AUDIO_INIT_DELAY
|
#define AUDIO_INIT_DELAY
|
||||||
#define AUDIO_CLICKY
|
#define AUDIO_CLICKY
|
||||||
|
|
||||||
#define SPEAKER_SHUTDOWN GP14
|
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
"pid": "0x0108",
|
"pid": "0x0108",
|
||||||
"device_version": "0.0.1"
|
"device_version": "0.0.1"
|
||||||
},
|
},
|
||||||
|
"audio": {
|
||||||
|
"power_control": {
|
||||||
|
"pin": "GP14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"encoder": {
|
"encoder": {
|
||||||
"rotary": [
|
"rotary": [
|
||||||
{"pin_a": "GP18", "pin_b": "GP17"}
|
{"pin_a": "GP18", "pin_b": "GP17"}
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
/* Copyright 2022 Jose Pablo Ramirez <jp.ramangulo@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "quantum.h"
|
|
||||||
|
|
||||||
#ifdef AUDIO_ENABLE
|
|
||||||
void keyboard_pre_init_kb(void) {
|
|
||||||
// ensure pin is set and enabled pre-audio init
|
|
||||||
setPinOutput(SPEAKER_SHUTDOWN);
|
|
||||||
writePinHigh(SPEAKER_SHUTDOWN);
|
|
||||||
keyboard_pre_init_user();
|
|
||||||
}
|
|
||||||
|
|
||||||
void keyboard_post_init_kb(void) {
|
|
||||||
// set pin based on active status
|
|
||||||
writePin(SPEAKER_SHUTDOWN, audio_is_on());
|
|
||||||
keyboard_post_init_user();
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_on_user(void) {
|
|
||||||
writePinHigh(SPEAKER_SHUTDOWN);
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_off_user(void) {
|
|
||||||
// needs a delay or it runs right after play note.
|
|
||||||
wait_ms(200);
|
|
||||||
writePinLow(SPEAKER_SHUTDOWN);
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -213,7 +213,7 @@ void channel_2_stop(void) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void audio_driver_initialize(void) {
|
void audio_driver_initialize_impl(void) {
|
||||||
#ifdef AUDIO1_PIN_SET
|
#ifdef AUDIO1_PIN_SET
|
||||||
channel_1_stop();
|
channel_1_stop();
|
||||||
gpio_set_pin_output(AUDIO1_PIN);
|
gpio_set_pin_output(AUDIO1_PIN);
|
||||||
|
@ -254,7 +254,7 @@ void audio_driver_initialize(void) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_stop(void) {
|
void audio_driver_stop_impl(void) {
|
||||||
#ifdef AUDIO1_PIN_SET
|
#ifdef AUDIO1_PIN_SET
|
||||||
channel_1_stop();
|
channel_1_stop();
|
||||||
#endif
|
#endif
|
||||||
|
@ -264,7 +264,7 @@ void audio_driver_stop(void) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_start(void) {
|
void audio_driver_start_impl(void) {
|
||||||
#ifdef AUDIO1_PIN_SET
|
#ifdef AUDIO1_PIN_SET
|
||||||
channel_1_start();
|
channel_1_start();
|
||||||
if (playing_note) {
|
if (playing_note) {
|
||||||
|
|
|
@ -303,7 +303,7 @@ static const DACConfig dac_conf = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_
|
||||||
*/
|
*/
|
||||||
static const DACConversionGroup dac_conv_cfg = {.num_channels = 1U, .end_cb = dac_end, .error_cb = dac_error, .trigger = DAC_TRG(0b000)};
|
static const DACConversionGroup dac_conv_cfg = {.num_channels = 1U, .end_cb = dac_end, .error_cb = dac_error, .trigger = DAC_TRG(0b000)};
|
||||||
|
|
||||||
void audio_driver_initialize(void) {
|
void audio_driver_initialize_impl(void) {
|
||||||
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
||||||
palSetLineMode(A4, PAL_MODE_INPUT_ANALOG);
|
palSetLineMode(A4, PAL_MODE_INPUT_ANALOG);
|
||||||
dacStart(&DACD1, &dac_conf);
|
dacStart(&DACD1, &dac_conf);
|
||||||
|
@ -350,11 +350,11 @@ void audio_driver_initialize(void) {
|
||||||
gptStart(&GPTD6, &gpt6cfg1);
|
gptStart(&GPTD6, &gpt6cfg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_stop(void) {
|
void audio_driver_stop_impl(void) {
|
||||||
state = OUTPUT_SHOULD_STOP;
|
state = OUTPUT_SHOULD_STOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_start(void) {
|
void audio_driver_start_impl(void) {
|
||||||
gptStartContinuous(&GPTD6, 2U);
|
gptStartContinuous(&GPTD6, 2U);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < AUDIO_MAX_SIMULTANEOUS_TONES; i++) {
|
for (uint8_t i = 0; i < AUDIO_MAX_SIMULTANEOUS_TONES; i++) {
|
||||||
|
|
|
@ -190,7 +190,7 @@ static void gpt_audio_state_cb(GPTDriver *gptp) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_initialize(void) {
|
void audio_driver_initialize_impl(void) {
|
||||||
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
||||||
palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG);
|
palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG);
|
||||||
dacStart(&DACD1, &dac_conf_ch1);
|
dacStart(&DACD1, &dac_conf_ch1);
|
||||||
|
@ -223,7 +223,7 @@ void audio_driver_initialize(void) {
|
||||||
gptStart(&AUDIO_STATE_TIMER, &gptStateUpdateCfg);
|
gptStart(&AUDIO_STATE_TIMER, &gptStateUpdateCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_stop(void) {
|
void audio_driver_stop_impl(void) {
|
||||||
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
||||||
gptStopTimer(&GPTD6);
|
gptStopTimer(&GPTD6);
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ void audio_driver_stop(void) {
|
||||||
gptStopTimer(&AUDIO_STATE_TIMER);
|
gptStopTimer(&AUDIO_STATE_TIMER);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_start(void) {
|
void audio_driver_start_impl(void) {
|
||||||
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) {
|
||||||
dacStartConversion(&DACD1, &dac_conv_grp_ch1, (dacsample_t *)dac_buffer_1, AUDIO_DAC_BUFFER_SIZE);
|
dacStartConversion(&DACD1, &dac_conv_grp_ch1, (dacsample_t *)dac_buffer_1, AUDIO_DAC_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ static void audio_callback(virtual_timer_t *vtp, void *p) {
|
||||||
chSysUnlockFromISR();
|
chSysUnlockFromISR();
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_initialize(void) {
|
void audio_driver_initialize_impl(void) {
|
||||||
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
|
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
|
||||||
|
|
||||||
// connect the AUDIO_PIN to the PWM hardware
|
// connect the AUDIO_PIN to the PWM hardware
|
||||||
|
@ -100,7 +100,7 @@ void audio_driver_initialize(void) {
|
||||||
chVTObjectInit(&audio_vt);
|
chVTObjectInit(&audio_vt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_start(void) {
|
void audio_driver_start_impl(void) {
|
||||||
channel_1_stop();
|
channel_1_stop();
|
||||||
channel_1_start();
|
channel_1_start();
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ void audio_driver_start(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_stop(void) {
|
void audio_driver_stop_impl(void) {
|
||||||
channel_1_stop();
|
channel_1_stop();
|
||||||
chVTReset(&audio_vt);
|
chVTReset(&audio_vt);
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ GPTConfig gptCFG = {
|
||||||
.callback = gpt_callback,
|
.callback = gpt_callback,
|
||||||
};
|
};
|
||||||
|
|
||||||
void audio_driver_initialize(void) {
|
void audio_driver_initialize_impl(void) {
|
||||||
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
|
pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
|
||||||
|
|
||||||
palSetLineMode(AUDIO_PIN, PAL_MODE_OUTPUT_PUSHPULL);
|
palSetLineMode(AUDIO_PIN, PAL_MODE_OUTPUT_PUSHPULL);
|
||||||
|
@ -138,7 +138,7 @@ void audio_driver_initialize(void) {
|
||||||
gptStart(&AUDIO_STATE_TIMER, &gptCFG);
|
gptStart(&AUDIO_STATE_TIMER, &gptCFG);
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_start(void) {
|
void audio_driver_start_impl(void) {
|
||||||
channel_1_stop();
|
channel_1_stop();
|
||||||
channel_1_start();
|
channel_1_start();
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ void audio_driver_start(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_driver_stop(void) {
|
void audio_driver_stop_impl(void) {
|
||||||
channel_1_stop();
|
channel_1_stop();
|
||||||
gptStopTimer(&AUDIO_STATE_TIMER);
|
gptStopTimer(&AUDIO_STATE_TIMER);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,6 @@
|
||||||
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
void audio_driver_initialize(void) {}
|
void audio_driver_initialize_impl(void) {}
|
||||||
void audio_driver_start() {}
|
void audio_driver_start_impl() {}
|
||||||
void audio_driver_stop() {}
|
void audio_driver_stop_impl() {}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "wait.h"
|
#include "wait.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "gpio.h"
|
||||||
|
|
||||||
/* audio system:
|
/* audio system:
|
||||||
*
|
*
|
||||||
|
@ -121,6 +122,32 @@ static bool audio_initialized = false;
|
||||||
static bool audio_driver_stopped = true;
|
static bool audio_driver_stopped = true;
|
||||||
audio_config_t audio_config;
|
audio_config_t audio_config;
|
||||||
|
|
||||||
|
#ifndef AUDIO_POWER_CONTROL_PIN_ON_STATE
|
||||||
|
# define AUDIO_POWER_CONTROL_PIN_ON_STATE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void audio_driver_initialize(void) {
|
||||||
|
#ifdef AUDIO_POWER_CONTROL_PIN
|
||||||
|
gpio_set_pin_output_push_pull(AUDIO_POWER_CONTROL_PIN);
|
||||||
|
gpio_write_pin(AUDIO_POWER_CONTROL_PIN, !AUDIO_POWER_CONTROL_PIN_ON_STATE);
|
||||||
|
#endif
|
||||||
|
audio_driver_initialize_impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_driver_stop(void) {
|
||||||
|
audio_driver_stop_impl();
|
||||||
|
#ifdef AUDIO_POWER_CONTROL_PIN
|
||||||
|
gpio_write_pin(AUDIO_POWER_CONTROL_PIN, !AUDIO_POWER_CONTROL_PIN_ON_STATE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_driver_start(void) {
|
||||||
|
#ifdef AUDIO_POWER_CONTROL_PIN
|
||||||
|
gpio_write_pin(AUDIO_POWER_CONTROL_PIN, AUDIO_POWER_CONTROL_PIN_ON_STATE);
|
||||||
|
#endif
|
||||||
|
audio_driver_start_impl();
|
||||||
|
}
|
||||||
|
|
||||||
void eeconfig_update_audio_current(void) {
|
void eeconfig_update_audio_current(void) {
|
||||||
eeconfig_update_audio(audio_config.raw);
|
eeconfig_update_audio(audio_config.raw);
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,9 +215,9 @@ void audio_startup(void);
|
||||||
// hardware interface
|
// hardware interface
|
||||||
|
|
||||||
// implementation in the driver_avr/arm_* respective parts
|
// implementation in the driver_avr/arm_* respective parts
|
||||||
void audio_driver_initialize(void);
|
void audio_driver_initialize_impl(void);
|
||||||
void audio_driver_start(void);
|
void audio_driver_start_impl(void);
|
||||||
void audio_driver_stop(void);
|
void audio_driver_stop_impl(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief get the number of currently active tones
|
* @brief get the number of currently active tones
|
||||||
|
|
Loading…
Reference in New Issue
Block a user