diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7a40905 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +# PLEASE, UPDATE THIS FILE WHEN ADDING ANY NEW C SOURCES + +set(COMPONENT_SRCS + # Cross-platform stuff + "soundMixer.cpp" + "soundProvider.cpp" + "soundProviderTask.cpp" + ) +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() + +#target_compile_features(${COMPONENT_TARGET} PUBLIC cxx_std_14) +#set_property(TARGET ${COMPONENT_TARGET} PROPERTY CXX_STANDARD 14) diff --git a/Kconfig b/Kconfig index 0310f9e..2647c99 100644 --- a/Kconfig +++ b/Kconfig @@ -1,19 +1,18 @@ menu "Sound module configuration" config SND_MAX_CHANNELS - int "Maximun count of mixed channels" + int "Maximal count of mixed channels" default 64 range 1 255 help Maximal count of channels to be mixed with SoundMixer. -config SND_CONTROL_QUEUE_SIZE - int "Size of mixer's queue" - default 128 - range 32 512 +config SND_USE_IRAM + bool "Move timer functions to IRAM" + default y help - In most cases default value will work fine. Try changing it in case of blocking. - Keep it bigger than SND_MAX_CHANNELS. + This generally allow extra optimisation. + If you need more space in IRAM, turn it off. menu "Provider configuration" diff --git a/LICENSE b/LICENSE index 261eeb9..c88d2ec 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. +MIT License + +Copyright (c) 2018 Valeri Ochinski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/component.mk b/component.mk index 0e1bdac..33aa4ce 100644 --- a/component.mk +++ b/component.mk @@ -1,2 +1,7 @@ COMPONENT_SRCDIRS:=. COMPONENT_ADD_INCLUDEDIRS:=. +CXXFLAGS += -std=c++14 + +ifeq ($(CONFIG_OPTIMIZATION_LEVEL_DEBUG),y) + CFLAGS += -fno-inline-functions +endif \ No newline at end of file diff --git a/sound.h b/sound.h index 36607ee..4b1ca3a 100644 --- a/sound.h +++ b/sound.h @@ -1,48 +1,4 @@ #pragma once -#include -#include -#include - -#include -#include -#include -#include - -#define SOUND_FREQ_TO_DELAY(f) (1000000/f) - -class SoundProvider; - -typedef unsigned int SoundChNum; -typedef uint8_t SoundData; -typedef long unsigned int SoundPos; -typedef unsigned int SoundVolume; - -enum SoundState { - STOPPED, - PLAYING, - PAUSED -}; - -enum SoundProviderControl { - END, - FAILURE -}; - -enum SoundEvent { - STOP, - START, - PAUSE, - RESUME, - VOLSET -}; - -struct SoundControl { - SoundEvent event; - SoundChNum channel; - SoundProvider *provider; - SoundVolume vol; -}; - #include #include diff --git a/soundDefines.h b/soundDefines.h new file mode 100644 index 0000000..1e2431f --- /dev/null +++ b/soundDefines.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +} + +#define USING_NS_SOUND using namespace Sound + +namespace Sound { + class SoundProvider; + class SoundMixer; + + using SoundChNum = unsigned int; + using SoundData = uint8_t; + using SoundPos = long unsigned int; + using SoundVolume = unsigned int; + + enum SoundState { + STOPPED, + PLAYING, + PAUSED + }; + + enum SoundProviderControl { + FREQUENCY_UPDATE, + END, + FAILURE + }; + + enum SoundEvent { + STOP, + START, + RESTART, + PAUSE, + RESUME, + VOLSET + }; + + struct SoundControl { + SoundEvent event; + SoundChNum channel; + std::shared_ptr provider; + SoundVolume vol; + }; +} + diff --git a/soundMixer.cpp b/soundMixer.cpp index 39f89bd..29864d0 100644 --- a/soundMixer.cpp +++ b/soundMixer.cpp @@ -1,297 +1,358 @@ -#include - -static int gcd(int a, int b) { - while(true) { - if (a == 0) return b; - b %= a; - if (b == 0) return a; - a %= b; +#include "soundMixer.h" +#include "soundProvider.h" + +#include + +#define SOUND_FREQ_TO_DELAY(f) (1000000/f) + +#if CONFIG_SND_USE_IRAM +#define TIMER_ATTRIBUTE IRAM_ATTR +#else +#define TIMER_ATTRIBUTE +#endif + +#ifdef CONFIG_OPTIMIZATION_LEVEL_RELEASE +#define forceinline inline __attribute__((always_inline)) +#else +#define forceinline // Turn off on debug builds to prevent debugger crash. +// Refer to https://github.com/espressif/esp-idf/issues/2343 +#endif + +extern "C" { + static int TIMER_ATTRIBUTE gcd(int a, int b) { + while(true) { + if (a == 0) return b; + b %= a; + if (b == 0) return a; + a %= b; + } } -} -static int lcm(int a, int b) { - int temp = gcd(a, b); + static int TIMER_ATTRIBUTE lcm(int a, int b) { + int temp = gcd(a, b); + + return temp ? (a / temp * b) : 0; + } - return temp ? (a / temp * b) : 0; + void TIMER_ATTRIBUTE sound_timer_callback_function(void* arg) { + static_cast(arg)->soundCallback(); + } } -bool SoundMixer::handleQueue() { - xSemaphoreTake(mutex, portMAX_DELAY); - SoundControl ctrl; - bool upd = false; // Should we recalculate anything? - while(xQueueReceive(queue, &ctrl, 0) == pdTRUE) { // Handle all events without blocking - SoundChNum channel = ctrl.channel; - SoundProvider *sound; - if (ctrl.event == START) { - sound = ctrl.provider; - } else { - sound = chSound[channel]; - } - SoundVolume vol = ctrl.vol; - switch(ctrl.event) { - case STOP: - if (chActive[channel]) {upd = true; decSound();} - if (chActive[channel] || chPaused[channel]) { +namespace Sound { + bool TIMER_ATTRIBUTE forceinline SoundMixer::handleQueue() { + SoundControl ctrl; + bool upd = false; // Should we recalculate anything? + std::lock_guard queueLock(queueMutex); + while(not queue.empty()) { // Handle all events without blocking + SoundControl ctrl = std::move(queue.front()); + queue.pop(); + SoundChNum& channel = ctrl.channel; + if (ctrl.event == START) { + chSound[channel] = std::move(ctrl.provider); + } + std::shared_ptr& sound = chSound[channel]; + switch(ctrl.event) { + case STOP: + if (chActive[channel]) { + upd = true; + decSound(); + } + if (chActive[channel] || chPaused[channel]) { chActive[channel] = false; chPaused[channel] = false; sound->provider_stop(); - } - break; - case START: // I'm sure that channel is free - upd = true; - incSound(); - chSound[channel] = sound; - chActive[channel] = true; - sound->provider_start(); - sound->actual = 0; - break; - case PAUSE: - if (chActive[channel]) { + chSound[channel] = nullptr; // Release pointer + } + break; + case START: // I'm sure that channel is free + upd = true; + incSound(); + chActive[channel] = true; + sound->provider_start(); + sound->actual = 0; + break; + case PAUSE: + if (chActive[channel]) { upd = true; decSound(); chActive[channel] = false; chPaused[channel] = true; sound->provider_pause(); - } - break; - case RESUME: - if (chPaused[channel]) { + } + break; + case RESTART: + if (chActive[channel]) { + sound->provider_restart(); + } + break; + case RESUME: + if (chPaused[channel]) { upd = true; incSound(); chActive[channel] = true; chPaused[channel] = false; sound->provider_resume(); - } - break; - case VOLSET: - chVolume[channel] = vol; - break; + } + break; + case VOLSET: + chVolume[channel] = ctrl.vol; + break; + } } + return upd; } - xSemaphoreGive(mutex); - return upd; -} -void SoundMixer::setupTimer() { - SoundChNum activeCount = uxSemaphoreGetCount(chActiveCount); - counterMax = 1; - if (activeCount == 1) { // Only one sound - for (SoundChNum i = 0; i < chCount; i++) { if (chActive[i]) { - chSound[i]->divisor = 1; - esp_timer_start_periodic(timer, SOUND_FREQ_TO_DELAY(chSound[i]->getFrequency())); - break; - }} - } else { - SoundChNum n = 0; - unsigned long int freqArr[activeCount]; - for (SoundChNum i = 0; i < chCount; i++) { if (chActive[i]) { - freqArr[n++] = chSound[i]->getFrequency(); - }} - - int freqLcm = std::accumulate(&(freqArr[1]), &(freqArr[activeCount]), freqArr[0], lcm); - for (SoundChNum i = 0; i < chCount; i++) { if (chActive[i]) { - SoundProvider *sound = chSound[i]; - sound->divisor = freqLcm / sound->getFrequency(); - counterMax = lcm(counterMax, sound->divisor); - }} - esp_timer_start_periodic(timer, SOUND_FREQ_TO_DELAY(freqLcm)); - } -} + void TIMER_ATTRIBUTE SoundMixer::setupTimer() { + counterMax = 1; + if (chActiveCount == 1) { // Only one sound + for (SoundChNum i = 0; i < chCount; ++i) { + if (chActive[i]) { + chSound[i]->divisor = 1; + esp_timer_start_periodic(timer, SOUND_FREQ_TO_DELAY(chSound[i]->getFrequency())); + break; + } + } + } else { + SoundChNum n = 0; + std::vector freqArr(chActiveCount); + for (SoundChNum i = 0; i < chCount; ++i) { + if (chActive[i]) { + freqArr[n++] = chSound[i]->getFrequency(); + } + } -void SoundMixer::soundCallback() { - bool upd = handleQueue(); - if (upd) { - esp_timer_stop(timer); // It will work OK anyway - if (uxSemaphoreGetCount(chActiveCount) == 0) { // If nothing to play - xSemaphoreGive(timerMutex); - return; + int freqLcm = std::accumulate(&(freqArr[1]), &(freqArr[chActiveCount]), freqArr[0], lcm); + for (SoundChNum i = 0; i < chCount; ++i) { + if (chActive[i]) { + std::shared_ptr sound = chSound[i]; + sound->divisor = freqLcm / sound->getFrequency(); + counterMax = lcm(counterMax, sound->divisor); + } + } + esp_timer_start_periodic(timer, SOUND_FREQ_TO_DELAY(freqLcm)); } - setupTimer(); // TODO: implement that function - counter = 0; // Only for later ++ } - counter++; - if (counter > counterMax) counter = 1; - - unsigned int out = 0; - for (SoundChNum i = 0; i < chCount; i++) { if (chActive[i]) { - SoundProvider *sound = chSound[i]; - if ((counter % sound->divisor) == 0) { - SoundData sample; - if (xQueueReceive(sound->queue, &sample, 0) == pdTRUE) { - sound->actual = sample; - } + void TIMER_ATTRIBUTE forceinline SoundMixer::soundCallback() { + std::unique_lock lock(mutex); + bool upd = handleQueue(); + if (upd) { + esp_timer_stop(timer); // It will work OK anyway + if (chActiveCount == 0) { // If nothing to play + timerActive = false; + dac_output_voltage(dacCh, 0); // Reduce energy usage + return; + } + setupTimer(); + counter = 0; // Only for later ++ } - out += sound->actual * chVolume[i]; - SoundProviderControl ctrl; - while(xQueueReceive(sound->controlQueue, &ctrl, 0) == pdTRUE) { - switch(ctrl) { - case END: - if (sound->repeat) { - sound->provider_restart(); - } else { - stop(i); + lock.unlock(); // We leave critical area + + ++counter; + if (counter > counterMax) counter = 1; + + unsigned int out = 0; + upd = false; + for (SoundChNum i = 0; i < chCount; ++i) { + if (chActive[i]) { + std::shared_ptr& sound = chSound[i]; + if ((counter % sound->divisor) == 0) { + SoundData sample; + if (xQueueReceive(sound->queue, &sample, 0) == pdTRUE) { + sound->actual = sample; } - break; - case FAILURE: - stop(i); - break; + } + out += sound->actual * chVolume[i]; + SoundProviderControl ctrl; + while(xQueueReceive(sound->controlQueue, &ctrl, 0) == pdTRUE) { + switch(ctrl) { + case FREQUENCY_UPDATE: + upd = true; + break; + case END: + if (sound->repeat) { + restart(i); + } else { + stop(i); + } + break; + case FAILURE: + stop(i); + break; + } + } } } - }} - out = out / 255 / chCount; - dac_output_voltage(dacCh, min(out, 255)); // Do NOT overload -} + out = out / 255 / chCount; + dac_output_voltage(dacCh, std::min(out, 255U)); // Do NOT overload -void SoundMixer::incSound() { - xSemaphoreGive(chActiveCount); -} + if (upd) { // If someone have changed frequency + lock.lock(); + esp_timer_stop(timer); + setupTimer(); + lock.unlock(); // If I'll add some code later + } + } -void SoundMixer::decSound() { - xSemaphoreTake(chActiveCount, portMAX_DELAY); -} + void TIMER_ATTRIBUTE forceinline SoundMixer::incSound() { + ++chActiveCount; + } -void SoundMixer::addEvent(SoundControl event) { - xQueueSendToBack(queue, &event, portMAX_DELAY); -} + void TIMER_ATTRIBUTE forceinline SoundMixer::decSound() { + --chActiveCount; + } -SoundMixer::SoundMixer(SoundChNum normal_channels, SoundChNum auto_channels, dac_channel_t dac) { - chCount = normal_channels + auto_channels; - chFirstAuto = normal_channels; // It isn't mistake, but looks strange - assert(chCount <= CONFIG_SND_MAX_CHANNELS); - dacCh = dac; + void TIMER_ATTRIBUTE SoundMixer::addEvent(const SoundControl& event) { + std::lock_guard queueLock(queueMutex); + queue.push(event); + } - dac_output_enable(dacCh); - esp_timer_create_args_t timer_args; + SoundMixer::SoundMixer(SoundChNum normal_channels, SoundChNum auto_channels, dac_channel_t dac) : + chCount(normal_channels + auto_channels), chFirstAuto(normal_channels) // It isn't mistake, but looks strange + { + assert(chCount <= CONFIG_SND_MAX_CHANNELS); + dacCh = dac; - timer_args.callback = reinterpret_cast(&SoundMixer::soundCallback); - timer_args.arg = this; - timer_args.dispatch_method = ESP_TIMER_TASK; - timer_args.name = "Sound timer"; + dac_output_enable(dacCh); + esp_timer_create_args_t timer_args; - esp_timer_create(&timer_args, &timer); + timer_args.callback = sound_timer_callback_function; + timer_args.arg = this; + timer_args.dispatch_method = ESP_TIMER_TASK; + timer_args.name = "Sound timer"; - mutex = xSemaphoreCreateMutex(); - timerMutex = xSemaphoreCreateCounting(1, 1); - chActiveCount = xSemaphoreCreateCounting(chCount, 0); - queue = xQueueCreate(CONFIG_SND_CONTROL_QUEUE_SIZE, sizeof(SoundControl)); + esp_timer_create(&timer_args, &timer); - for (SoundChNum i = 0; i < chCount; i++) { // Set defaults - chActive[i] = false; - chPaused[i] = false; - chVolume[i] = 255; - chSound[i] = NULL; + for (SoundChNum i = 0; i < chCount; ++i) { // Set defaults + chActive[i] = false; + chPaused[i] = false; + chVolume[i] = 255; + chSound[i] = nullptr; + } } -} - -SoundMixer::~SoundMixer() { - esp_timer_stop(timer); - esp_timer_delete(timer); - vSemaphoreDelete(mutex); - vSemaphoreDelete(timerMutex); - vSemaphoreDelete(chActiveCount); - vQueueDelete(queue); -} - -void SoundMixer::checkTimer() { - if (xSemaphoreTake(timerMutex, 0) == pdTRUE) { // If timer isn't active - esp_timer_start_once(timer, 0); // Activate one-shot handler + SoundMixer::~SoundMixer() { + esp_timer_stop(timer); + esp_timer_delete(timer); } -} -void SoundMixer::play(SoundChNum channel, SoundProvider *sound) { - stop(channel); + void SoundMixer::checkTimer() { + std::shared_lock lock(mutex); + if (not timerActive) { // If timer isn't active + timerActive = true; + esp_timer_start_once(timer, 0); // Activate one-shot handler + } + } - SoundControl ctrl; - ctrl.event = START; - ctrl.channel = channel; - ctrl.provider = sound; - addEvent(ctrl); + void SoundMixer::play(SoundChNum channel, const std::shared_ptr& sound) { + stop(channel); - checkTimer(); -} - -void SoundMixer::stop(SoundChNum channel) { - if (uxSemaphoreGetCount(timerMutex) == 0) { SoundControl ctrl; - ctrl.event = STOP; + ctrl.event = START; ctrl.channel = channel; + ctrl.provider = sound; // Copy addEvent(ctrl); + + checkTimer(); } -} -void SoundMixer::stopAll() { - for (SoundChNum i = 0; i < chCount; i++) { - stop(i); + void TIMER_ATTRIBUTE SoundMixer::stop(SoundChNum channel) { + std::shared_lock lock(mutex); + if (timerActive) { + lock.unlock(); + SoundControl ctrl; + ctrl.event = STOP; + ctrl.channel = channel; + addEvent(ctrl); + } } -} -void SoundMixer::pause(SoundChNum channel) { - if (uxSemaphoreGetCount(timerMutex) == 0) { - SoundControl ctrl; - ctrl.event = PAUSE; - ctrl.channel = channel; - addEvent(ctrl); + void SoundMixer::stopAll() { + for (SoundChNum i = 0; i < chCount; ++i) { + stop(i); + } } -} -void SoundMixer::pauseAll() { - for (SoundChNum i = 0; i < chCount; i++) { - pause(i); + void SoundMixer::pause(SoundChNum channel) { + std::shared_lock lock(mutex); + if (timerActive) { + lock.unlock(); + SoundControl ctrl; + ctrl.event = PAUSE; + ctrl.channel = channel; + addEvent(ctrl); + } } -} -void SoundMixer::resume(SoundChNum channel) { - SoundControl ctrl; - ctrl.event = RESUME; - ctrl.channel = channel; - addEvent(ctrl); + void SoundMixer::pauseAll() { + for (SoundChNum i = 0; i < chCount; ++i) { + pause(i); + } + } - checkTimer(); -} + void TIMER_ATTRIBUTE SoundMixer::restart(SoundChNum channel) { + std::shared_lock lock(mutex); + if (timerActive) { + SoundControl ctrl; + ctrl.event = RESTART; + ctrl.channel = channel; + addEvent(ctrl); + } + } + + void SoundMixer::restartAll() { + for (SoundChNum i = 0; i < chCount; ++i) { + restart(i); + } + } + void SoundMixer::resume(SoundChNum channel) { + SoundControl ctrl; + ctrl.event = RESUME; + ctrl.channel = channel; + addEvent(ctrl); -void SoundMixer::resumeAll() { - for (SoundChNum i = 0; i < chCount; i++) { - resume(i); + checkTimer(); } -} -void SoundMixer::setVolume(SoundChNum channel, SoundVolume vol) { - SoundControl ctrl; - ctrl.event = VOLSET; - ctrl.channel = channel; - ctrl.vol = vol; - addEvent(ctrl); // We don't call checkTimer because this event can be handled later -} + void SoundMixer::resumeAll() { + for (SoundChNum i = 0; i < chCount; ++i) { + resume(i); + } + } -SoundVolume SoundMixer::getVolume(SoundChNum channel) { - SoundVolume vol; - xSemaphoreTake(mutex, portMAX_DELAY); - vol = chVolume[channel]; - xSemaphoreGive(mutex); - return vol; -} + void SoundMixer::setVolume(SoundChNum channel, SoundVolume vol) { + SoundControl ctrl; + ctrl.event = VOLSET; + ctrl.channel = channel; + ctrl.vol = vol; + addEvent(ctrl); // We don't call checkTimer because this event can be handled later + } -SoundState SoundMixer::state(SoundChNum channel) { - xSemaphoreTake(mutex, portMAX_DELAY); - SoundState s; - if (chActive[channel]) s = PLAYING; - else if (chPaused[channel]) s = PAUSED; - else s = STOPPED; - xSemaphoreGive(mutex); - return s; -} + SoundVolume SoundMixer::getVolume(SoundChNum channel) { + SoundVolume vol; + std::shared_lock lock(mutex); + vol = chVolume[channel]; + return vol; + } -SoundChNum SoundMixer::playAuto(SoundProvider *sound, SoundVolume vol) { - for (SoundChNum i = chFirstAuto; i < chCount; i++) { - if (state(i) == STOPPED) { // We found free channel, setting up - setVolume(i, vol); - play(i, sound); - return i; + SoundState SoundMixer::state(SoundChNum channel) { + std::shared_lock lock(mutex); + if (chActive[channel]) return PLAYING; + else if (chPaused[channel]) return PAUSED; + else return STOPPED; + } + + SoundChNum SoundMixer::playAuto(const std::shared_ptr& sound, SoundVolume vol) { + for (SoundChNum i = chFirstAuto; i < chCount; ++i) { + if (state(i) == STOPPED) { // We found free channel, setting up + setVolume(i, vol); + play(i, sound); + return i; + } } + return chCount; // No free channels } - return chCount; // No free channels } diff --git a/soundMixer.h b/soundMixer.h index 9a85e51..31e5c7d 100644 --- a/soundMixer.h +++ b/soundMixer.h @@ -1,61 +1,70 @@ #pragma once +#include "soundDefines.h" +#include +#include +#include +#include +#include +#include -#ifndef min -#define min(a,b) ((a)<(b)?(a):(b)) -#endif +extern "C" void sound_timer_callback_function(void* arg); -#include -#include -#include +namespace Sound { + class SoundMixer { + protected: + esp_timer_handle_t timer = nullptr; // Timer for this instance + std::shared_timed_mutex mutex; // Mutex for this instance + std::atomic timerActive = {false}; // Is timer active? -class SoundProvider; + std::queue> queue; // Queue for this instance, not FreeRTOS due to smart pointers + // Refer to https://stackoverflow.com/q/51632219/5697743 -class SoundMixer { - protected: - esp_timer_handle_t timer = NULL; // Timer for this instance - SemaphoreHandle_t mutex = NULL; // Mutex for this instance - SemaphoreHandle_t timerMutex = NULL; // Mutex for timer control - QueueHandle_t queue = NULL; // Queue for this instance - dac_channel_t dacCh; // DAC channel for sound + std::mutex queueMutex; // Mutex for queue - unsigned long int counter = 1; // Only for callback - unsigned long int counterMax = 1; + dac_channel_t dacCh; // DAC channel for sound - SoundChNum chCount; // Total channels count, <= CONFIG_SND_MAX_CHANNELS - SoundChNum chFirstAuto; // Number of first "auto" channel - SemaphoreHandle_t chActiveCount = NULL; // Count of active channels (to control timer) - SoundProvider* chSound[CONFIG_SND_MAX_CHANNELS]; // Sound provider pointers (you should carry about memory by youself) - bool chActive[CONFIG_SND_MAX_CHANNELS]; // Active channels, UNSAFE - bool chPaused[CONFIG_SND_MAX_CHANNELS]; // Paused channels, UNSAFE + unsigned long int counter = 1; // Only for callback + unsigned long int counterMax = 1; - SoundVolume chVolume[CONFIG_SND_MAX_CHANNELS]; // Volume map + SoundChNum chCount; // Total channels count, <= CONFIG_SND_MAX_CHANNELS + SoundChNum chFirstAuto; // Number of first "auto" channel + std::atomic chActiveCount = {0}; // Count of active channels (to control timer) + std::array, CONFIG_SND_MAX_CHANNELS> chSound; // Sound provider pointers + std::array chActive; // Active channels, UNSAFE + std::array chPaused; // Paused channels, UNSAFE - void incSound(); // Increment counter - void decSound(); // Decrement counter + SoundVolume chVolume[CONFIG_SND_MAX_CHANNELS]; // Volume map - void soundCallback(); // Play one step - bool handleQueue(); // Handle suspended events (SAFE) - void setupTimer(); // Set divisors and start timer + void incSound(); // Increment counter + void decSound(); // Decrement counter - void addEvent(SoundControl event); + void soundCallback(); // Play one step + bool handleQueue(); // Handle suspended events (SEMI-SAFE) + void setupTimer(); // Set divisors and start timer - void checkTimer(); // Start one-shot "promo-"timer if isn't active - public: + void addEvent(const SoundControl& event); - SoundMixer(SoundChNum normal_channels, SoundChNum auto_channels, dac_channel_t dac); // Setup SoundMixer - ~SoundMixer(); // Destroy extra stuff + void checkTimer(); // Start one-shot "promo-"timer if isn't active + public: + SoundMixer(SoundChNum normal_channels, SoundChNum auto_channels, dac_channel_t dac); // Setup SoundMixer + ~SoundMixer(); // Destroy extra stuff - void play(SoundChNum channel, SoundProvider *sound); - void stop(SoundChNum channel); - void pause(SoundChNum channel); - void resume(SoundChNum channel); - void setVolume(SoundChNum channel, SoundVolume vol); - SoundVolume getVolume(SoundChNum channel); - SoundState state(SoundChNum channel); // SAFE + void play(SoundChNum channel, const std::shared_ptr& sound); + void stop(SoundChNum channel); + void pause(SoundChNum channel); + void restart(SoundChNum channel); + void resume(SoundChNum channel); + void setVolume(SoundChNum channel, SoundVolume vol); + SoundVolume getVolume(SoundChNum channel); + SoundState state(SoundChNum channel); // SAFE - SoundChNum playAuto(SoundProvider *sound, SoundVolume vol); // Auto select channel and play sound on it (if no aviable, count of channels will be returned) + SoundChNum playAuto(const std::shared_ptr& sound, SoundVolume vol); // Auto select channel and play sound on it (if no aviable, count of channels will be returned) - void stopAll(); - void pauseAll(); - void resumeAll(); -}; + void stopAll(); + void pauseAll(); + void restartAll(); + void resumeAll(); + + friend void ::sound_timer_callback_function(void* arg); + }; +} diff --git a/soundProvider.cpp b/soundProvider.cpp index e4ab165..4cbdf46 100644 --- a/soundProvider.cpp +++ b/soundProvider.cpp @@ -1,25 +1,27 @@ #include -SoundProvider::SoundProvider() { - queue = xQueueCreate(CONFIG_SND_PROVIDER_MAIN_QUEUE_SIZE, sizeof(SoundData)); - assert(queue != NULL); - controlQueue = xQueueCreate(CONFIG_SND_PROVIDER_CONTROL_QUEUE_SIZE, sizeof(SoundProviderControl)); - assert(controlQueue != NULL); -} +namespace Sound { + SoundProvider::SoundProvider() { + queue = xQueueCreate(CONFIG_SND_PROVIDER_MAIN_QUEUE_SIZE, sizeof(SoundData)); + assert(queue != nullptr); + controlQueue = xQueueCreate(CONFIG_SND_PROVIDER_CONTROL_QUEUE_SIZE, sizeof(SoundProviderControl)); + assert(controlQueue != nullptr); + } -SoundProvider::~SoundProvider() { - vQueueDelete(queue); - vQueueDelete(controlQueue); -} + SoundProvider::~SoundProvider() { + vQueueDelete(queue); + vQueueDelete(controlQueue); + } -void SoundProvider::postSample(SoundData sample) { - xQueueSendToBack(queue, &sample, portMAX_DELAY); -} + void SoundProvider::postSample(SoundData sample) { + xQueueSendToBack(queue, &sample, portMAX_DELAY); + } -void SoundProvider::postControl(SoundProviderControl ctrl) { - xQueueSendToBack(controlQueue, &ctrl, portMAX_DELAY); -} + void SoundProvider::postControl(SoundProviderControl ctrl) { + xQueueSendToBack(controlQueue, &ctrl, portMAX_DELAY); + } -void SoundProvider::queueReset() { - xQueueReset(queue); + void SoundProvider::queueReset() { + xQueueReset(queue); + } } diff --git a/soundProvider.h b/soundProvider.h index b88c066..3f37ea2 100644 --- a/soundProvider.h +++ b/soundProvider.h @@ -1,37 +1,46 @@ #pragma once - -class SoundMixer; - -class SoundProvider { // Abstract interface for sound providers. Include queues initialisation/deinitialisation. - protected: - QueueHandle_t queue = NULL; // Read from here - QueueHandle_t controlQueue = NULL; // Read controlling data from here - - // PROVIDER CONTROL INTERFACE START - virtual void provider_start() = 0; // Start filling (should be ok if started) - virtual void provider_pause() {}; // Optional methods for extra optimisation - virtual void provider_resume() {}; - virtual void provider_stop() = 0; // Stop filling (should be ok if isn't started) - - virtual void provider_restart() {provider_stop(); provider_start();} // This one calls if track repeats (default implementation should work, but it is better to write your own) - // PROVIDER CONTROL INTERFACE END - - void postSample(SoundData sample); - void postControl(SoundProviderControl ctrl); - - void queueReset(); - - virtual unsigned long int getFrequency() = 0; // Frequency in Hz, should be constant - - private: - unsigned int divisor = 1; // For different frequencies in same mixer - SoundData actual = 0; - - public: - SoundProvider(); - virtual ~SoundProvider(); - - bool repeat = false; // Implementation souldn't use any optimisations based on this - - friend SoundMixer; -}; +#include "soundDefines.h" + +namespace Sound { + class SoundProvider { // Abstract interface for sound providers. Include queues initialisation/deinitialisation. + protected: + QueueHandle_t queue = nullptr; // Read from here + QueueHandle_t controlQueue = nullptr; // Read controlling data from here + + // PROVIDER CONTROL INTERFACE START + virtual void provider_start() = 0; // Start filling (should be ok if started) + virtual void provider_pause() { + } + ; // Optional methods for extra optimisation + virtual void provider_resume() { + } + ; + virtual void provider_stop() = 0; // Stop filling (should be ok if isn't started) + + virtual void provider_restart() { + provider_stop(); + provider_start(); + } + ; // This one calls if track repeats (default implementation MAY NOT work) + // PROVIDER CONTROL INTERFACE END + + void postSample(SoundData sample); + void postControl(SoundProviderControl ctrl); + + void queueReset(); + + virtual unsigned long int getFrequency() = 0; // Frequency in Hz, should be constant + + private: + unsigned int divisor = 1; // For different frequencies in same mixer + SoundData actual = 0; + + public: + SoundProvider(); + virtual ~SoundProvider(); + + bool repeat = false; // Implementation souldn't use any optimisations based on this + + friend SoundMixer; + }; +} diff --git a/soundProviderTask.cpp b/soundProviderTask.cpp index 9f71936..3cdf47f 100644 --- a/soundProviderTask.cpp +++ b/soundProviderTask.cpp @@ -1,36 +1,56 @@ #include -#include -SoundProviderTask::SoundProviderTask() {} -SoundProviderTask::~SoundProviderTask() {} +extern "C" void sound_provider_start(void* arg) { + static_cast(arg)->taskProviderCode(); +} -void SoundProviderTask::provider_start() { - if (taskHandle == NULL) { - task_prestart(); - xTaskCreate(reinterpret_cast(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle); - task_poststart(); +namespace Sound { + SoundProviderTask::SoundProviderTask() { + } + SoundProviderTask::~SoundProviderTask() { } -} -void SoundProviderTask::provider_stop() { - if (taskHandle != NULL) { - task_prestop(); - vTaskDelete(taskHandle); - taskHandle = NULL; - task_poststop(); - queueReset(); + void SoundProviderTask::unconditionalStart() { + xTaskCreate(sound_provider_start, "SProvTask", stackSize, this, 10, &taskHandle); } -} -void SoundProviderTask::stopFromTask() { - TaskHandle_t handle = taskHandle; - taskHandle = NULL; - vTaskDelete(handle); - while(true) {}; -} + void SoundProviderTask::provider_start() { + if (taskHandle == nullptr) { + task_prestart(); + unconditionalStart(); + task_poststart(); + } + } -void SoundProviderTask::taskProviderCode() { - task_code(); - postControl(END); - stopFromTask(); + void SoundProviderTask::provider_stop() { + if (taskHandle != nullptr) { + task_prestop(); + vTaskDelete(taskHandle); + taskHandle = nullptr; + task_poststop(); + queueReset(); + } + } + + void SoundProviderTask::provider_restart() { + if (taskHandle != nullptr) { + task_prestop(); + vTaskDelete(taskHandle); + task_poststop(); + } + unconditionalStart(); + } + + void SoundProviderTask::stopFromTask() { + TaskHandle_t handle = taskHandle; + taskHandle = nullptr; + vTaskDelete(handle); + while(true) { + }; + } + + void SoundProviderTask::taskProviderCode() { + task_code(); + stopFromTask(); + } } diff --git a/soundProviderTask.h b/soundProviderTask.h index 1a97325..0a732b6 100644 --- a/soundProviderTask.h +++ b/soundProviderTask.h @@ -1,28 +1,49 @@ #pragma once - #include -class SoundProviderTask: public SoundProvider { - protected: - // TASK PROVIDER INTERFACE START - virtual void task_prestart() {}; - virtual void task_poststart() {}; - virtual void task_code() = 0; - virtual void task_prestop() {}; - virtual void task_poststop() {}; - // TASK PROVIDER INTERFACE END +extern "C" void sound_provider_start(void* arg); + +namespace Sound { + class SoundProviderTask: public SoundProvider { + protected: + // TASK PROVIDER INTERFACE START + virtual void task_prestart() { + } + ; + virtual void task_poststart() { + } + ; + virtual void task_code() = 0; + virtual void task_prestop() { + } + ; + virtual void task_poststop() { + } + ; + // TASK PROVIDER INTERFACE END + + // PROVIDER CONTROL INTERFACE START + virtual void provider_start() override final; // Start filling (should be ok if started) + virtual void provider_stop() override final; // Stop filling (should be ok if isn't started) + virtual void provider_restart() override; // This one calls if track repeats (default implementation MAY NOT work) + // PROVIDER CONTROL INTERFACE END + + void taskProviderCode(); + void stopFromTask(); + void unconditionalStart(); + void waitQueueEmpty() { + while(uxQueueMessagesWaiting(queue) > 0) + vTaskDelay(1); + } + ; // Idiomatic + TaskHandle_t taskHandle = nullptr; - // PROVIDER CONTROL INTERFACE START - void provider_start(); // Start filling (should be ok if started) - void provider_stop(); // Stop filling (should be ok if isn't started) - // PROVIDER CONTROL INTERFACE END + size_t stackSize = 2048; - void taskProviderCode(); - void stopFromTask(); - TaskHandle_t taskHandle = NULL; + public: + SoundProviderTask(); + virtual ~SoundProviderTask(); - size_t stackSize = 2048; - public: - SoundProviderTask(); - virtual ~SoundProviderTask(); -}; + friend void ::sound_provider_start(void* arg); + }; +}